(f0d812055) v0.9.9.0
This commit is contained in:
@@ -51,7 +51,8 @@
|
||||
<Item file="Content/Items/Legacy/legacypump.xml" />
|
||||
<Item file="Content/Items/Legacy/legacyrailgun.xml" />
|
||||
<Item file="Content/Items/Legacy/legacysearchlight.xml" />
|
||||
<Character file="Content/Characters/Carrier/Carrier.xml" />
|
||||
<Item file="Content/Items/Shipwrecks/wreckeditems.xml" />
|
||||
<Item file="Content/Map/Thalamus/thalamusitems.xml" />
|
||||
<Character file="Content/Characters/Charybdis/Charybdis.xml" />
|
||||
<Character file="Content/Characters/Coelanth/Coelanth.xml" />
|
||||
<Character file="Content/Characters/Crawler/Crawler.xml" />
|
||||
@@ -77,9 +78,16 @@
|
||||
<Character file="Content/Characters/Watcher/Watcher.xml" />
|
||||
<Character file="Content/Characters/Tigerthresher/Tigerthresher.xml" />
|
||||
<Character file="Content/Characters/Bonethresher/Bonethresher.xml" />
|
||||
<Character file="Content/Characters/Leucocyte/Leucocyte.xml" />
|
||||
<Character file="Content/Characters/Terminalcell/Terminalcell.xml" />
|
||||
<Outpost file="Content/Map/Outposts/Outpost.sub" />
|
||||
<Outpost file="Content/Map/Outposts/Outpost2.sub" />
|
||||
<Outpost file="Content/Map/Outposts/Outpost3.sub" />
|
||||
<Wreck file="Content/Map/Wrecks/Dugong_Wrecked.sub" />
|
||||
<Wreck file="Content/Map/Wrecks/Kastrull_Wrecked.sub" />
|
||||
<Wreck file="Content/Map/Wrecks/Berilia_Wrecked1.sub" />
|
||||
<Wreck file="Content/Map/Wrecks/Wreck1.sub" />
|
||||
<Wreck file="Content/Map/Wrecks/Hemulen_Wrecked.sub" />
|
||||
<Submarine file="Submarines/Berilia.sub" />
|
||||
<Submarine file="Submarines/Dugong.sub" />
|
||||
<Submarine file="Submarines/Hemulen.sub" />
|
||||
@@ -93,6 +101,7 @@
|
||||
<Submarine file="Submarines/Typhon.sub" />
|
||||
<Submarine file="Submarines/Typhon2.sub" />
|
||||
<Submarine file="Submarines/Venture.sub" />
|
||||
<Submarine file="Submarines/Azimuth.sub" />
|
||||
<Text file="Content/Texts/English/EnglishVanilla.xml" />
|
||||
<Text file="Content/Texts/German/GermanVanilla.xml" />
|
||||
<Text file="Content/Texts/French/FrenchVanilla.xml" />
|
||||
@@ -109,7 +118,10 @@
|
||||
<UIStyle file="Content/UI/style.xml" />
|
||||
<Afflictions file="Content/Afflictions.xml" />
|
||||
<Structure file="Content/Map/StructurePrefabs.xml" />
|
||||
<Structure file="Content/Items/Shipwrecks/StructurePrefabsWrecked.xml" />
|
||||
<Structure file="Content/Map/Thalamus/thalamusstructures.xml" />
|
||||
<RuinConfig file="Content/Map/RuinConfig.xml" />
|
||||
<WreckAIConfig file ="Content/Map/Thalamus/ThalamusAI.xml"/>
|
||||
<BackgroundCreaturePrefabs file="Content/BackgroundCreatures/BackgroundCreaturePrefabs.xml" />
|
||||
<LevelObjectPrefabs file="Content/LevelObjects/LevelObjectPrefabs.xml" />
|
||||
<Particles file="Content/Particles/ParticlePrefabs.xml" />
|
||||
@@ -136,6 +148,7 @@
|
||||
<NPCConversations file="Content/NPCConversations/Korean/NpcConversations_Korean.xml" />
|
||||
<Jobs file="Content/Jobs.xml" />
|
||||
<Orders file="Content/Orders.xml" />
|
||||
<Corpses file="Content/Corpses.xml" />
|
||||
<Sounds file="Content/Sounds/sounds.xml" />
|
||||
<Tutorials file="Content/Tutorials/Tutorials.xml" />
|
||||
<SkillSettings file="Content/SkillSettings.xml" />
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
kickbanthreshold="1"
|
||||
kicksbeforeban="3"
|
||||
karmanotificationinterval="15"
|
||||
resetkarmabetweenrounds="true" />
|
||||
resetkarmabetweenrounds="true"
|
||||
dangerousitemstealkarmadecrease="15"
|
||||
dangerousitemstealbots="false" />
|
||||
<Preset
|
||||
name="Strict"
|
||||
karmadecay="0.08"
|
||||
@@ -45,7 +47,9 @@
|
||||
kickbanthreshold="1"
|
||||
kicksbeforeban="1"
|
||||
karmanotificationinterval="15"
|
||||
resetkarmabetweenrounds="true" />
|
||||
resetkarmabetweenrounds="true"
|
||||
dangerousitemstealkarmadecrease="15"
|
||||
dangerousitemstealbots="true" />
|
||||
<Preset
|
||||
name="Custom"
|
||||
karmadecay="0.08"
|
||||
@@ -68,5 +72,7 @@
|
||||
kickbanthreshold="1"
|
||||
kicksbeforeban="3"
|
||||
karmanotificationinterval="15"
|
||||
resetkarmabetweenrounds="true" />
|
||||
resetkarmabetweenrounds="true"
|
||||
dangerousitemstealkarmadecrease="15"
|
||||
dangerousitemstealbots="false" />
|
||||
</KarmaManager>
|
||||
@@ -177,7 +177,8 @@ namespace Barotrauma
|
||||
StaticSight = true;
|
||||
}
|
||||
SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f);
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
string label = element.GetAttributeString("sonarlabel", "");
|
||||
SonarLabel = TextManager.Get(label, returnNull: true) ?? label;
|
||||
SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
|
||||
string typeString = element.GetAttributeString("type", "Any");
|
||||
if (Enum.TryParse(typeString, out TargetType t))
|
||||
|
||||
@@ -36,14 +36,16 @@ namespace Barotrauma
|
||||
|
||||
private readonly float updateTargetsInterval = 1;
|
||||
private readonly float updateMemoriesInverval = 1;
|
||||
private readonly float attackLimbResetInterval = 2;
|
||||
|
||||
private float avoidLookAheadDistance;
|
||||
private readonly float avoidLookAheadDistance;
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
private float updateTargetsTimer;
|
||||
private float updateMemoriesTimer;
|
||||
|
||||
private float attackLimbResetTimer;
|
||||
|
||||
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
|
||||
public float CombatStrength => Character.Params.AI.CombatStrength;
|
||||
private float Sight => Character.Params.AI.Sight;
|
||||
@@ -63,6 +65,7 @@ namespace Barotrauma
|
||||
get { return _attackingLimb; }
|
||||
private set
|
||||
{
|
||||
attackLimbResetTimer = 0;
|
||||
_attackingLimb = value;
|
||||
attackVector = null;
|
||||
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
|
||||
@@ -376,7 +379,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
var attackLimb = GetAttackLimb(SelectedAiTarget.WorldPosition);
|
||||
var attackLimb = AttackingLimb ?? GetAttackLimb(SelectedAiTarget.WorldPosition);
|
||||
if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
|
||||
{
|
||||
run = true;
|
||||
@@ -644,10 +647,14 @@ namespace Barotrauma
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
var target = pickable.Picker?.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
Entity owner = pickable.Picker ?? item.ParentInventory?.Owner;
|
||||
if (owner != null)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
var target = owner.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +692,7 @@ namespace Barotrauma
|
||||
WallSection section = wall.Sections[i];
|
||||
if (CanPassThroughHole(wall, i) && section?.gap != null)
|
||||
{
|
||||
if (SteerThroughGap(wall, section, section.gap.WorldPosition, deltaTime))
|
||||
if (SteerThroughGap(wall, section, wall.SectionPosition(i, true), deltaTime))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -980,7 +987,15 @@ namespace Barotrauma
|
||||
{
|
||||
// If not, reset the attacking limb, if the cooldown is not running
|
||||
// Don't use the property, because we don't want cancel reversing, if we are reversing.
|
||||
_attackingLimb = null;
|
||||
if (attackLimbResetTimer > attackLimbResetInterval)
|
||||
{
|
||||
_attackingLimb = null;
|
||||
attackLimbResetTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
attackLimbResetTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
Limb steeringLimb = canAttack ? AttackingLimb : null;
|
||||
@@ -1045,7 +1060,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
// Steer towards the target if in the same room and swimming
|
||||
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
|
||||
if ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
|
||||
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
}
|
||||
@@ -1112,31 +1128,13 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CanAttack(Entity target)
|
||||
{
|
||||
if (target == null) { return false; }
|
||||
if (target is Character c)
|
||||
{
|
||||
if (Character.CurrentHull == null && c.CurrentHull != null || Character.CurrentHull != null && c.CurrentHull == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (target is Item i && i.GetComponent<Door>() == null)
|
||||
{
|
||||
if (Character.CurrentHull == null && i.CurrentHull != null || Character.CurrentHull != null && i.CurrentHull == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly List<Limb> attackLimbs = new List<Limb>();
|
||||
private readonly List<float> weights = new List<float>();
|
||||
private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null)
|
||||
{
|
||||
var currentContexts = Character.GetAttackContexts();
|
||||
Entity target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity;
|
||||
if (!CanAttack(target)) { return null; }
|
||||
if (target == null) { return null; }
|
||||
Limb selectedLimb = null;
|
||||
float currentPriority = -1;
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
@@ -1153,12 +1151,26 @@ namespace Barotrauma
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
|
||||
}
|
||||
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { continue; }
|
||||
float priority = CalculatePriority(limb, attackWorldPos);
|
||||
if (priority > currentPriority)
|
||||
if (AIParams.RandomAttack)
|
||||
{
|
||||
currentPriority = priority;
|
||||
selectedLimb = limb;
|
||||
attackLimbs.Add(limb);
|
||||
weights.Add(limb.attack.Priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
float priority = CalculatePriority(limb, attackWorldPos);
|
||||
if (priority > currentPriority)
|
||||
{
|
||||
currentPriority = priority;
|
||||
selectedLimb = limb;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (AIParams.RandomAttack)
|
||||
{
|
||||
selectedLimb = ToolBox.SelectWeightedRandom(attackLimbs, weights, Rand.RandSync.Server);
|
||||
attackLimbs.Clear();
|
||||
weights.Clear();
|
||||
}
|
||||
return selectedLimb;
|
||||
|
||||
@@ -1176,18 +1188,22 @@ namespace Barotrauma
|
||||
{
|
||||
wallTarget = null;
|
||||
if (SelectedAiTarget == null) { return; }
|
||||
if (SelectedAiTarget.Entity == null) { return; }
|
||||
//check if there's a wall between the target and the Character
|
||||
Vector2 rayStart = SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
|
||||
if (offset)
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
|
||||
{
|
||||
rayEnd -= Character.Submarine.SimPosition;
|
||||
}
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true, ignoreSensors: CanEnterSubmarine, ignoreDisabledWalls: CanEnterSubmarine);
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null)
|
||||
{
|
||||
if (closestBody.UserData is Structure wall && wall.Submarine != null)
|
||||
if (closestBody.UserData is Structure wall && wall.Submarine != null && (wall.Submarine.Info.IsPlayer || wall.Submarine.Info.IsOutpost && TargetOutposts))
|
||||
{
|
||||
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
|
||||
float sectionDamage = wall.SectionDamage(sectionIndex);
|
||||
@@ -1265,20 +1281,26 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
if (attacker == null || attacker.AiTarget == null) { return; }
|
||||
bool isFriendly = attacker.SpeciesName == Character.SpeciesName || attacker.Params.Group == Character.Params.Group;
|
||||
if (wasLatched)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
if (!isFriendly)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (State == AIState.Flee)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
if (!isFriendly)
|
||||
{
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attackResult.Damage > 0.0f)
|
||||
if (!isFriendly && attackResult.Damage > 0.0f)
|
||||
{
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
|
||||
if (Character.Params.AI.AttackWhenProvoked && canAttack)
|
||||
@@ -1325,13 +1347,20 @@ namespace Barotrauma
|
||||
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
|
||||
}
|
||||
}
|
||||
else if (canAttack && attacker.IsHuman && AIParams.TryGetTarget(attacker.SpeciesName, out CharacterParams.TargetParams targetingParams))
|
||||
{
|
||||
if (targetingParams.State == AIState.Aggressive)
|
||||
{
|
||||
ChangeTargetState(attacker, AIState.Attack, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
bool retaliate = SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
|
||||
bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
|
||||
bool avoidGunFire = Character.Params.AI.AvoidGunfire && attacker.Submarine != Character.Submarine;
|
||||
|
||||
if (State == AIState.Attack && !IsCoolDownRunning)
|
||||
@@ -1376,6 +1405,8 @@ namespace Barotrauma
|
||||
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
if (damageTarget != null)
|
||||
{
|
||||
//simulate attack input to get the character to attack client-side
|
||||
Character.SetInput(InputType.Attack, true, true);
|
||||
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
|
||||
{
|
||||
if (damageTarget.Health > 0)
|
||||
@@ -1519,7 +1550,7 @@ namespace Barotrauma
|
||||
if (aiTarget.Type == AITarget.TargetType.HumanOnly) { continue; }
|
||||
if (!TargetOutposts)
|
||||
{
|
||||
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.IsOutpost) { continue; }
|
||||
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsOutpost) { continue; }
|
||||
}
|
||||
Character targetCharacter = aiTarget.Entity as Character;
|
||||
//ignore the aitarget if it is the Character itself
|
||||
@@ -1529,30 +1560,6 @@ namespace Barotrauma
|
||||
string targetingTag = null;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
// In a different sub or the target is outside when we are inside or vice versa.
|
||||
// Make an exception for humans so that creatures cannot avoid/flee from humans that are inside a sub.
|
||||
if (!targetCharacter.IsHuman && State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
|
||||
{
|
||||
// If we are escaping, let's not ignore the target entirely, because there can be a gaps where we or they can go freely
|
||||
if (targetCharacter.Submarine != null)
|
||||
{
|
||||
// Target is inside -> reduce the priority
|
||||
valueModifier *= 0.5f;
|
||||
if (Character.Submarine != null)
|
||||
{
|
||||
// Both inside different submarines -> can ignore safely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't attack targets that are not in the same submarine
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (targetCharacter.IsDead)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
@@ -1604,9 +1611,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (aiTarget.Entity != null)
|
||||
{
|
||||
// Ignore all structures and items inside wrecks
|
||||
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsWreck) { continue; }
|
||||
// Ignore the target if it's a room and the character is already inside a sub
|
||||
if (character.CurrentHull != null && aiTarget.Entity is Hull) { continue; }
|
||||
|
||||
|
||||
Door door = null;
|
||||
if (aiTarget.Entity is Item item)
|
||||
{
|
||||
@@ -1644,15 +1653,13 @@ namespace Barotrauma
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (character.CurrentHull != null)
|
||||
bool isCharacterOutside = s.Submarine == null || character.CurrentHull == null;
|
||||
bool targetInnerWalls = AIParams.TargetInnerWalls;
|
||||
if (!isCharacterOutside && !targetInnerWalls)
|
||||
{
|
||||
// Ignore walls when inside (walltargets still work)
|
||||
continue;
|
||||
}
|
||||
if (s.Submarine == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
valueModifier = 1;
|
||||
if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s))
|
||||
{
|
||||
@@ -1665,28 +1672,43 @@ namespace Barotrauma
|
||||
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
|
||||
if (Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
if (CanPassThroughHole(s, i))
|
||||
if (isCharacterOutside)
|
||||
{
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : (targetInnerWalls ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
|
||||
if (!canAttackSub)
|
||||
{
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
if (AggressiveBoarding && leadsInside)
|
||||
{
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!canAttackSub || CanPassThroughHole(s, i))
|
||||
{
|
||||
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
|
||||
if (!canAttackSub)
|
||||
{
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
// Up to 100% priority increase for every gap in the wall
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
// Already inside -> ignore holes in the walls and ignore walls if cannot attack the sub.
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (canAttackSub && !AggressiveBoarding)
|
||||
{
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
}
|
||||
}
|
||||
else if (!leadsInside)
|
||||
else if (!leadsInside || !canAttackSub)
|
||||
{
|
||||
// Ignore inner walls
|
||||
// Can't get in, ignore inner walls
|
||||
// Also ignore all walls if cannot attack the sub
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
@@ -1774,6 +1796,50 @@ namespace Barotrauma
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
{
|
||||
if (targetCharacter.Submarine != null)
|
||||
{
|
||||
// Target is inside -> reduce the priority
|
||||
valueModifier *= 0.5f;
|
||||
if (Character.Submarine != null)
|
||||
{
|
||||
// Both inside different submarines -> can ignore safely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Character.CurrentHull != null)
|
||||
{
|
||||
// Target outside, but we are inside -> Check if we can get to the target.
|
||||
// Only check if we are not already targeting the character.
|
||||
// If we are, keep the target (unless we choose another).
|
||||
if (SelectedAiTarget?.Entity != targetCharacter)
|
||||
{
|
||||
foreach (var gap in Character.CurrentHull.ConnectedGaps)
|
||||
{
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door == null || !door.IsOpen)
|
||||
{
|
||||
var wall = gap.ConnectedWall;
|
||||
if (wall != null)
|
||||
{
|
||||
for (int j = 0; j < wall.Sections.Length; j++)
|
||||
{
|
||||
WallSection section = wall.Sections[j];
|
||||
if (!CanPassThroughHole(wall, j) && section?.gap != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableCrewAI || Character.IsUnconscious || Character.Removed) { return; }
|
||||
if (DisableCrewAI || Character.IsIncapacitated || Character.Removed) { return; }
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (unreachableClearTimer > 0)
|
||||
@@ -139,7 +139,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
ReportProblems();
|
||||
if (Character.Submarine != null && Character.Submarine.TeamID == Character.TeamID && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
ReportProblems();
|
||||
}
|
||||
UpdateSpeaking();
|
||||
}
|
||||
UnequipUnnecessaryItems();
|
||||
@@ -1049,12 +1052,59 @@ namespace Barotrauma
|
||||
|
||||
private static bool FilterCrewMember(Character self, Character other) => other != null && !other.IsDead && !other.Removed && other.AIController is HumanAIController humanAi && humanAi.IsFriendly(self);
|
||||
|
||||
public static bool IsItemOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
|
||||
{
|
||||
operatingCharacter = null;
|
||||
if (target?.Item == null) { return false; }
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (character != null && c == character) { continue; }
|
||||
if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
|
||||
if (c.SelectedConstruction != target.Item) { continue; }
|
||||
operatingCharacter = c;
|
||||
// If the other character is player, don't try to operate
|
||||
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
|
||||
if (c.AIController is HumanAIController controllingHumanAi)
|
||||
{
|
||||
// If the other character is ordered to operate the item, let him do it
|
||||
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
|
||||
}
|
||||
else
|
||||
{
|
||||
return target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't go here, unless we allow non-humans to operate items
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Wrappers
|
||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
||||
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
|
||||
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
|
||||
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
|
||||
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
|
||||
public bool IsItemOperatedByAnother(ItemComponent target, out Character operatingCharacter) => IsItemOperatedByAnother(Character, target, out operatingCharacter);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,9 +136,10 @@ namespace Barotrauma
|
||||
string allowedJobsStr = element.GetAttributeString("allowedjobs", "");
|
||||
foreach (string allowedJobIdentifier in allowedJobsStr.Split(','))
|
||||
{
|
||||
if (JobPrefab.Prefabs.ContainsKey(allowedJobIdentifier.ToLowerInvariant()))
|
||||
string key = allowedJobIdentifier.ToLowerInvariant();
|
||||
if (JobPrefab.Prefabs.ContainsKey(key))
|
||||
{
|
||||
AllowedJobs.Add(JobPrefab.Prefabs[allowedJobIdentifier.ToLowerInvariant()]);
|
||||
AllowedJobs.Add(JobPrefab.Prefabs[key]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Barotrauma
|
||||
|
||||
public virtual bool KeepDivingGearOn => false;
|
||||
public virtual bool UnequipItems => false;
|
||||
public virtual bool AllowOutsideSubmarine => false;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
@@ -182,11 +183,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsAllowed => AllowOutsideSubmarine || character.Submarine != null && character.Submarine.TeamID == character.TeamID && character.Submarine.Info.IsPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Call this only when the priority needs to be recalculated. Use the cached Priority property when you don't need to recalculate.
|
||||
/// </summary>
|
||||
public virtual float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
|
||||
+5
-1
@@ -23,8 +23,12 @@ namespace Barotrauma
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
}
|
||||
if (item.ConditionPercentage <= 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
if (IsReady(battery)) { return false; }
|
||||
return true;
|
||||
|
||||
+17
-5
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
|
||||
private readonly CombatMode initialMode;
|
||||
|
||||
@@ -149,12 +150,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (!HoldPosition && seekAmmunition == null)
|
||||
{
|
||||
Move();
|
||||
Move(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Move()
|
||||
private void Move(float deltaTime)
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
@@ -163,7 +164,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case CombatMode.Defensive:
|
||||
case CombatMode.Retreat:
|
||||
Retreat();
|
||||
Retreat(deltaTime);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
@@ -411,7 +412,10 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Retreat()
|
||||
private float findHullTimer;
|
||||
private readonly float findHullInterval = 1.0f;
|
||||
|
||||
private void Retreat(float deltaTime)
|
||||
{
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
RemoveSubObjective(ref seekAmmunition);
|
||||
@@ -421,7 +425,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||
{
|
||||
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls);
|
||||
if (findHullTimer > 0)
|
||||
{
|
||||
findHullTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
retreatTarget = findSafety.FindBestHull(HumanAIController.VisibleHulls);
|
||||
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
}
|
||||
if (retreatTarget != null && character.CurrentHull != retreatTarget)
|
||||
{
|
||||
|
||||
+5
@@ -27,6 +27,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
|
||||
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
|
||||
+5
-1
@@ -32,7 +32,11 @@ namespace Barotrauma
|
||||
if (hull.FireSources.None()) { return false; }
|
||||
if (hull.Submarine == null) { return false; }
|
||||
if (hull.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (hull.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -40,7 +40,11 @@ namespace Barotrauma
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (target.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-9
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
|
||||
// TODO: expose?
|
||||
@@ -35,6 +36,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
|
||||
@@ -77,7 +83,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
Priority = Math.Min(Priority + dangerFactor * priorityIncrease * deltaTime, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +106,7 @@ namespace Barotrauma
|
||||
if (needsEquipment && divingGearObjective == null && !character.LockHands)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
@@ -139,14 +145,14 @@ namespace Barotrauma
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
}
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD ||
|
||||
HumanAIController.NeedsDivingGear(character, currentHull, out bool needsSuit) && (needsSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingMask(character)))
|
||||
{
|
||||
resetPriority = true;
|
||||
@@ -244,10 +250,8 @@ namespace Barotrauma
|
||||
//(no need to do the expensive pathfinding if we already know we're not going to choose this hull)
|
||||
if (hullSafety < bestValue) { continue; }
|
||||
// Don't allow to go outside if not already outside.
|
||||
var path = character.CurrentHull != null ?
|
||||
PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, nodeFilter: node => node.Waypoint.CurrentHull != null) :
|
||||
PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
|
||||
if (path.Unreachable && character.CurrentHull != null)
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
if (path.Unreachable)
|
||||
{
|
||||
HumanAIController.UnreachableHulls.Add(hull);
|
||||
continue;
|
||||
@@ -265,7 +269,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
// Outside
|
||||
if (hull.RoomName != null && hull.RoomName.ToLowerInvariant().Contains("airlock"))
|
||||
if (hull.RoomName != null && hull.RoomName.Contains("airlock", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
@@ -287,6 +291,7 @@ namespace Barotrauma
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
hullSafety *= distanceFactor;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
// Intentionally exclude wrecks from this check
|
||||
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
hullSafety /= 10;
|
||||
|
||||
+13
-5
@@ -1,9 +1,9 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,15 +20,23 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
private AIObjectiveOperateItem operateObjective;
|
||||
|
||||
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
|
||||
public bool IgnoreSeverityAndDistance { get; private set; }
|
||||
|
||||
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool ignoreSeverityAndDistance = false) : base (character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Leak = leak;
|
||||
IgnoreSeverityAndDistance = ignoreSeverityAndDistance;
|
||||
}
|
||||
|
||||
protected override bool Check() => Leak.Open <= 0 || Leak.Removed;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (Leak.Removed || Leak.Open <= 0)
|
||||
{
|
||||
Priority = 0;
|
||||
@@ -39,8 +47,8 @@ namespace Barotrauma
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||
float distanceFactor = xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
|
||||
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float distanceFactor = IgnoreSeverityAndDistance || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, xDist + yDist * 3.0f));
|
||||
float severity = IgnoreSeverityAndDistance ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
|
||||
+11
-5
@@ -1,7 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -11,8 +9,12 @@ namespace Barotrauma
|
||||
public override string DebugTag => "fix leaks";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
private Hull PrioritizedHull { get; set; }
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Hull prioritizedHull = null) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
PrioritizedHull = prioritizedHull;
|
||||
}
|
||||
|
||||
protected override bool Filter(Gap gap) => IsValidTarget(gap, character);
|
||||
|
||||
@@ -60,7 +62,7 @@ namespace Barotrauma
|
||||
|
||||
protected override IEnumerable<Gap> GetList() => Gap.GapList;
|
||||
protected override AIObjective ObjectiveConstructor(Gap gap)
|
||||
=> new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier);
|
||||
=> new AIObjectiveFixLeak(gap, character, objectiveManager, priorityModifier: PriorityModifier, ignoreSeverityAndDistance: gap.FlowTargetHull == PrioritizedHull);
|
||||
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Gap target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveFixLeaks, Gap>(character, target);
|
||||
@@ -71,7 +73,11 @@ namespace Barotrauma
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null) { return false; }
|
||||
if (gap.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (gap.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -220,7 +220,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
}
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
float itemPriority = 1;
|
||||
if (GetItemPriority != null)
|
||||
@@ -276,6 +280,7 @@ namespace Barotrauma
|
||||
|
||||
private bool CheckItem(Item item)
|
||||
{
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (ignoredItems.Contains(item)) { return false; };
|
||||
if (item.Condition < TargetCondition) { return false; }
|
||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||
|
||||
+7
-4
@@ -44,6 +44,9 @@ namespace Barotrauma
|
||||
public bool AllowGoingOutside { get; set; }
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
|
||||
public string DialogueIdentifier { get; set; }
|
||||
public string TargetName { get; set; }
|
||||
|
||||
@@ -65,13 +68,13 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.GetPriority();
|
||||
Priority = objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : 10;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
|
||||
: base (character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.Target = target;
|
||||
this.repeat = repeat;
|
||||
@@ -204,7 +207,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (needsEquipment)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
|
||||
+8
-5
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "idle";
|
||||
public override bool UnequipItems => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
|
||||
private readonly float newTargetIntervalMin = 10;
|
||||
private readonly float newTargetIntervalMax = 20;
|
||||
@@ -231,9 +232,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (HumanAIController.UnsafeHulls.Contains(hull)) { continue; }
|
||||
if (hull.Submarine == null) { continue; }
|
||||
if (hull.Submarine.TeamID != character.TeamID) { continue; }
|
||||
// If the character is inside, only take connected hulls into account.
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
if (character.Submarine == null) { break; }
|
||||
if (hull.Submarine.TeamID != character.Submarine.TeamID) { continue; }
|
||||
if (hull.Submarine.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
// If the character is inside, only take connected subs into account.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
if (IsForbidden(hull)) { continue; }
|
||||
// Ignore hulls that are too low to stand inside
|
||||
if (character.AnimController is HumanoidAnimController animController)
|
||||
@@ -261,9 +264,9 @@ namespace Barotrauma
|
||||
public static bool IsForbidden(Hull hull)
|
||||
{
|
||||
if (hull == null) { return true; }
|
||||
string hullName = hull.RoomName?.ToLowerInvariant();
|
||||
string hullName = hull.RoomName;
|
||||
if (hullName == null) { return false; }
|
||||
return hullName.Contains("ballast") || hullName.Contains("airlock");
|
||||
return hullName.Contains("ballast", StringComparison.OrdinalIgnoreCase) || hullName.Contains("airlock", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (character.LockHands || character.Submarine == null || Targets.None())
|
||||
{
|
||||
Priority = 0;
|
||||
|
||||
+37
-21
@@ -1,9 +1,10 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -102,20 +103,12 @@ namespace Barotrauma
|
||||
{
|
||||
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)) :
|
||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType));
|
||||
matchingItems.RemoveAll(it => it.Submarine != character.Submarine);
|
||||
var item = matchingItems.GetRandom();
|
||||
var order = new Order(
|
||||
orderPrefab,
|
||||
item ?? character.CurrentHull as Entity,
|
||||
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType),
|
||||
orderGiver: character);
|
||||
var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, false)?.GetRandom() : null;
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
|
||||
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
var objective = CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier);
|
||||
if (objective != null)
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
objectiveCount++;
|
||||
@@ -220,7 +213,7 @@ namespace Barotrauma
|
||||
}
|
||||
GetCurrentObjective()?.SortSubObjectives();
|
||||
}
|
||||
|
||||
|
||||
public void DoCurrentObjective(float deltaTime)
|
||||
{
|
||||
if (WaitTimer <= 0)
|
||||
@@ -232,7 +225,7 @@ namespace Barotrauma
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
{
|
||||
CurrentOrder = objective;
|
||||
@@ -271,13 +264,13 @@ namespace Barotrauma
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
newObjective = new AIObjectiveGoTo(order.TargetEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
AllowGoingOutside = character.CurrentHull == null
|
||||
};
|
||||
break;
|
||||
case "fixleaks":
|
||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier);
|
||||
newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier: priorityModifier, prioritizedHull: order.TargetEntity as Hull);
|
||||
break;
|
||||
case "chargebatteries":
|
||||
newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier);
|
||||
@@ -286,13 +279,24 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveRescueAll(character, this, priorityModifier);
|
||||
break;
|
||||
case "repairsystems":
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier)
|
||||
case "repairmechanical":
|
||||
case "repairelectrical":
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
|
||||
{
|
||||
RelevantSkill = order.AppropriateSkill,
|
||||
RequireAdequateSkills = option == "jobspecific"
|
||||
};
|
||||
break;
|
||||
case "pumpwater":
|
||||
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
|
||||
if (order.TargetItemComponent is Pump targetPump)
|
||||
{
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier);
|
||||
// newObjective.Completed += DismissSelf;
|
||||
}
|
||||
else
|
||||
{
|
||||
newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier);
|
||||
}
|
||||
break;
|
||||
case "extinguishfires":
|
||||
newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier);
|
||||
@@ -324,6 +328,18 @@ namespace Barotrauma
|
||||
return newObjective;
|
||||
}
|
||||
|
||||
private void DismissSelf()
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.IsSinglePlayer)
|
||||
{
|
||||
GameMain.GameSession?.CrewManager?.SetCharacterOrder(character, Order.GetPrefab("dismissed"), null, character);
|
||||
}
|
||||
#else
|
||||
GameMain.Server?.SendOrderChatMessage(new OrderChatMessage(Order.GetPrefab("dismissed"), null, null, character, character));
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool IsAllowedToWait()
|
||||
{
|
||||
if (CurrentOrder != null) { return false; }
|
||||
|
||||
+22
-52
@@ -32,6 +32,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (component.Item.ConditionPercentage <= 0)
|
||||
{
|
||||
Priority = 0;
|
||||
@@ -42,11 +47,21 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
if (component.Item.CurrentHull == null || component.Item.CurrentHull.FireSources.Any() || IsOperatedByAnother(character, GetTarget(), out _))
|
||||
Item targetItem = GetTarget()?.Item;
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Item or component of AI Objective Operate item wass null. This shouldn't happen.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
Priority = 0;
|
||||
return 0.0f;
|
||||
}
|
||||
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(GetTarget(), out _))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
else if (Character.CharacterList.Any(c => c.CurrentHull == targetItem.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
@@ -60,8 +75,8 @@ namespace Barotrauma
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
: base (character, objectiveManager, priorityModifier, option)
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
this.requireEquip = requireEquip;
|
||||
@@ -76,51 +91,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsOperatedByAnother(Character character, ItemComponent target, out Character operatingCharacter)
|
||||
{
|
||||
operatingCharacter = null;
|
||||
foreach (var c in Character.CharacterList)
|
||||
{
|
||||
if (character != null && c == character) { continue; }
|
||||
if (character?.AIController is HumanAIController humanAi && !humanAi.IsFriendly(c)) { continue; }
|
||||
if (c.SelectedConstruction != target.Item) { continue; }
|
||||
operatingCharacter = c;
|
||||
// If the other character is player, don't try to operate
|
||||
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
|
||||
if (c.AIController is HumanAIController controllingHumanAi)
|
||||
{
|
||||
// If the other character is ordered to operate the item, let him do it
|
||||
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (target is Steering)
|
||||
{
|
||||
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
|
||||
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
|
||||
}
|
||||
else
|
||||
{
|
||||
return target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shouldn't go here, unless we allow non-humans to operate items
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands)
|
||||
@@ -136,7 +106,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
|
||||
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(character, target, out _))
|
||||
if (objectiveManager.CurrentOrder != this && HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
{
|
||||
// Don't abandon
|
||||
return;
|
||||
@@ -161,7 +131,7 @@ namespace Barotrauma
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = target.Item.Name
|
||||
},
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
}
|
||||
@@ -176,7 +146,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!character.Inventory.Items.Contains(component.Item))
|
||||
{
|
||||
TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true),
|
||||
TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref getItemObjective));
|
||||
}
|
||||
|
||||
+5
-1
@@ -33,7 +33,11 @@ namespace Barotrauma
|
||||
if (pump.Item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (pump.Item.ConditionPercentage <= 0) { return false; }
|
||||
if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (pump.Item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; }
|
||||
}
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
if (IsReady(pump)) { return false; }
|
||||
return true;
|
||||
|
||||
+18
-10
@@ -20,14 +20,22 @@ namespace Barotrauma
|
||||
private RepairTool repairTool;
|
||||
|
||||
private bool IsRepairing => character.SelectedConstruction == Item && Item.GetComponent<Repairable>()?.CurrentFixer == character;
|
||||
private readonly bool isPriority;
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
|
||||
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
Item = item;
|
||||
this.isPriority = isPriority;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
// TODO: priority list?
|
||||
// Ignore items that are being repaired by someone else.
|
||||
if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character))
|
||||
@@ -36,16 +44,16 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
if (Item.CurrentHull == character.CurrentHull)
|
||||
float distanceFactor = 1;
|
||||
if (!isPriority && Item.CurrentHull != character.CurrentHull)
|
||||
{
|
||||
distanceFactor = 1;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
}
|
||||
float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float damagePriority = isPriority ? 1 : MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = isPriority ? 1 : MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float isSelected = IsRepairing ? 50 : 0;
|
||||
float devotion = (CumulatedDevotion + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
@@ -165,7 +173,7 @@ namespace Barotrauma
|
||||
}
|
||||
repairable.StopRepairing(character);
|
||||
}
|
||||
else
|
||||
else if (repairable.CurrentFixer != character)
|
||||
{
|
||||
repairable.StartRepairing(character, Repairable.FixActions.Repair);
|
||||
}
|
||||
|
||||
+22
-3
@@ -15,12 +15,23 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool RequireAdequateSkills;
|
||||
|
||||
/// <summary>
|
||||
/// If set, only fix items where required skill matches this.
|
||||
/// </summary>
|
||||
public string RelevantSkill;
|
||||
|
||||
private readonly Item prioritizedItem;
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
|
||||
public override bool IsDuplicate<T>(T otherObjective) =>
|
||||
(otherObjective as AIObjective) is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills;
|
||||
|
||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.prioritizedItem = prioritizedItem;
|
||||
}
|
||||
|
||||
protected override void CreateObjectives()
|
||||
{
|
||||
@@ -71,6 +82,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(RelevantSkill))
|
||||
{
|
||||
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -103,7 +118,7 @@ namespace Barotrauma
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
=> new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier);
|
||||
=> new AIObjectiveRepairItem(character, item, objectiveManager, priorityModifier: PriorityModifier, isPriority: item == prioritizedItem);
|
||||
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveRepairItems, Item>(character, target);
|
||||
@@ -116,7 +131,11 @@ namespace Barotrauma
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-12
@@ -22,6 +22,8 @@ namespace Barotrauma
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private float treatmentTimer;
|
||||
private Hull safeHull;
|
||||
private float findHullTimer;
|
||||
private readonly float findHullInterval = 1.0f;
|
||||
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -56,14 +58,17 @@ namespace Barotrauma
|
||||
|
||||
if (targetCharacter != character)
|
||||
{
|
||||
// Unconcious target is not in a safe place -> Move to a safe place first
|
||||
if (targetCharacter.IsUnconscious && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
if (targetCharacter.IsIncapacitated && HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
|
||||
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
|
||||
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||
if (targetCharacter.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget", new string[2] { "[targetname]", "[roomname]" },
|
||||
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
|
||||
null, 1.0f, "foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
// Go to the target and select it
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
@@ -92,9 +97,17 @@ namespace Barotrauma
|
||||
// Drag the character into safety
|
||||
if (safeHull == null)
|
||||
{
|
||||
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
|
||||
if (findHullTimer > 0)
|
||||
{
|
||||
findHullTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
safeHull = objectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull(HumanAIController.VisibleHulls);
|
||||
findHullTimer = findHullInterval * Rand.Range(0.9f, 1.1f);
|
||||
}
|
||||
}
|
||||
if (character.CurrentHull != safeHull)
|
||||
if (safeHull != null && character.CurrentHull != safeHull)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(safeHull, character, objectiveManager),
|
||||
@@ -132,10 +145,13 @@ namespace Barotrauma
|
||||
{
|
||||
// We can start applying treatment
|
||||
if (character != targetCharacter && character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" },
|
||||
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
|
||||
null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f);
|
||||
{
|
||||
if (targetCharacter.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundWoundedTarget", new string[2] { "[targetname]", "[roomname]" },
|
||||
new string[2] { targetCharacter.Name, targetCharacter.CurrentHull.DisplayName }, new bool[2] { false, true }),
|
||||
null, 1.0f, "foundwoundedtarget" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
character.SelectCharacter(targetCharacter);
|
||||
}
|
||||
@@ -151,7 +167,7 @@ namespace Barotrauma
|
||||
if (!targetCharacter.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController.SteeringManager.Reset();
|
||||
targetCharacter.AIController?.SteeringManager.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
@@ -278,6 +294,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
{
|
||||
Priority = 0;
|
||||
|
||||
+6
-1
@@ -73,6 +73,7 @@ namespace Barotrauma
|
||||
public static float GetVitalityFactor(Character character)
|
||||
{
|
||||
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
|
||||
@@ -105,7 +106,11 @@ namespace Barotrauma
|
||||
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; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
}
|
||||
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
{
|
||||
// Ignore all concious targets that are currently fighting, fleeing or treating characters
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,8 +14,7 @@ namespace Barotrauma
|
||||
Movement,
|
||||
Power,
|
||||
Maintenance,
|
||||
Operate,
|
||||
Undefined
|
||||
Operate
|
||||
}
|
||||
|
||||
class Order
|
||||
@@ -31,11 +31,7 @@ namespace Barotrauma
|
||||
return order;
|
||||
}
|
||||
|
||||
public Order Prefab
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public Order Prefab { get; private set; }
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
@@ -55,7 +51,7 @@ namespace Barotrauma
|
||||
{
|
||||
return color.Value;
|
||||
}
|
||||
else if (OrderCategoryIcons.TryGetValue(Category, out Tuple<Sprite, Color> sprite))
|
||||
else if (Category.HasValue && OrderCategoryIcons.TryGetValue((OrderCategory)Category, out Tuple<Sprite, Color> sprite))
|
||||
{
|
||||
return sprite.Item2;
|
||||
}
|
||||
@@ -83,7 +79,8 @@ namespace Barotrauma
|
||||
|
||||
public Character OrderGiver;
|
||||
|
||||
public readonly OrderCategory Category;
|
||||
private readonly OrderCategory? category;
|
||||
public OrderCategory? Category => category;
|
||||
|
||||
//legacy support
|
||||
public readonly string[] AppropriateJobs;
|
||||
@@ -93,6 +90,25 @@ namespace Barotrauma
|
||||
public readonly Dictionary<string, Sprite> OptionSprites;
|
||||
|
||||
public readonly float Weight;
|
||||
public readonly bool MustSetTarget;
|
||||
public readonly string AppropriateSkill;
|
||||
|
||||
public bool HasOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPrefab)
|
||||
{
|
||||
return MustSetTarget || Options.Length > 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Prefab.MustSetTarget || Prefab.Options.Length > 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool IsPrefab { get; private set; }
|
||||
public readonly bool MustManuallyAssign;
|
||||
|
||||
static Order()
|
||||
{
|
||||
@@ -197,8 +213,11 @@ namespace Barotrauma
|
||||
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
|
||||
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
Category = (OrderCategory)Enum.Parse(typeof(OrderCategory), orderElement.GetAttributeString("category", "undefined"), true);
|
||||
var category = orderElement.GetAttributeString("category", null);
|
||||
if (!string.IsNullOrWhiteSpace(category)) { this.category = (OrderCategory)Enum.Parse(typeof(OrderCategory), category, true); }
|
||||
Weight = orderElement.GetAttributeFloat(0.0f, "weight");
|
||||
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
|
||||
AppropriateSkill = orderElement.GetAttributeString("appropriateskill", null);
|
||||
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + Identifier, true);
|
||||
if (translatedOptionNames == null)
|
||||
@@ -241,6 +260,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -253,6 +275,7 @@ namespace Barotrauma
|
||||
Name = prefab.Name;
|
||||
Identifier = prefab.Identifier;
|
||||
ItemComponentType = prefab.ItemComponentType;
|
||||
ItemIdentifiers = prefab.ItemIdentifiers;
|
||||
Options = prefab.Options;
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
Color = prefab.Color;
|
||||
@@ -261,22 +284,35 @@ namespace Barotrauma
|
||||
AppropriateJobs = prefab.AppropriateJobs;
|
||||
FadeOutTime = prefab.FadeOutTime;
|
||||
Weight = prefab.Weight;
|
||||
Category = prefab.Category;
|
||||
OrderGiver = orderGiver;
|
||||
MustSetTarget = prefab.MustSetTarget;
|
||||
AppropriateSkill = prefab.AppropriateSkill;
|
||||
category = prefab.Category;
|
||||
MustManuallyAssign = prefab.MustManuallyAssign;
|
||||
|
||||
OrderGiver = orderGiver;
|
||||
TargetEntity = targetEntity;
|
||||
if (targetItem != null)
|
||||
{
|
||||
if (UseController)
|
||||
{
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
ConnectedController =
|
||||
targetItem.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
targetItem.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
}
|
||||
if (UseController) { ConnectedController = FindController(targetItem); }
|
||||
TargetEntity = targetItem.Item;
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
private Controller FindController(ItemComponent targetComponent)
|
||||
{
|
||||
if (targetComponent?.Item == null) { return null; }
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
return targetComponent.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
targetComponent.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
}
|
||||
|
||||
private bool TryFindController(ItemComponent targetComponent, out Controller controller)
|
||||
{
|
||||
controller = FindController(targetComponent);
|
||||
return controller != null;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
@@ -291,7 +327,7 @@ namespace Barotrauma
|
||||
}
|
||||
for (int i = 0; i < AppropriateJobs.Length; i++)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier.ToLowerInvariant() == AppropriateJobs[i].ToLowerInvariant()) { return true; }
|
||||
if (character.Info.Job.Prefab.Identifier.Equals(AppropriateJobs[i], StringComparison.OrdinalIgnoreCase)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -310,5 +346,40 @@ namespace Barotrauma
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public List<Item> GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub)
|
||||
{
|
||||
List<Item> matchingItems = new List<Item>();
|
||||
if (submarine == null) { return matchingItems; }
|
||||
if (ItemComponentType != null || ItemIdentifiers.Length > 0)
|
||||
{
|
||||
matchingItems = ItemIdentifiers.Length > 0 ?
|
||||
Item.ItemList.FindAll(it => ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(ItemIdentifiers)) :
|
||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == ItemComponentType));
|
||||
if (mustBelongToPlayerSub)
|
||||
{
|
||||
matchingItems.RemoveAll(it => it.Submarine?.Info != null && it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player);
|
||||
matchingItems.RemoveAll(it => it.Submarine != submarine && !submarine.DockedTo.Contains(it.Submarine));
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingItems.RemoveAll(it => it.Submarine != submarine);
|
||||
}
|
||||
matchingItems.RemoveAll(it => it.NonInteractable);
|
||||
if (UseController)
|
||||
{
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType && TryFindController(c, out _)));
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
}
|
||||
|
||||
public List<Item> GetMatchingItems(bool mustBelongToPlayerSub)
|
||||
{
|
||||
Submarine submarine = Character.Controlled != null && Character.Controlled.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1 ?
|
||||
Submarine.MainSubs[1] :
|
||||
Submarine.MainSub;
|
||||
return GetMatchingItems(submarine, mustBelongToPlayerSub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Networking;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WreckAI : IServerSerializable
|
||||
{
|
||||
public Submarine Wreck { get; private set; }
|
||||
|
||||
public bool IsAlive { get; private set; }
|
||||
|
||||
private readonly List<Item> allItems;
|
||||
private readonly List<Item> thalamusItems;
|
||||
private readonly List<Structure> thalamusStructures;
|
||||
private readonly List<Turret> turrets = new List<Turret>();
|
||||
private readonly List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
private readonly List<Hull> hulls = new List<Hull>();
|
||||
private readonly List<Item> spawnOrgans = new List<Item>();
|
||||
private readonly Item brain;
|
||||
|
||||
private bool initialCellsSpawned;
|
||||
|
||||
public readonly WreckAIConfig Config;
|
||||
|
||||
private bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
private bool IsThalamus(MapEntityPrefab entityPrefab) => IsThalamus(entityPrefab, Config.Entity);
|
||||
|
||||
private static IEnumerable<T> GetThalamusEntities<T>(Submarine wreck, string tag) where T : MapEntity => GetThalamusEntities(wreck, tag).Where(e => e is T).Select(e => e as T);
|
||||
|
||||
private static IEnumerable<MapEntity> GetThalamusEntities(Submarine wreck, string tag) => MapEntity.mapEntityList.Where(e => e.Submarine == wreck && e.prefab != null && IsThalamus(e.prefab, tag));
|
||||
|
||||
private static bool IsThalamus(MapEntityPrefab entityPrefab, string tag) => entityPrefab.Category == MapEntityCategory.Thalamus || entityPrefab.Tags.Contains(tag);
|
||||
|
||||
public WreckAI(Submarine wreck)
|
||||
{
|
||||
Wreck = wreck;
|
||||
Config = WreckAIConfig.GetRandom();
|
||||
if (Config == null)
|
||||
{
|
||||
DebugConsole.ThrowError("WreckAI: No wreck AI config found!");
|
||||
return;
|
||||
}
|
||||
var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => IsThalamus(p));
|
||||
var brainPrefab = thalamusPrefabs.GetRandom(i => i.Tags.Contains(Config.Brain), Rand.RandSync.Server);
|
||||
if (brainPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"WreckAI: Could not find any brain prefab with the tag {Config.Brain}! Cannot continue. Failed to create wreck AI.");
|
||||
return;
|
||||
}
|
||||
allItems = Wreck.GetItems(false);
|
||||
thalamusItems = allItems.FindAll(i => IsThalamus(i.prefab));
|
||||
var hulls = Wreck.GetHulls(false);
|
||||
brain = new Item(brainPrefab, Vector2.Zero, Wreck);
|
||||
thalamusItems.Add(brain);
|
||||
Vector2 negativeMargin = new Vector2(40, 20);
|
||||
Vector2 minSize = brain.Rect.Size.ToVector2() - negativeMargin;
|
||||
Vector2 maxSize = new Vector2(brain.Rect.Width * 3, brain.Rect.Height * 3);
|
||||
// First try to get a room that is not too big and not in the edges of the sub.
|
||||
// Also try not to create the brain in a room that already have carrier items inside.
|
||||
// Ignore hulls that have any linked hulls to keep the calculations simple.
|
||||
// Shrink the horizontal axis so that the brain is not placed in the left or right side, where we often have curved walls.
|
||||
// Also ignore hulls that have open gaps, because we'll want the room to be full of water. The room will be filled with water when the brain is inserted in the room.
|
||||
Rectangle shrinkedBounds = ToolBox.GetWorldBounds(Wreck.WorldPosition.ToPoint(), new Point(Wreck.Borders.Width - 500, Wreck.Borders.Height));
|
||||
bool BaseCondition(Hull h) => h.RectWidth > minSize.X && h.RectHeight > minSize.Y && h.GetLinkedEntities<Hull>().None() && h.ConnectedGaps.None(g => g.Open > 0);
|
||||
bool IsNotTooBig(Hull h) => h.RectWidth < maxSize.X && h.RectHeight < maxSize.Y;
|
||||
bool IsNotInFringes(Hull h) => shrinkedBounds.ContainsWorld(h.WorldRect);
|
||||
bool DoesNotContainOtherItems(Hull h) => thalamusItems.None(i => i.CurrentHull == h);
|
||||
Hull brainHull = hulls.GetRandom(h => BaseCondition(h) && IsNotTooBig(h) && IsNotInFringes(h) && DoesNotContainOtherItems(h), Rand.RandSync.Server);
|
||||
if (brainHull == null)
|
||||
{
|
||||
brainHull = hulls.GetRandom(h => BaseCondition(h) && IsNotInFringes(h) && DoesNotContainOtherItems(h), Rand.RandSync.Server);
|
||||
}
|
||||
if (brainHull == null)
|
||||
{
|
||||
brainHull = hulls.GetRandom(h => BaseCondition(h) && (IsNotInFringes(h) || DoesNotContainOtherItems(h)), Rand.RandSync.Server);
|
||||
}
|
||||
if (brainHull == null)
|
||||
{
|
||||
brainHull = hulls.GetRandom(BaseCondition, Rand.RandSync.Server);
|
||||
}
|
||||
var thalamusStructurePrefabs = StructurePrefab.Prefabs.Where(p => IsThalamus(p));
|
||||
if (brainHull == null) { return; }
|
||||
brainHull.WaterVolume = brainHull.Volume;
|
||||
brain.SetTransform(brainHull.SimPosition, rotation: 0, findNewHull: false);
|
||||
brain.CurrentHull = brainHull;
|
||||
var backgroundPrefab = thalamusStructurePrefabs.GetRandom(i => i.Tags.Contains(Config.BrainRoomBackground), Rand.RandSync.Server);
|
||||
if (backgroundPrefab != null)
|
||||
{
|
||||
new Structure(brainHull.Rect, backgroundPrefab, Wreck);
|
||||
}
|
||||
var horizontalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomHorizontalWall), Rand.RandSync.Server);
|
||||
if (horizontalWallPrefab != null)
|
||||
{
|
||||
int height = (int)horizontalWallPrefab.Size.Y;
|
||||
int halfHeight = height / 2;
|
||||
int quarterHeight = halfHeight / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, Wreck);
|
||||
}
|
||||
var verticalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomVerticalWall), Rand.RandSync.Server);
|
||||
if (verticalWallPrefab != null)
|
||||
{
|
||||
int width = (int)verticalWallPrefab.Size.X;
|
||||
int halfWidth = width / 2;
|
||||
int quarterWidth = halfWidth / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, Wreck);
|
||||
}
|
||||
foreach (Item item in allItems)
|
||||
{
|
||||
if (thalamusItems.Contains(item))
|
||||
{
|
||||
// Ensure that thalamus items are visible
|
||||
item.HiddenInGame = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load regular turrets
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
foreach (var linkedItem in item.GetLinkedEntities<Item>())
|
||||
{
|
||||
var container = linkedItem.GetComponent<ItemContainer>();
|
||||
if (container == null) { continue; }
|
||||
for (int i = 0; i < container.Inventory.Capacity; i++)
|
||||
{
|
||||
if (container.Inventory.Items[i] != null) { continue; }
|
||||
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab i && container.CanBeContained(i) &&
|
||||
Config.ForbiddenAmmunition.None(id => id.Equals(i.Identifier, StringComparison.OrdinalIgnoreCase)), Rand.RandSync.Server) is ItemPrefab ammoPrefab)
|
||||
{
|
||||
Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck);
|
||||
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
turrets.Add(turret);
|
||||
}
|
||||
if (item.HasTag(Config.Spawner))
|
||||
{
|
||||
if (!spawnOrgans.Contains(item))
|
||||
{
|
||||
spawnOrgans.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
wayPoints.AddRange(Wreck.GetWaypoints(false));
|
||||
hulls.AddRange(Wreck.GetHulls(false));
|
||||
IsAlive = true;
|
||||
thalamusStructures = GetThalamusEntities<Structure>(Wreck, Config.Entity).ToList();
|
||||
}
|
||||
|
||||
private readonly List<Item> destroyedOrgans = new List<Item>();
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (!IsAlive) { return; }
|
||||
if (Wreck == null || Wreck.Removed)
|
||||
{
|
||||
Remove();
|
||||
return;
|
||||
}
|
||||
if (brain == null || brain.Removed || brain.Condition <= 0)
|
||||
{
|
||||
Kill();
|
||||
return;
|
||||
}
|
||||
destroyedOrgans.Clear();
|
||||
foreach (var organ in spawnOrgans)
|
||||
{
|
||||
if (organ.Condition <= 0)
|
||||
{
|
||||
destroyedOrgans.Add(organ);
|
||||
}
|
||||
}
|
||||
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
|
||||
bool someoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 2.0f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c != Character.Controlled && !c.IsRemotePlayer) { continue; }
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
OperateTurrets(deltaTime);
|
||||
if (!IsClient)
|
||||
{
|
||||
if (!initialCellsSpawned) { SpawnInitialCells(); }
|
||||
UpdateReinforcements(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnInitialCells()
|
||||
{
|
||||
int brainRoomCells = Rand.Range(MinCellsPerBrainRoom, MaxCellsPerRoom);
|
||||
if (brain.CurrentHull?.WaterPercentage >= MinWaterLevel)
|
||||
{
|
||||
for (int i = 0; i < brainRoomCells; i++)
|
||||
{
|
||||
if (!TrySpawnCell(out _, brain.CurrentHull)) { break; }
|
||||
}
|
||||
}
|
||||
int cellsInside = Rand.Range(MinCellsInside, MaxCellsInside);
|
||||
for (int i = 0; i < cellsInside; i++)
|
||||
{
|
||||
if (!TrySpawnCell(out _)) { break; }
|
||||
}
|
||||
int cellsOutside = Rand.Range(MinCellsOutside, MaxCellsOutside);
|
||||
// If we failed to spawn some of the cells in the brainroom/inside, spawn some extra cells outside.
|
||||
cellsOutside = Math.Clamp(cellsOutside + brainRoomCells + cellsInside - protectiveCells.Count, cellsOutside, MaxCellsOutside);
|
||||
for (int i = 0; i < cellsOutside; i++)
|
||||
{
|
||||
ISpatialEntity targetEntity = wayPoints.GetRandom(wp => wp.CurrentHull == null);
|
||||
if (targetEntity == null) { break; }
|
||||
if (!TrySpawnCell(out _, targetEntity)) { break; }
|
||||
}
|
||||
initialCellsSpawned = true;
|
||||
}
|
||||
|
||||
private void Kill()
|
||||
{
|
||||
thalamusItems.ForEach(i => i.Condition = 0);
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
// Snap all tendons
|
||||
foreach (Item item in turret.ActiveProjectiles)
|
||||
{
|
||||
if (item.GetComponent<Projectile>()?.IsStuckToTarget ?? false)
|
||||
{
|
||||
item.Condition = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
FadeOutColors();
|
||||
protectiveCells.ForEach(c => c.OnDeath -= OnCellDeath);
|
||||
if (!IsClient)
|
||||
{
|
||||
if (Config != null)
|
||||
{
|
||||
if (Config.KillAgentsWhenEntityDies)
|
||||
{
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null, isNetworkMessage: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
protectiveCells.Clear();
|
||||
IsAlive = false;
|
||||
}
|
||||
|
||||
partial void FadeOutColors();
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Kill();
|
||||
RemoveThalamusItems(Wreck);
|
||||
thalamusItems?.Clear();
|
||||
thalamusStructures?.Clear();
|
||||
}
|
||||
|
||||
public static void RemoveThalamusItems(Submarine wreck)
|
||||
{
|
||||
foreach (var wreckAiConfig in WreckAIConfig.List)
|
||||
{
|
||||
GetThalamusEntities(wreck, wreckAiConfig.Entity).ForEachMod(e => e.Remove());
|
||||
}
|
||||
}
|
||||
|
||||
// The client doesn't use these, so we don't have to sync them.
|
||||
private readonly List<Character> protectiveCells = new List<Character>();
|
||||
// Intentionally contains duplicates.
|
||||
private readonly List<Hull> populatedHulls = new List<Hull>();
|
||||
private float cellSpawnTimer;
|
||||
|
||||
private float CellSpawnTime => Config.AgentSpawnDelay;
|
||||
private float CellSpawnRandomFactor => Config.AgentSpawnDelayRandomFactor;
|
||||
private int MinCellsPerBrainRoom => Config.MinAgentsPerBrainRoom;
|
||||
private int MaxCellsPerRoom => Config.MaxAgentsPerRoom;
|
||||
private int MinCellsOutside => Config.MinAgentsOutside;
|
||||
private int MaxCellsOutside => Config.MaxAgentsOutside;
|
||||
private int MinCellsInside => Config.MinAgentsInside;
|
||||
private int MaxCellsInside => Config.MaxAgentsInside;
|
||||
private int MaxCellCount => Config.MaxAgentCount;
|
||||
private float MinWaterLevel => Config.MinWaterLevel;
|
||||
|
||||
void UpdateReinforcements(float deltaTime)
|
||||
{
|
||||
if (protectiveCells.Count >= MaxCellCount || spawnOrgans.Count == 0) { return; }
|
||||
cellSpawnTimer -= deltaTime;
|
||||
if (cellSpawnTimer < 0)
|
||||
{
|
||||
TrySpawnCell(out _, spawnOrgans.GetRandom());
|
||||
cellSpawnTimer = CellSpawnTime * Rand.Range(CellSpawnRandomFactor, 1 + CellSpawnRandomFactor);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrySpawnCell(out Character cell, ISpatialEntity targetEntity = null)
|
||||
{
|
||||
cell = null;
|
||||
if (protectiveCells.Count >= MaxCellCount) { return false; }
|
||||
if (targetEntity == null)
|
||||
{
|
||||
targetEntity =
|
||||
wayPoints.GetRandom(wp => wp.CurrentHull != null && populatedHulls.Count(h => h == wp.CurrentHull) < MaxCellsPerRoom && wp.CurrentHull.WaterPercentage >= MinWaterLevel) ??
|
||||
hulls.GetRandom(h => populatedHulls.Count(h2 => h2 == h) < MaxCellsPerRoom && h.WaterPercentage >= MinWaterLevel) as ISpatialEntity;
|
||||
}
|
||||
if (targetEntity == null) { return false; }
|
||||
if (targetEntity is Hull h)
|
||||
{
|
||||
populatedHulls.Add(h);
|
||||
}
|
||||
else if (targetEntity is WayPoint wp && wp.CurrentHull != null)
|
||||
{
|
||||
populatedHulls.Add(wp.CurrentHull);
|
||||
}
|
||||
// Don't add items in the list, because we want to be able to ignore the restrictions for spawner organs.
|
||||
cell = Character.Create(Config.DefensiveAgent, targetEntity.WorldPosition, ToolBox.RandomSeed(8), hasAi: true, createNetworkEvent: true);
|
||||
protectiveCells.Add(cell);
|
||||
cell.OnDeath += OnCellDeath;
|
||||
cellSpawnTimer = CellSpawnTime * Rand.Range(CellSpawnRandomFactor, 1 + CellSpawnRandomFactor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OperateTurrets(float deltaTime)
|
||||
{
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
// Never target other creatures than humans with the turrets.
|
||||
turret.ThalamusOperate(this, deltaTime,
|
||||
!turret.Item.HasTag("ignorecharacters"),
|
||||
targetOtherCreatures: false,
|
||||
!turret.Item.HasTag("ignoresubmarines"),
|
||||
turret.Item.HasTag("ignoreaimdelay"));
|
||||
}
|
||||
}
|
||||
|
||||
void OnCellDeath(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
protectiveCells.Remove(character);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public void ServerWrite(IWriteMessage msg, Client client, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsAlive);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WreckAIConfig : ISerializableEntity
|
||||
{
|
||||
public string Name => "Wreck AI Config";
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Entity { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string DefensiveAgent { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Brain { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Spawner { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string BrainRoomBackground { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string BrainRoomVerticalWall { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string BrainRoomHorizontalWall { get; private set; }
|
||||
|
||||
[Serialize(60f, false)]
|
||||
public float AgentSpawnDelay { get; private set; }
|
||||
|
||||
[Serialize(0.5f, false)]
|
||||
public float AgentSpawnDelayRandomFactor { get; private set; }
|
||||
|
||||
[Serialize(0, false)]
|
||||
public int MinAgentsPerBrainRoom { get; private set; }
|
||||
|
||||
[Serialize(3, false)]
|
||||
public int MaxAgentsPerRoom { get; private set; }
|
||||
|
||||
[Serialize(2, false)]
|
||||
public int MinAgentsOutside { get; private set; }
|
||||
|
||||
[Serialize(5, false)]
|
||||
public int MaxAgentsOutside { get; private set; }
|
||||
|
||||
[Serialize(3, false)]
|
||||
public int MinAgentsInside { get; private set; }
|
||||
|
||||
[Serialize(10, false)]
|
||||
public int MaxAgentsInside { get; private set; }
|
||||
|
||||
[Serialize(15, false)]
|
||||
public int MaxAgentCount { get; private set; }
|
||||
|
||||
[Serialize(100f, false)]
|
||||
public float MinWaterLevel { get; private set; }
|
||||
|
||||
[Serialize(true, false)]
|
||||
public bool KillAgentsWhenEntityDies { get; private set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float DeadEntityColorMultiplier { get; private set; }
|
||||
|
||||
[Serialize(1f, false)]
|
||||
public float DeadEntityColorFadeOutTime { get; private set; }
|
||||
|
||||
public readonly string[] ForbiddenAmmunition;
|
||||
|
||||
public static List<WreckAIConfig> List
|
||||
{
|
||||
get
|
||||
{
|
||||
if (paramsList == null)
|
||||
{
|
||||
LoadAll();
|
||||
}
|
||||
return paramsList;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<WreckAIConfig> paramsList;
|
||||
|
||||
public static WreckAIConfig GetRandom() => List.GetRandom(Rand.RandSync.Server);
|
||||
|
||||
public WreckAIConfig(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
ForbiddenAmmunition = XMLExtensions.GetAttributeStringArray(element, "ForbiddenAmmunition", new string[0], convertToLowerInvariant: true);
|
||||
}
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
paramsList = new List<WreckAIConfig>();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.WreckAIConfig);
|
||||
if (files.None())
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot find any Wreck AI config!");
|
||||
return;
|
||||
}
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
var mainElement = doc.Root;
|
||||
if (mainElement.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
paramsList.Clear();
|
||||
DebugConsole.NewMessage($"Overriding the wreck ai config with '{file.Path}'", Color.Yellow);
|
||||
}
|
||||
else if (paramsList.Any())
|
||||
{
|
||||
DebugConsole.NewMessage($"Adding additional wreck ai config from file '{file.Path}'");
|
||||
}
|
||||
paramsList.Add(new WreckAIConfig(mainElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,12 @@ namespace Barotrauma
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
if (!Enabled) return;
|
||||
if (!Enabled) { return; }
|
||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated) { return; }
|
||||
|
||||
//don't enable simple physics on dead/incapacitated characters
|
||||
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
||||
//but in simple physics mode the ragdoll would get disabled, causing the character to not move at all
|
||||
if (!IsRemotePlayer)
|
||||
{
|
||||
float characterDist = float.MaxValue;
|
||||
@@ -70,10 +74,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDead || Vitality <= 0.0f || IsUnconscious || Stun > 0.0f) return;
|
||||
if (!aiController.Enabled) return;
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) return;
|
||||
if (Controlled == this) return;
|
||||
if (!aiController.Enabled) { return; }
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
if (Controlled == this) { return; }
|
||||
|
||||
if (!IsRemotePlayer)
|
||||
{
|
||||
|
||||
+18
-9
@@ -441,7 +441,7 @@ namespace Barotrauma
|
||||
for (int i = -1; i < 2; i += 2)
|
||||
{
|
||||
Vector2 footPos = GetColliderBottom();
|
||||
footPos = new Vector2(waist.SimPosition.X + Math.Sign(StepSize.Value.X * i) * Dir * 0.3f, footPos.Y - 0.1f * RagdollParams.JointScale);
|
||||
footPos = new Vector2(waist.SimPosition.X + Math.Sign(WalkParams.StepSize.X * i) * Dir * 0.3f, footPos.Y - 0.1f * RagdollParams.JointScale);
|
||||
var foot = i == -1 ? rightFoot : leftFoot;
|
||||
MoveLimb(foot, footPos, Math.Abs(foot.SimPosition.X - footPos.X) * 100.0f, true);
|
||||
}
|
||||
@@ -481,7 +481,7 @@ namespace Barotrauma
|
||||
|
||||
swimmingStateLockTimer -= deltaTime;
|
||||
|
||||
if (forceStanding)
|
||||
if (forceStanding || character.AnimController.AnimationTestPose)
|
||||
{
|
||||
swimming = false;
|
||||
}
|
||||
@@ -1410,7 +1410,7 @@ namespace Barotrauma
|
||||
SteamAchievementManager.OnCharacterRevived(target, character);
|
||||
lastReviveTime = (float)Timing.TotalTime;
|
||||
#if SERVER
|
||||
GameMain.Server?.KarmaManager?.OnCharacterHealthChanged(target, character, damage: Math.Min(prevVitality - target.Vitality, 0.0f));
|
||||
GameMain.Server?.KarmaManager?.OnCharacterHealthChanged(target, character, damage: Math.Min(prevVitality - target.Vitality, 0.0f), stun: 0.0f);
|
||||
#endif
|
||||
//reset attacker, we don't want the character to start attacking us
|
||||
//because we caused a bit of damage to them during CPR
|
||||
@@ -1540,6 +1540,11 @@ namespace Barotrauma
|
||||
{
|
||||
sourceSimPos -= character.SelectedCharacter.Submarine.SimPosition;
|
||||
}
|
||||
else if (character.Submarine != null && character.SelectedCharacter.Submarine != null && character.Submarine != character.SelectedCharacter.Submarine)
|
||||
{
|
||||
targetSimPos += character.SelectedCharacter.Submarine.SimPosition;
|
||||
targetSimPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
var body = Submarine.CheckVisibility(sourceSimPos, targetSimPos, ignoreSubs: true);
|
||||
if (body != null)
|
||||
{
|
||||
@@ -1553,8 +1558,8 @@ namespace Barotrauma
|
||||
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(targetLimb.WorldPosition - pullLimb.WorldPosition);
|
||||
|
||||
Vector2 targetAnchor = targetLimb.SimPosition;
|
||||
float targetForce = 0.0f;
|
||||
Vector2 targetAnchor;
|
||||
float targetForce;
|
||||
pullLimb.PullJointEnabled = true;
|
||||
if (targetLimb.type == LimbType.Torso || targetLimb == target.AnimController.MainLimb)
|
||||
{
|
||||
@@ -1624,6 +1629,7 @@ namespace Barotrauma
|
||||
|
||||
if (!target.AllowInput)
|
||||
{
|
||||
target.AnimController.Stairs = Stairs;
|
||||
target.AnimController.IgnorePlatforms = IgnorePlatforms;
|
||||
target.AnimController.TargetMovement = TargetMovement;
|
||||
}
|
||||
@@ -1652,7 +1658,10 @@ namespace Barotrauma
|
||||
//TODO: refactor this method, it's way too convoluted
|
||||
public override void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle, float itemAngleRelativeToHoldAngle = 0.0f)
|
||||
{
|
||||
if (character.IsUnconscious || character.Stun > 0.0f) aim = false;
|
||||
if (character.Stun > 0.0f || character.IsIncapacitated)
|
||||
{
|
||||
aim = false;
|
||||
}
|
||||
|
||||
//calculate the handle positions
|
||||
Matrix itemTransfrom = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
@@ -1676,7 +1685,7 @@ namespace Barotrauma
|
||||
|
||||
Holdable holdable = item.GetComponent<Holdable>();
|
||||
|
||||
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero)
|
||||
if (!isClimbing && !usingController && character.Stun <= 0.0f && aim && itemPos != Vector2.Zero && !character.IsIncapacitated)
|
||||
{
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.SmoothedCursorPosition);
|
||||
|
||||
@@ -1763,7 +1772,7 @@ namespace Barotrauma
|
||||
|
||||
if (holdable.Pusher != null)
|
||||
{
|
||||
if (character.IsUnconscious || character.Stun > 0.0f)
|
||||
if (character.Stun > 0.0f || character.IsIncapacitated)
|
||||
{
|
||||
holdable.Pusher.Enabled = false;
|
||||
}
|
||||
@@ -1778,7 +1787,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
holdable.Pusher.TargetPosition = currItemPos;
|
||||
holdable.Pusher.TargetRotation = character.IsUnconscious || character.Stun > 0.0f ? itemAngle : holdAngle * Dir;
|
||||
holdable.Pusher.TargetRotation = holdAngle * Dir;
|
||||
|
||||
holdable.Pusher.MoveToTargetPosition(true);
|
||||
|
||||
|
||||
@@ -55,7 +55,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (limbs == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this));
|
||||
string errorMsg = "Attempted to access a potentially removed ragdoll. Character: " + character.Name + ", id: " + character.ID + ", removed: " + character.Removed + ", ragdoll removed: " + !list.Contains(this);
|
||||
#if DEBUG || UNSTABLE
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
#endif
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Ragdoll.Limbs:AccessRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
@@ -80,7 +84,7 @@ namespace Barotrauma
|
||||
frozen = value;
|
||||
|
||||
Collider.FarseerBody.LinearDamping = frozen ? (1.5f / (float)Timing.Step) : 0.0f;
|
||||
Collider.FarseerBody.AngularDamping = frozen ? (1.5f / (float)Timing.Step) : 0.0f;
|
||||
Collider.FarseerBody.AngularDamping = frozen ? (1.5f / (float)Timing.Step) : PhysicsBody.DefaultAngularDamping;
|
||||
Collider.FarseerBody.IgnoreGravity = frozen;
|
||||
|
||||
//Collider.PhysEnabled = !frozen;
|
||||
@@ -97,8 +101,6 @@ namespace Barotrauma
|
||||
|
||||
protected float strongestImpact;
|
||||
|
||||
protected double onFloorTimer;
|
||||
|
||||
private float splashSoundTimer;
|
||||
|
||||
//the movement speed of the ragdoll
|
||||
@@ -635,10 +637,7 @@ namespace Barotrauma
|
||||
Vector2 colliderBottom = GetColliderBottom();
|
||||
if (structure.IsPlatform)
|
||||
{
|
||||
if (IgnorePlatforms) { return false; }
|
||||
|
||||
//the collision is ignored if the lowest limb is under the platform
|
||||
//if (lowestLimb==null || lowestLimb.Position.Y < structure.Rect.Y) return false;
|
||||
if (IgnorePlatforms || currentHull == null) { return false; }
|
||||
|
||||
if (colliderBottom.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) { return false; }
|
||||
if (f1.Body.Position.Y < ConvertUnits.ToSimUnits(structure.Rect.Y - 5)) { return false; }
|
||||
@@ -732,14 +731,20 @@ namespace Barotrauma
|
||||
limbJoint.IsSevered = true;
|
||||
limbJoint.Enabled = false;
|
||||
|
||||
Vector2 limbDiff = limbJoint.LimbA.SimPosition - limbJoint.LimbB.SimPosition;
|
||||
if (limbDiff.LengthSquared() < 0.0001f) { limbDiff = Rand.Vector(1.0f); }
|
||||
limbDiff = Vector2.Normalize(limbDiff);
|
||||
float mass = limbJoint.BodyA.Mass + limbJoint.BodyB.Mass;
|
||||
limbJoint.LimbA.body.ApplyLinearImpulse(limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
limbJoint.LimbB.body.ApplyLinearImpulse(-limbDiff * mass, (limbJoint.LimbA.SimPosition + limbJoint.LimbB.SimPosition) / 2.0f);
|
||||
|
||||
List<Limb> connectedLimbs = new List<Limb>();
|
||||
List<LimbJoint> checkedJoints = new List<LimbJoint>();
|
||||
|
||||
GetConnectedLimbs(connectedLimbs, checkedJoints, MainLimb);
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (connectedLimbs.Contains(limb)) continue;
|
||||
|
||||
if (connectedLimbs.Contains(limb)) { continue; }
|
||||
limb.IsSevered = true;
|
||||
}
|
||||
|
||||
@@ -962,8 +967,8 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
if (character.Position.X < gap.Rect.X || character.Position.X > gap.Rect.Right) continue;
|
||||
if (Math.Sign((gap.Rect.Y - gap.Rect.Height / 2) - (currentHull.Rect.Center.Y - currentHull.Rect.Height / 2)) !=
|
||||
Math.Sign(character.Position.Y - (currentHull.Rect.Center.Y - currentHull.Rect.Height / 2)))
|
||||
if (Math.Sign((gap.Rect.Y - gap.Rect.Height / 2) - (currentHull.Rect.Y - currentHull.Rect.Height / 2)) !=
|
||||
Math.Sign(character.Position.Y - (currentHull.Rect.Y - currentHull.Rect.Height / 2)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1224,6 +1229,8 @@ namespace Barotrauma
|
||||
|
||||
private void CheckBodyInRest(float deltaTime)
|
||||
{
|
||||
if (SimplePhysicsEnabled) { return; }
|
||||
|
||||
if (InWater || Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead)
|
||||
{
|
||||
bodyInRestTimer = 0.0f;
|
||||
@@ -1633,7 +1640,7 @@ namespace Barotrauma
|
||||
{
|
||||
float offset = 0.0f;
|
||||
|
||||
if (!character.IsUnconscious && !character.IsDead && character.Stun <= 0.0f)
|
||||
if (!character.IsDead && character.Stun <= 0.0f && !character.IsIncapacitated)
|
||||
{
|
||||
offset = -ColliderHeightFromFloor;
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ namespace Barotrauma
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float range = 0.0f)
|
||||
public Attack(float damage, float bleedingDamage, float burnDamage, float structureDamage, float itemDamage, float range = 0.0f)
|
||||
{
|
||||
if (damage > 0.0f) Afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damage), null);
|
||||
if (bleedingDamage > 0.0f) Afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamage), null);
|
||||
@@ -269,6 +269,7 @@ namespace Barotrauma
|
||||
Range = range;
|
||||
DamageRange = range;
|
||||
StructureDamage = structureDamage;
|
||||
ItemDamage = itemDamage;
|
||||
}
|
||||
|
||||
public Attack(XElement element, string parentDebugName)
|
||||
@@ -298,7 +299,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - define afflictions using identifiers instead of names.");
|
||||
string afflictionName = subElement.GetAttributeString("name", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.ToLowerInvariant() == afflictionName);
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Name.Equals(afflictionName, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionName + "\" not found.");
|
||||
@@ -308,7 +309,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Attack (" + parentDebugName + ") - Affliction prefab \"" + afflictionIdentifier + "\" not found.");
|
||||
@@ -343,7 +344,7 @@ namespace Barotrauma
|
||||
AfflictionPrefab afflictionPrefab;
|
||||
Affliction affliction;
|
||||
string afflictionIdentifier = subElement.GetAttributeString("identifier", "").ToLowerInvariant();
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.ToLowerInvariant() == afflictionIdentifier);
|
||||
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(ap => ap.Identifier.Equals(afflictionIdentifier, System.StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab != null)
|
||||
{
|
||||
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
|
||||
@@ -515,10 +516,10 @@ namespace Barotrauma
|
||||
public void SetCoolDown()
|
||||
{
|
||||
float randomFraction = CoolDown * CoolDownRandomFactor;
|
||||
CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
|
||||
CurrentRandomCoolDown = MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value());
|
||||
CoolDownTimer = CoolDown + CurrentRandomCoolDown;
|
||||
randomFraction = SecondaryCoolDown * CoolDownRandomFactor;
|
||||
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value(Rand.RandSync.Server));
|
||||
SecondaryCoolDownTimer = SecondaryCoolDown + MathHelper.Lerp(-randomFraction, randomFraction, Rand.Value());
|
||||
}
|
||||
|
||||
public void ResetCoolDown()
|
||||
|
||||
@@ -126,6 +126,14 @@ namespace Barotrauma
|
||||
set => Params.NeedsAir = value;
|
||||
}
|
||||
|
||||
public bool NeedsWater
|
||||
{
|
||||
get => Params.NeedsWater;
|
||||
set => Params.NeedsWater = value;
|
||||
}
|
||||
|
||||
public bool NeedsOxygen => NeedsAir || NeedsWater && !AnimController.InWater;
|
||||
|
||||
public float Noise
|
||||
{
|
||||
get => Params.Noise;
|
||||
@@ -144,7 +152,28 @@ namespace Barotrauma
|
||||
|
||||
private float attackCoolDown;
|
||||
|
||||
public Order CurrentOrder { get; private set; }
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return Info?.CurrentOrder;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (Info != null) { Info.CurrentOrder = value; }
|
||||
}
|
||||
}
|
||||
public string CurrentOrderOption
|
||||
{
|
||||
get
|
||||
{
|
||||
return Info?.CurrentOrderOption;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (Info != null) { Info.CurrentOrderOption = value; }
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<StatusEffect> statusEffects = new List<StatusEffect>();
|
||||
private readonly List<float> speedMultipliers = new List<float>();
|
||||
@@ -170,7 +199,7 @@ namespace Barotrauma
|
||||
if (turret != null)
|
||||
{
|
||||
viewTargetWorldPos = new Vector2(
|
||||
targetItem.WorldRect.X + turret.TransformedBarrelPos.X,
|
||||
targetItem.WorldRect.X + turret.TransformedBarrelPos.X,
|
||||
targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
}
|
||||
@@ -255,7 +284,7 @@ namespace Barotrauma
|
||||
//text displayed when the character is highlighted if custom interact is set
|
||||
public string customInteractHUDText;
|
||||
private Action<Character, Character> onCustomInteract;
|
||||
|
||||
|
||||
private float lockHandsTimer;
|
||||
public bool LockHands
|
||||
{
|
||||
@@ -271,22 +300,22 @@ namespace Barotrauma
|
||||
|
||||
public bool AllowInput
|
||||
{
|
||||
get { return !IsUnconscious && Stun <= 0.0f && !IsDead; }
|
||||
get { return Stun <= 0.0f && !IsDead && !IsIncapacitated; }
|
||||
}
|
||||
|
||||
public bool CanMove
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!AllowInput) { return false; }
|
||||
if (!AnimController.InWater && !AnimController.CanWalk) { return false; }
|
||||
if (!AllowInput) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanInteract
|
||||
{
|
||||
get { return AllowInput && IsHumanoid && !LockHands && !Removed; }
|
||||
get { return AllowInput && IsHumanoid && !LockHands && !Removed && !IsIncapacitated; }
|
||||
}
|
||||
|
||||
public Vector2 CursorPosition
|
||||
@@ -372,12 +401,21 @@ namespace Barotrauma
|
||||
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float ragdollingLockTimer;
|
||||
public bool IsRagdolled;
|
||||
public bool IsForceRagdolled;
|
||||
public bool dontFollowCursor;
|
||||
|
||||
public bool IsIncapacitated
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsUnconscious) { return true; }
|
||||
return CharacterHealth.Afflictions.Any(a => a.Prefab.AfflictionType == "paralysis" && a.Strength >= a.Prefab.MaxStrength);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsUnconscious
|
||||
{
|
||||
get { return CharacterHealth.IsUnconscious; }
|
||||
@@ -501,6 +539,8 @@ namespace Barotrauma
|
||||
|
||||
public bool IsDead { get; private set; }
|
||||
|
||||
public bool EnableDespawn { get; set; } = true;
|
||||
|
||||
public CauseOfDeath CauseOfDeath
|
||||
{
|
||||
get;
|
||||
@@ -523,7 +563,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!canBeDragged) { return false; }
|
||||
if (Removed || !AnimController.Draggable) { return false; }
|
||||
return IsDead || Stun > 0.0f || LockHands || IsUnconscious;
|
||||
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated;
|
||||
}
|
||||
set { canBeDragged = value; }
|
||||
}
|
||||
@@ -541,7 +581,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return (IsDead || Stun > 0.0f || LockHands || IsUnconscious);
|
||||
return (IsDead || Stun > 0.0f || LockHands || IsIncapacitated);
|
||||
}
|
||||
}
|
||||
set { canInventoryBeAccessed = value; }
|
||||
@@ -562,7 +602,9 @@ namespace Barotrauma
|
||||
{
|
||||
errorMsg += " AnimController.Collider == null";
|
||||
}
|
||||
|
||||
#if DEBUG || UNSTABLE
|
||||
errorMsg += '\n' + Environment.StackTrace;
|
||||
#endif
|
||||
DebugConsole.NewMessage(errorMsg, Color.Red);
|
||||
GameAnalyticsManager.AddErrorEventOnce(
|
||||
"Character.SimPosition:AccessRemoved",
|
||||
@@ -768,7 +810,7 @@ namespace Barotrauma
|
||||
var matchingAffliction = AfflictionPrefab.List
|
||||
.Where(p => p.AfflictionType == "huskinfection")
|
||||
.Select(p => p as AfflictionPrefabHusk)
|
||||
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.InvariantCultureIgnoreCase)));
|
||||
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase)));
|
||||
string nonHuskedSpeciesName = string.Empty;
|
||||
if (matchingAffliction == null)
|
||||
{
|
||||
@@ -1212,7 +1254,7 @@ namespace Barotrauma
|
||||
{
|
||||
attackCoolDown -= deltaTime;
|
||||
}
|
||||
else if (IsKeyDown(InputType.Attack))
|
||||
else if (IsKeyDown(InputType.Attack) && (IsRemotePlayer || Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)))
|
||||
{
|
||||
var currentContexts = GetAttackContexts();
|
||||
var validLimbs = AnimController.Limbs.Where(l => !l.IsSevered && !l.IsStuck && l.attack != null && l.attack.IsValidContext(currentContexts));
|
||||
@@ -1522,7 +1564,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Finds the closest item seeking by identifiers or tags from the world.
|
||||
/// Ignores items that are outside or in another team's submarine or in a submarine that is not connected to this submarine.
|
||||
/// Also ignores items that are taken by someone else.
|
||||
/// Also ignores non-interactable items and items that are taken by someone else.
|
||||
/// The method is run in steps for performance reasons. So you'll have to provide the reference to the itemIndex.
|
||||
/// Returns false while running and true when done.
|
||||
/// </summary>
|
||||
@@ -1539,12 +1581,17 @@ namespace Barotrauma
|
||||
{
|
||||
itemIndex++;
|
||||
var item = Item.ItemList[itemIndex];
|
||||
if (item.NonInteractable) { continue; }
|
||||
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
||||
if (item.Submarine == null) { continue; }
|
||||
if (item.Submarine.TeamID != TeamID) { continue; }
|
||||
if (Submarine != null && !Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
if (item.CurrentHull == null) { continue; }
|
||||
if (ignoreBroken && item.Condition <= 0) { continue; }
|
||||
if (Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != Submarine.Info.Type) { continue; }
|
||||
if (!Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
}
|
||||
if (customPredicate != null && !customPredicate(item)) { continue; }
|
||||
if (identifiers != null && identifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
@@ -1785,13 +1832,17 @@ namespace Barotrauma
|
||||
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
|
||||
{
|
||||
FocusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null;
|
||||
if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; }
|
||||
float aimAssist = GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
|
||||
if (SelectedItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
|
||||
{
|
||||
//disable aim assist when rewiring to make it harder to accidentally select items when adding wire nodes
|
||||
aimAssist = 0.0f;
|
||||
}
|
||||
focusedItem = CanInteract ? FindItemAtPosition(mouseSimPos, aimAssist) : null;
|
||||
|
||||
var item = FindItemAtPosition(mouseSimPos, aimAssist);
|
||||
|
||||
focusedItem = CanInteract ? item : null;
|
||||
findFocusedTimer = 0.05f;
|
||||
}
|
||||
}
|
||||
@@ -1925,7 +1976,7 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
//disable AI characters that are far away from all clients and the host's character and not controlled by anyone
|
||||
if (c.IsPlayer || c.IsBot)
|
||||
if (c.IsPlayer || (c.IsBot && !c.IsDead))
|
||||
{
|
||||
c.Enabled = true;
|
||||
}
|
||||
@@ -2094,12 +2145,17 @@ namespace Barotrauma
|
||||
UpdateControlled(deltaTime, cam);
|
||||
|
||||
//Health effects
|
||||
if (NeedsAir) { UpdateOxygen(deltaTime); }
|
||||
if (NeedsOxygen)
|
||||
{
|
||||
UpdateOxygen(deltaTime);
|
||||
}
|
||||
CharacterHealth.Update(deltaTime);
|
||||
|
||||
if (IsUnconscious)
|
||||
if (IsIncapacitated)
|
||||
{
|
||||
UpdateUnconscious();
|
||||
Stun = Math.Max(5.0f, Stun);
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedConstruction = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2138,11 +2194,12 @@ namespace Barotrauma
|
||||
lowPassMultiplier = MathHelper.Lerp(lowPassMultiplier, 1.0f, 0.1f);
|
||||
|
||||
//ragdoll button
|
||||
if (IsRagdolled)
|
||||
if (IsRagdolled || !CanMove)
|
||||
{
|
||||
if (AnimController is HumanoidAnimController) ((HumanoidAnimController)AnimController).Crouching = false;
|
||||
/*if(GameMain.Server != null)
|
||||
GameMain.Server.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });*/
|
||||
if (AnimController is HumanoidAnimController)
|
||||
{
|
||||
((HumanoidAnimController)AnimController).Crouching = false;
|
||||
}
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedConstruction = null;
|
||||
return;
|
||||
@@ -2165,40 +2222,51 @@ namespace Barotrauma
|
||||
SelectedConstruction = null;
|
||||
}
|
||||
|
||||
if (!IsDead) LockHands = false;
|
||||
if (!IsDead) { LockHands = false; }
|
||||
}
|
||||
|
||||
partial void UpdateControlled(float deltaTime, Camera cam);
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime, Camera cam);
|
||||
|
||||
partial void SetOrderProjSpecific(Order order, string orderOption);
|
||||
|
||||
private void UpdateOxygen(float deltaTime)
|
||||
{
|
||||
PressureProtection -= deltaTime * 100.0f;
|
||||
float hullAvailableOxygen = 0.0f;
|
||||
if (!AnimController.HeadInWater && AnimController.CurrentHull != null)
|
||||
if (NeedsAir)
|
||||
{
|
||||
//don't decrease the amount of oxygen in the hull if the character has more oxygen available than the hull
|
||||
//(i.e. if the character has some external source of oxygen)
|
||||
if (OxygenAvailable * 0.98f < AnimController.CurrentHull.OxygenPercentage)
|
||||
PressureProtection -= deltaTime * 100.0f;
|
||||
}
|
||||
if (NeedsWater)
|
||||
{
|
||||
float waterAvailable = 100;
|
||||
if (!AnimController.InWater && CurrentHull != null)
|
||||
{
|
||||
AnimController.CurrentHull.Oxygen -= Hull.OxygenConsumptionSpeed * deltaTime;
|
||||
waterAvailable = CurrentHull.WaterPercentage;
|
||||
}
|
||||
hullAvailableOxygen = AnimController.CurrentHull.OxygenPercentage;
|
||||
OxygenAvailable += MathHelper.Clamp(waterAvailable - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
float hullAvailableOxygen = 0.0f;
|
||||
if (!AnimController.HeadInWater && AnimController.CurrentHull != null)
|
||||
{
|
||||
//don't decrease the amount of oxygen in the hull if the character has more oxygen available than the hull
|
||||
//(i.e. if the character has some external source of oxygen)
|
||||
if (OxygenAvailable * 0.98f < AnimController.CurrentHull.OxygenPercentage)
|
||||
{
|
||||
AnimController.CurrentHull.Oxygen -= Hull.OxygenConsumptionSpeed * deltaTime;
|
||||
}
|
||||
hullAvailableOxygen = AnimController.CurrentHull.OxygenPercentage;
|
||||
|
||||
}
|
||||
OxygenAvailable += MathHelper.Clamp(hullAvailableOxygen - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
|
||||
}
|
||||
|
||||
OxygenAvailable += MathHelper.Clamp(hullAvailableOxygen - oxygenAvailable, -deltaTime * 50.0f, deltaTime * 50.0f);
|
||||
}
|
||||
|
||||
partial void UpdateOxygenProjSpecific(float prevOxygen);
|
||||
|
||||
private void UpdateUnconscious()
|
||||
{
|
||||
Stun = Math.Max(5.0f, Stun);
|
||||
|
||||
AnimController.ResetPullJoints();
|
||||
SelectedConstruction = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How far the character is from the closest human player (including spectators)
|
||||
/// </summary>
|
||||
@@ -2231,23 +2299,44 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private float despawnTimer;
|
||||
private const float DespawnDelay = 5.0f * 60.0f; //5 minutes
|
||||
private void UpdateDespawn(float deltaTime)
|
||||
{
|
||||
if (!EnableDespawn) { return; }
|
||||
|
||||
//clients don't despawn characters unless the server says so
|
||||
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
|
||||
|
||||
if (!IsDead) { return; }
|
||||
|
||||
int subCorpseCount = 0;
|
||||
|
||||
if (Submarine != null)
|
||||
{
|
||||
subCorpseCount = CharacterList.Count(c => c.IsDead && c.Submarine == Submarine);
|
||||
if (subCorpseCount < GameMain.Config.CorpsesPerSubDespawnThreshold) { return; }
|
||||
}
|
||||
|
||||
float distToClosestPlayer = GetDistanceToClosestPlayer();
|
||||
if (distToClosestPlayer > NetConfig.DisableCharacterDist)
|
||||
{
|
||||
//despawn in 1 second if very far from all human players
|
||||
despawnTimer = Math.Max(despawnTimer, DespawnDelay - 1.0f);
|
||||
//despawn in 1 minute if very far from all human players
|
||||
despawnTimer = Math.Max(despawnTimer, GameMain.Config.CorpseDespawnDelay - 60.0f);
|
||||
}
|
||||
|
||||
despawnTimer += deltaTime;
|
||||
if (despawnTimer < DespawnDelay) { return; }
|
||||
float despawnPriority = 1.0f;
|
||||
if (subCorpseCount > GameMain.Config.CorpsesPerSubDespawnThreshold)
|
||||
{
|
||||
//despawn faster if there are lots of corpses in the sub (twice as many as the threshold -> despawn twice as fast)
|
||||
despawnPriority += (subCorpseCount - GameMain.Config.CorpsesPerSubDespawnThreshold) / (float)GameMain.Config.CorpsesPerSubDespawnThreshold;
|
||||
}
|
||||
if (AIController is EnemyAIController)
|
||||
{
|
||||
//enemies despawn faster
|
||||
despawnPriority *= 2.0f;
|
||||
}
|
||||
|
||||
despawnTimer += deltaTime * despawnPriority;
|
||||
if (despawnTimer < GameMain.Config.CorpseDespawnDelay) { return; }
|
||||
|
||||
if (IsHuman)
|
||||
{
|
||||
@@ -2274,7 +2363,12 @@ namespace Barotrauma
|
||||
if (itemContainer == null) { return; }
|
||||
foreach (Item inventoryItem in Inventory.Items)
|
||||
{
|
||||
itemContainer.Inventory.TryPutItem(inventoryItem, user: null);
|
||||
if (inventoryItem == null) { continue; }
|
||||
if (!itemContainer.Inventory.TryPutItem(inventoryItem, user: null))
|
||||
{
|
||||
//if the item couldn't be put inside the despawn container, just drop it
|
||||
inventoryItem.Drop(dropper: this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2284,7 +2378,7 @@ namespace Barotrauma
|
||||
|
||||
public void DespawnNow()
|
||||
{
|
||||
despawnTimer = DespawnDelay;
|
||||
despawnTimer = GameMain.Config.CorpseDespawnDelay;
|
||||
}
|
||||
|
||||
public static void RemoveByPrefab(CharacterPrefab prefab)
|
||||
@@ -2350,18 +2444,29 @@ namespace Barotrauma
|
||||
//set the character order only if the character is close enough to hear the message
|
||||
if (orderGiver != null && !CanHearCharacter(orderGiver)) { return; }
|
||||
|
||||
// If there's another character operating the same device, make them dismiss themself
|
||||
if (order != null && order.Category == OrderCategory.Operate)
|
||||
{
|
||||
CharacterList.FindAll(c => c != this && c.TeamID == TeamID && c.CurrentOrder is Order characterOrder && characterOrder.Category == OrderCategory.Operate &&
|
||||
characterOrder.Identifier.Equals(order.Identifier) && characterOrder.TargetEntity == order.TargetEntity)?
|
||||
.ForEach(c => c.SetOrder(Order.GetPrefab("dismissed"), null, c, speak: true));
|
||||
}
|
||||
|
||||
if (AIController is HumanAIController humanAI)
|
||||
{
|
||||
humanAI.SetOrder(order, orderOption, orderGiver, speak);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
|
||||
#endif
|
||||
|
||||
SetOrderProjSpecific(order, orderOption);
|
||||
CurrentOrder = order;
|
||||
CurrentOrderOption = orderOption;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset order data so it doesn't carry into further rounds, as the AI is "recreated" always in between rounds anyway.
|
||||
/// </summary>
|
||||
public void ResetCurrentOrder() => Info?.ResetCurrentOrder();
|
||||
|
||||
private readonly List<AIChatMessage> aiChatMessageQueue = new List<AIChatMessage>();
|
||||
|
||||
//key = identifier, value = time the message was sent
|
||||
@@ -2511,7 +2616,7 @@ namespace Barotrauma
|
||||
if (attacker is Character attackingCharacter && attackingCharacter.AIController == null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(LogName + " attacked by " + attackingCharacter.LogName + ".");
|
||||
sb.Append(GameServer.CharacterLogName(this) + " attacked by " + GameServer.CharacterLogName(attackingCharacter) + ".");
|
||||
if (attackResult.Afflictions != null)
|
||||
{
|
||||
foreach (Affliction affliction in attackResult.Afflictions)
|
||||
@@ -2632,13 +2737,19 @@ namespace Barotrauma
|
||||
mainLimb.body.ApplyLinearImpulse(impulse, hitPos, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
bool wasDead = IsDead;
|
||||
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
|
||||
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound);
|
||||
CharacterHealth.ApplyDamage(hitLimb, attackResult);
|
||||
ApplyStatusEffects(ActionType.OnDamaged, 1.0f);
|
||||
if (attacker != this)
|
||||
{
|
||||
OnAttacked?.Invoke(attacker, attackResult);
|
||||
OnAttackedProjSpecific(attacker, attackResult);
|
||||
OnAttackedProjSpecific(attacker, attackResult, stun);
|
||||
if (!wasDead)
|
||||
{
|
||||
TryAdjustAttackerSkill(attacker, -attackResult.Damage);
|
||||
}
|
||||
};
|
||||
|
||||
if (attacker != null && attackResult.Damage > 0.0f)
|
||||
@@ -2649,7 +2760,31 @@ namespace Barotrauma
|
||||
return attackResult;
|
||||
}
|
||||
|
||||
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult);
|
||||
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun);
|
||||
|
||||
public void TryAdjustAttackerSkill(Character attacker, float healthChange)
|
||||
{
|
||||
if (attacker == null) { return; }
|
||||
|
||||
bool isEnemy = AIController is EnemyAIController || TeamID != attacker.TeamID;
|
||||
if (isEnemy)
|
||||
{
|
||||
if (healthChange < 0.0f)
|
||||
{
|
||||
float attackerSkillLevel = attacker.GetSkillLevel("weapons");
|
||||
attacker.Info?.IncreaseSkillLevel("weapons",
|
||||
-healthChange * SkillSettings.Current.SkillIncreasePerHostileDamage / Math.Max(attackerSkillLevel, 1.0f),
|
||||
attacker.WorldPosition + Vector2.UnitY * 100.0f);
|
||||
}
|
||||
}
|
||||
else if (healthChange > 0.0f)
|
||||
{
|
||||
float attackerSkillLevel = attacker.GetSkillLevel("medical");
|
||||
attacker.Info?.IncreaseSkillLevel("medical",
|
||||
healthChange * SkillSettings.Current.SkillIncreasePerFriendlyHealed / Math.Max(attackerSkillLevel, 1.0f),
|
||||
attacker.WorldPosition + Vector2.UnitY * 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
|
||||
{
|
||||
@@ -2732,7 +2867,7 @@ namespace Barotrauma
|
||||
|
||||
partial void ImplodeFX();
|
||||
|
||||
public void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage = false)
|
||||
public void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage = false, bool log = true)
|
||||
{
|
||||
if (IsDead || CharacterHealth.Unkillable) { return; }
|
||||
|
||||
@@ -2775,9 +2910,9 @@ namespace Barotrauma
|
||||
|
||||
SteamAchievementManager.OnCharacterKilled(this, CauseOfDeath);
|
||||
|
||||
KillProjSpecific(causeOfDeath, causeOfDeathAffliction);
|
||||
KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log);
|
||||
|
||||
if (info != null) info.CauseOfDeath = CauseOfDeath;
|
||||
if (info != null) { info.CauseOfDeath = CauseOfDeath; }
|
||||
AnimController.movement = Vector2.Zero;
|
||||
AnimController.TargetMovement = Vector2.Zero;
|
||||
|
||||
@@ -2800,7 +2935,7 @@ namespace Barotrauma
|
||||
GameMain.GameSession.KillCharacter(this);
|
||||
}
|
||||
}
|
||||
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction);
|
||||
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log);
|
||||
|
||||
public void Revive()
|
||||
{
|
||||
@@ -2930,13 +3065,13 @@ namespace Barotrauma
|
||||
public IEnumerable<AttackContext> GetAttackContexts()
|
||||
{
|
||||
currentContexts.Clear();
|
||||
if (AnimController.CurrentAnimationParams.IsGroundedAnimation)
|
||||
if (AnimController.InWater)
|
||||
{
|
||||
currentContexts.Add(AttackContext.Ground);
|
||||
currentContexts.Add(AttackContext.Water);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentContexts.Add(AttackContext.Water);
|
||||
currentContexts.Add(AttackContext.Ground);
|
||||
}
|
||||
if (CurrentHull == null)
|
||||
{
|
||||
|
||||
@@ -293,6 +293,9 @@ namespace Barotrauma
|
||||
|
||||
private NPCPersonalityTrait personalityTrait;
|
||||
|
||||
public Order CurrentOrder { get; set;}
|
||||
public string CurrentOrderOption { get; set; }
|
||||
|
||||
//unique ID given to character infos in MP
|
||||
//used by clients to identify which infos are the same to prevent duplicate characters in round summary
|
||||
public ushort ID;
|
||||
@@ -524,9 +527,11 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (XElement subElement in infoElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "job") continue;
|
||||
Job = new Job(subElement);
|
||||
break;
|
||||
if (subElement.Name.ToString().Equals("job", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Job = new Job(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
LoadHeadAttachments();
|
||||
}
|
||||
@@ -661,7 +666,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (XElement limbElement in Ragdoll.MainElement.Elements())
|
||||
{
|
||||
if (limbElement.GetAttributeString("type", "").ToLowerInvariant() != "head") { continue; }
|
||||
if (!limbElement.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
XElement spriteElement = limbElement.Element("sprite");
|
||||
if (spriteElement == null) { continue; }
|
||||
@@ -677,7 +682,7 @@ namespace Barotrauma
|
||||
//go through the files in the directory to find a matching sprite
|
||||
foreach (string file in Directory.GetFiles(Path.GetDirectoryName(spritePath)))
|
||||
{
|
||||
if (!file.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -828,6 +833,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (Job == null || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) || Character == null) { return; }
|
||||
|
||||
if (Job.Prefab.Identifier == "assistant")
|
||||
{
|
||||
increase *= SkillSettings.Current.AssistantSkillIncreaseMultiplier;
|
||||
}
|
||||
|
||||
float prevLevel = Job.GetSkillLevel(skillIdentifier);
|
||||
Job.IncreaseSkillLevel(skillIdentifier, increase);
|
||||
|
||||
@@ -955,7 +965,7 @@ namespace Barotrauma
|
||||
foreach (XElement childInvElement in itemElement.Elements())
|
||||
{
|
||||
if (itemContainerIndex >= itemContainers.Count) break;
|
||||
if (childInvElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
|
||||
if (!childInvElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
SpawnInventoryItemsRecursive(itemContainers[itemContainerIndex].Inventory, childInvElement);
|
||||
itemContainerIndex++;
|
||||
}
|
||||
@@ -987,6 +997,15 @@ namespace Barotrauma
|
||||
faceAttachments = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset order data so it doesn't carry into further rounds, as the AI is "recreated" always in between rounds anyway.
|
||||
/// </summary>
|
||||
public void ResetCurrentOrder()
|
||||
{
|
||||
CurrentOrder = null;
|
||||
CurrentOrderOption = "";
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Character = null;
|
||||
|
||||
+3
-1
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
public virtual float Strength
|
||||
{
|
||||
get { return _strength; }
|
||||
set { _strength = value; }
|
||||
set { _strength = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength); }
|
||||
}
|
||||
|
||||
[Serialize("", true), Editable]
|
||||
@@ -184,6 +184,8 @@ namespace Barotrauma
|
||||
{
|
||||
_strength += currentEffect.StrengthChange * deltaTime * (1f - characterHealth.GetResistance(Prefab.Identifier));
|
||||
}
|
||||
// Don't use the property, because its virtual and some afflictions like husk overload it for external use.
|
||||
_strength = MathHelper.Clamp(_strength, 0.0f, Prefab.MaxStrength);
|
||||
|
||||
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
|
||||
{
|
||||
|
||||
+2
-2
@@ -101,9 +101,9 @@ namespace Barotrauma
|
||||
{
|
||||
float random = Rand.Value(Rand.RandSync.Server);
|
||||
huskInfection.Clear();
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * deltaTime / character.AnimController.Limbs.Length));
|
||||
huskInfection.Add(AfflictionPrefab.InternalDamage.Instantiate(random * 10 * deltaTime / character.AnimController.Limbs.Length));
|
||||
character.LastDamageSource = null;
|
||||
float force = applyForce ? random * 0.1f * limb.Mass : 0;
|
||||
float force = applyForce ? random * 0.5f * limb.Mass : 0;
|
||||
character.DamageLimb(limb.WorldPosition, limb, huskInfection, 0, false, force);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -265,7 +265,8 @@ namespace Barotrauma
|
||||
public readonly Sprite Icon;
|
||||
public readonly Color[] IconColors;
|
||||
|
||||
private List<Effect> effects = new List<Effect>();
|
||||
private readonly List<Effect> effects = new List<Effect>();
|
||||
public IEnumerable<Effect> Effects => effects;
|
||||
|
||||
private readonly string typeName;
|
||||
|
||||
|
||||
@@ -114,6 +114,10 @@ namespace Barotrauma
|
||||
private List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||
//non-limb-specific afflictions
|
||||
private List<Affliction> afflictions = new List<Affliction>();
|
||||
/// <summary>
|
||||
/// Note: returns only the non-limb-secific afflictions. Use GetAllAfflictions or some other method for getting also the limb-specific afflictions.
|
||||
/// </summary>
|
||||
public IEnumerable<Affliction> Afflictions => afflictions;
|
||||
|
||||
private HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
||||
private Affliction bloodlossAffliction;
|
||||
@@ -160,12 +164,12 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Character.NeedsAir || Unkillable) return 100.0f;
|
||||
if (!Character.NeedsOxygen || Unkillable) { return 100.0f; }
|
||||
return -oxygenLowAffliction.Strength + 100;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!Character.NeedsAir || Unkillable) return;
|
||||
if (!Character.NeedsOxygen || Unkillable) { return; }
|
||||
oxygenLowAffliction.Strength = MathHelper.Clamp(-value + 100, 0.0f, 200.0f);
|
||||
}
|
||||
}
|
||||
@@ -216,7 +220,7 @@ namespace Barotrauma
|
||||
limbHealths.Clear();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "limb") continue;
|
||||
if (!subElement.Name.ToString().Equals("limb", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
limbHealths.Add(new LimbHealth(subElement, this));
|
||||
}
|
||||
if (limbHealths.Count == 0)
|
||||
@@ -269,30 +273,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true)
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (affliction.Prefab.Identifier == identifier) return affliction;
|
||||
}
|
||||
if (!allowLimbAfflictions) return null;
|
||||
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
if (affliction.Prefab.Identifier == identifier) return affliction;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetAffliction<T>(string identifier, bool allowLimbAfflictions = true) where T : Affliction
|
||||
{
|
||||
return GetAffliction(identifier, allowLimbAfflictions) as T;
|
||||
}
|
||||
|
||||
public IEnumerable<Affliction> GetAfflictionsByType(string afflictionType, Limb limb)
|
||||
{
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
@@ -304,6 +284,37 @@ namespace Barotrauma
|
||||
return limbHealths[limb.HealthIndex].Afflictions.Where(a => a.Prefab.AfflictionType == afflictionType);
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, bool allowLimbAfflictions = true)
|
||||
=> GetAffliction(a => a.Prefab.Identifier == identifier, allowLimbAfflictions);
|
||||
|
||||
public Affliction GetAfflictionOfType(string afflictionType, bool allowLimbAfflictions = true)
|
||||
=> GetAffliction(a => a.Prefab.AfflictionType == afflictionType, allowLimbAfflictions);
|
||||
|
||||
private Affliction GetAffliction(Func<Affliction, bool> predicate, bool allowLimbAfflictions = true)
|
||||
{
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
if (predicate(affliction)) { return affliction; }
|
||||
}
|
||||
if (!allowLimbAfflictions)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
if (predicate(affliction)) { return affliction; }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public T GetAffliction<T>(string identifier, bool allowLimbAfflictions = true) where T : Affliction
|
||||
{
|
||||
return GetAffliction(identifier, allowLimbAfflictions) as T;
|
||||
}
|
||||
|
||||
public Affliction GetAffliction(string identifier, Limb limb)
|
||||
{
|
||||
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
|
||||
@@ -408,12 +419,11 @@ namespace Barotrauma
|
||||
return resistance;
|
||||
}
|
||||
|
||||
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
||||
public void ReduceAffliction(Limb targetLimb, string affliction, float amount)
|
||||
{
|
||||
affliction = affliction.ToLowerInvariant();
|
||||
|
||||
List<Affliction> matchingAfflictions = new List<Affliction>(afflictions);
|
||||
|
||||
matchingAfflictions.Clear();
|
||||
matchingAfflictions.AddRange(afflictions);
|
||||
if (targetLimb != null)
|
||||
{
|
||||
matchingAfflictions.AddRange(limbHealths[targetLimb.HealthIndex].Afflictions);
|
||||
@@ -426,8 +436,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
matchingAfflictions.RemoveAll(a =>
|
||||
a.Prefab.Identifier.ToLowerInvariant() != affliction &&
|
||||
a.Prefab.AfflictionType.ToLowerInvariant() != affliction);
|
||||
!a.Prefab.Identifier.Equals(affliction, StringComparison.OrdinalIgnoreCase) &&
|
||||
!a.Prefab.AfflictionType.Equals(affliction, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (matchingAfflictions.Count == 0) return;
|
||||
|
||||
@@ -526,7 +536,7 @@ namespace Barotrauma
|
||||
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsAir && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
@@ -559,7 +569,7 @@ namespace Barotrauma
|
||||
private void AddAffliction(Affliction newAffliction)
|
||||
{
|
||||
if (!DoesBleed && newAffliction is AfflictionBleeding) return;
|
||||
if (!Character.NeedsAir && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) return;
|
||||
if (newAffliction.Prefab.AfflictionType == "huskinfection")
|
||||
{
|
||||
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
|
||||
@@ -653,7 +663,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateOxygen(float deltaTime)
|
||||
{
|
||||
if (!Character.NeedsAir) return;
|
||||
if (!Character.NeedsOxygen) { return; }
|
||||
|
||||
float prevOxygen = OxygenAmount;
|
||||
if (IsUnconscious)
|
||||
@@ -691,13 +701,15 @@ namespace Barotrauma
|
||||
foreach (Affliction affliction in limbHealth.Afflictions)
|
||||
{
|
||||
float vitalityDecrease = affliction.GetVitalityDecrease(this);
|
||||
if (limbHealth.VitalityMultipliers.ContainsKey(affliction.Prefab.Identifier.ToLowerInvariant()))
|
||||
string identifier = affliction.Prefab.Identifier.ToLowerInvariant();
|
||||
string type = affliction.Prefab.AfflictionType.ToLowerInvariant();
|
||||
if (limbHealth.VitalityMultipliers.ContainsKey(identifier))
|
||||
{
|
||||
vitalityDecrease *= limbHealth.VitalityMultipliers[affliction.Prefab.Identifier.ToLowerInvariant()];
|
||||
vitalityDecrease *= limbHealth.VitalityMultipliers[identifier];
|
||||
}
|
||||
if (limbHealth.VitalityTypeMultipliers.ContainsKey(affliction.Prefab.AfflictionType.ToLowerInvariant()))
|
||||
if (limbHealth.VitalityTypeMultipliers.ContainsKey(type))
|
||||
{
|
||||
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[affliction.Prefab.AfflictionType.ToLowerInvariant()];
|
||||
vitalityDecrease *= limbHealth.VitalityTypeMultipliers[type];
|
||||
}
|
||||
vitalityDecrease *= damageResistanceMultiplier;
|
||||
Vitality -= vitalityDecrease;
|
||||
@@ -750,6 +762,7 @@ namespace Barotrauma
|
||||
return new Pair<CauseOfDeathType, Affliction>(causeOfDeath, strongestAffliction);
|
||||
}
|
||||
|
||||
// TODO: this method is called a lot (every half second) -> optimize, don't create new class instances and lists every time!
|
||||
private List<Affliction> GetAllAfflictions(bool mergeSameAfflictions)
|
||||
{
|
||||
List<Affliction> allAfflictions = new List<Affliction>(afflictions);
|
||||
@@ -832,10 +845,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
||||
private readonly List<Pair<LimbHealth, Affliction>> limbAfflictions = new List<Pair<LimbHealth, Affliction>>();
|
||||
public void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
List<Affliction> activeAfflictions = afflictions.FindAll(a => a.Strength > 0.0f && a.Strength >= a.Prefab.ActivationThreshold);
|
||||
|
||||
activeAfflictions.Clear();
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
if (affliction.Strength > 0.0f && affliction.Strength >= affliction.Prefab.ActivationThreshold)
|
||||
{
|
||||
activeAfflictions.Add(affliction);
|
||||
}
|
||||
}
|
||||
msg.Write((byte)activeAfflictions.Count);
|
||||
foreach (Affliction affliction in activeAfflictions)
|
||||
{
|
||||
@@ -845,7 +866,7 @@ namespace Barotrauma
|
||||
0.0f, affliction.Prefab.MaxStrength, 8);
|
||||
}
|
||||
|
||||
List<Pair<LimbHealth, Affliction>> limbAfflictions = new List<Pair<LimbHealth, Affliction>>();
|
||||
limbAfflictions.Clear();
|
||||
foreach (LimbHealth limbHealth in limbHealths)
|
||||
{
|
||||
foreach (Affliction limbAffliction in limbHealth.Afflictions)
|
||||
|
||||
@@ -19,6 +19,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(1.0f, false), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = 1)]
|
||||
public float ProbabilityMultiplier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize("0.0,360", false), Editable]
|
||||
public Vector2 ArmorSector
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
||||
skills = new Dictionary<string, Skill>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "skill") { continue; }
|
||||
if (!subElement.Name.ToString().Equals("skill", System.StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
string skillIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrEmpty(skillIdentifier)) { continue; }
|
||||
skills.Add(
|
||||
@@ -80,8 +80,8 @@ namespace Barotrauma
|
||||
|
||||
public float GetSkillLevel(string skillIdentifier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(skillIdentifier)) { return 0.0f; }
|
||||
skills.TryGetValue(skillIdentifier, out Skill skill);
|
||||
|
||||
return (skill == null) ? 0.0f : skill.Level;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
// TODO: not used
|
||||
[Serialize(10.0f, false)]
|
||||
public float Commonness
|
||||
{
|
||||
@@ -262,7 +263,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (XElement element in mainElement.Elements())
|
||||
{
|
||||
if (element.Name.ToString().ToLowerInvariant() == "nojob") { continue; }
|
||||
if (element.Name.ToString().Equals("nojob", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (element.IsOverride())
|
||||
{
|
||||
var job = new JobPrefab(element.FirstElement(), file.Path)
|
||||
|
||||
@@ -366,6 +366,8 @@ namespace Barotrauma
|
||||
|
||||
public string Name => Params.Name;
|
||||
|
||||
public bool IsDead => character.IsDead;
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties
|
||||
{
|
||||
get;
|
||||
@@ -471,43 +473,59 @@ namespace Barotrauma
|
||||
return AddDamage(simPosition, afflictions, playSound);
|
||||
}
|
||||
|
||||
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||
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
|
||||
var afflictionsCopy = afflictions.Where(a => Rand.Range(0.0f, 1.0f) <= a.Probability).ToList();
|
||||
for (int i = 0; i < afflictionsCopy.Count; i++)
|
||||
appliedDamageModifiers.Clear();
|
||||
afflictionsCopy.Clear();
|
||||
foreach (var affliction in afflictions)
|
||||
{
|
||||
var newAffliction = affliction;
|
||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||
if (random > affliction.Probability) { continue; }
|
||||
bool applyAffliction = true;
|
||||
foreach (DamageModifier damageModifier in DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictionsCopy[i])) continue;
|
||||
if (!damageModifier.MatchesAffliction(affliction)) { continue; }
|
||||
if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
|
||||
{
|
||||
applyAffliction = false;
|
||||
continue;
|
||||
}
|
||||
if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
|
||||
{
|
||||
afflictionsCopy[i] = afflictionsCopy[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
newAffliction = affliction.CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WearableSprite wearable in wearingItems)
|
||||
{
|
||||
foreach (DamageModifier damageModifier in wearable.WearableComponent.DamageModifiers)
|
||||
{
|
||||
if (!damageModifier.MatchesAffliction(afflictionsCopy[i])) continue;
|
||||
if (!damageModifier.MatchesAffliction(affliction)) { continue; }
|
||||
if (random > affliction.Probability * damageModifier.ProbabilityMultiplier)
|
||||
{
|
||||
applyAffliction = false;
|
||||
continue;
|
||||
}
|
||||
if (SectorHit(damageModifier.ArmorSectorInRadians, simPosition))
|
||||
{
|
||||
afflictionsCopy[i] = afflictionsCopy[i].CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
newAffliction = affliction.CreateMultiplied(damageModifier.DamageMultiplier);
|
||||
appliedDamageModifiers.Add(damageModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (applyAffliction)
|
||||
{
|
||||
afflictionsCopy.Add(newAffliction);
|
||||
}
|
||||
}
|
||||
|
||||
AddDamageProjSpecific(simPosition, afflictionsCopy, playSound, appliedDamageModifiers);
|
||||
|
||||
AddDamageProjSpecific(afflictionsCopy, playSound, appliedDamageModifiers);
|
||||
return new AttackResult(afflictionsCopy, this, appliedDamageModifiers);
|
||||
}
|
||||
|
||||
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers);
|
||||
partial void AddDamageProjSpecific(IEnumerable<Affliction> afflictions, bool playSound, IEnumerable<DamageModifier> appliedDamageModifiers);
|
||||
|
||||
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
|
||||
{
|
||||
@@ -646,33 +664,19 @@ namespace Barotrauma
|
||||
|
||||
if (wasHit)
|
||||
{
|
||||
bool playSound = false;
|
||||
#if CLIENT
|
||||
playSound = LastAttackSoundTime < Timing.TotalTime - SoundInterval;
|
||||
if (playSound)
|
||||
if (character == Character.Controlled || GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
LastAttackSoundTime = SoundInterval;
|
||||
ExecuteAttack(damageTarget, targetLimb, out attackResult);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new object[]
|
||||
{
|
||||
NetEntityEvent.Type.ExecuteAttack,
|
||||
this,
|
||||
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
|
||||
damageTarget is Character && targetLimb != null ? Array.IndexOf(((Character)damageTarget).AnimController.Limbs, targetLimb) : 0
|
||||
});
|
||||
#endif
|
||||
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(Params.GetSpriteOrientation());
|
||||
var from = body.FarseerBody.GetWorldPoint(localFront);
|
||||
var to = from;
|
||||
var drawPos = body.DrawPosition;
|
||||
StickTo(structureBody, from, to);
|
||||
}
|
||||
attack.ResetAttackTimer();
|
||||
attack.SetCoolDown();
|
||||
}
|
||||
|
||||
Vector2 diff = attackSimPos - SimPosition;
|
||||
@@ -705,11 +709,49 @@ namespace Barotrauma
|
||||
if (!attack.IsRunning)
|
||||
{
|
||||
// Set the main collider where the body lands after the attack
|
||||
character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: 0);
|
||||
character.AnimController.Collider.SetTransform(character.AnimController.MainLimb.body.SimPosition, rotation: character.AnimController.Collider.Rotation);
|
||||
}
|
||||
return wasHit;
|
||||
}
|
||||
|
||||
public void ExecuteAttack(IDamageable damageTarget, Limb targetLimb, out AttackResult attackResult)
|
||||
{
|
||||
bool playSound = false;
|
||||
#if CLIENT
|
||||
playSound = LastAttackSoundTime < Timing.TotalTime - SoundInterval;
|
||||
if (playSound)
|
||||
{
|
||||
LastAttackSoundTime = SoundInterval;
|
||||
}
|
||||
#endif
|
||||
if (damageTarget is Character targetCharacter && targetLimb != null)
|
||||
{
|
||||
attackResult = attack.DoDamageToLimb(character, targetLimb, WorldPosition, 1.0f, playSound);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (damageTarget is Item targetItem && !targetItem.Prefab.DamagedByMonsters)
|
||||
{
|
||||
attackResult = new AttackResult();
|
||||
}
|
||||
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(Params.GetSpriteOrientation());
|
||||
var from = body.FarseerBody.GetWorldPoint(localFront);
|
||||
var to = from;
|
||||
var drawPos = body.DrawPosition;
|
||||
StickTo(structureBody, from, to);
|
||||
}*/
|
||||
attack.ResetAttackTimer();
|
||||
attack.SetCoolDown();
|
||||
}
|
||||
|
||||
private WeldJoint attachJoint;
|
||||
private WeldJoint colliderJoint;
|
||||
public bool IsStuck => attachJoint != null;
|
||||
|
||||
+2
-2
@@ -125,7 +125,7 @@ namespace Barotrauma
|
||||
public static string GetFolder(XDocument doc, string filePath)
|
||||
{
|
||||
var folder = doc.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = Path.Combine(Path.GetDirectoryName(filePath), "Animations");
|
||||
}
|
||||
@@ -198,7 +198,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
|
||||
selectedFile = filteredFiles.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[AnimationParams] Could not find an animation file that matches the name {fileName} and the animation type {animType}. Using the default animations.");
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace Barotrauma
|
||||
[Serialize(false, true), Editable]
|
||||
public bool NeedsAir { get; set; }
|
||||
|
||||
[Serialize(false, true, description: "Can the creature live without water or does it die on dry land?"), Editable]
|
||||
public bool NeedsWater { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool CanSpeak { get; set; }
|
||||
|
||||
@@ -52,12 +55,21 @@ namespace Barotrauma
|
||||
[Serialize("blood", true), Editable]
|
||||
public string BloodDecal { get; private set; }
|
||||
|
||||
[Serialize("blooddrop", true), Editable]
|
||||
public string BleedParticleAir { get; private set; }
|
||||
|
||||
[Serialize("waterblood", true), Editable]
|
||||
public string BleedParticleWater { get; private set; }
|
||||
|
||||
[Serialize(10f, true, description: "How effectively/easily the character eats other characters. Affects the forces, the amount of particles, and the time required before the target is eaten away"), Editable(MinValueFloat = 1, MaxValueFloat = 1000, ValueStep = 1)]
|
||||
public float EatingSpeed { get; set; }
|
||||
|
||||
[Serialize(1f, true, "Decreases the intensive path finding call frequency. Set to a lower value for insignificant creatures to improve performance."), Editable(minValue: 0f, maxValue: 1f)]
|
||||
public float PathFinderPriority { get; set; }
|
||||
|
||||
[Serialize(false, true), Editable]
|
||||
public bool HideInSonar { get; set; }
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public readonly List<SubParam> SubParams = new List<SubParam>();
|
||||
@@ -462,6 +474,12 @@ namespace Barotrauma
|
||||
[Serialize(true, true, description: "Enforce aggressive behavior if the creature is spawned as a target of a monster mission."), Editable()]
|
||||
public bool EnforceAggressiveBehaviorForMissions { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "Should the character target or ignore walls when it's inside the submarine. Doesn't have any effect if no target priority for walls is defined."), Editable()]
|
||||
public bool TargetInnerWalls { get; private set; }
|
||||
|
||||
[Serialize(false, true, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable()]
|
||||
public bool RandomAttack { get; private set; }
|
||||
|
||||
// TODO: latchonto, swarming
|
||||
|
||||
public IEnumerable<TargetParams> Targets => targets;
|
||||
|
||||
+12
-4
@@ -94,8 +94,7 @@ namespace Barotrauma
|
||||
|
||||
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
|
||||
{
|
||||
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier.ToLowerInvariant()==speciesName.ToLowerInvariant() &&
|
||||
(contentPackage==null || p.ContentPackage == contentPackage));
|
||||
CharacterPrefab prefab = CharacterPrefab.Find(p => p.Identifier.Equals(speciesName, StringComparison.OrdinalIgnoreCase) && (contentPackage == null || p.ContentPackage == contentPackage));
|
||||
if (prefab?.XDocument == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find config file for '{speciesName}' (content package {contentPackage?.Name ?? "null"})");
|
||||
@@ -107,7 +106,7 @@ namespace Barotrauma
|
||||
public static string GetFolder(XDocument doc, string filePath)
|
||||
{
|
||||
var folder = doc.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
|
||||
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
|
||||
if (string.IsNullOrEmpty(folder) || folder.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
folder = Path.Combine(Path.GetDirectoryName(filePath), "Ragdolls") + Path.DirectorySeparatorChar;
|
||||
}
|
||||
@@ -150,7 +149,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).ToLowerInvariant() == fileName.ToLowerInvariant());
|
||||
selectedFile = files.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f).Equals(fileName, StringComparison.OrdinalIgnoreCase));
|
||||
if (selectedFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"[RagdollParams] Could not find a ragdoll file that matches the name {fileName}. Using the default ragdoll.");
|
||||
@@ -814,6 +813,15 @@ namespace Barotrauma
|
||||
[Serialize("", true), Editable()]
|
||||
public string Texture { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true), Editable()]
|
||||
public Color Color { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true, description: "Target color when the character is dead."), Editable()]
|
||||
public Color DeadColor { get; set; }
|
||||
|
||||
[Serialize(0f, true, "How long it takes to fade into the dead color? 0 = Not applied."), Editable(DecimalCount = 1, MinValueFloat = 0, MaxValueFloat = 10)]
|
||||
public float DeadColorTime { get; set; }
|
||||
|
||||
public override string Name => "Sprite";
|
||||
|
||||
public SpriteParams(XElement element, RagdollParams ragdoll) : base(element, ragdoll) { }
|
||||
|
||||
@@ -65,6 +65,37 @@ namespace Barotrauma
|
||||
set { skillIncreasePerFabricatorRequiredSkill = value; }
|
||||
}
|
||||
|
||||
private float skillIncreasePerHostileDamage;
|
||||
[Serialize(0.01f, true)]
|
||||
public float SkillIncreasePerHostileDamage
|
||||
{
|
||||
get { return skillIncreasePerHostileDamage * GetCurrentSkillGainMultiplier(); }
|
||||
set { skillIncreasePerHostileDamage = value; }
|
||||
}
|
||||
|
||||
private float skillIncreasePerSecondWhenOperatingTurret;
|
||||
[Serialize(0.001f, true)]
|
||||
public float SkillIncreasePerSecondWhenOperatingTurret
|
||||
{
|
||||
get { return skillIncreasePerSecondWhenOperatingTurret * GetCurrentSkillGainMultiplier(); }
|
||||
set { skillIncreasePerSecondWhenOperatingTurret = value; }
|
||||
}
|
||||
|
||||
private float skillIncreasePerFriendlyHealed;
|
||||
[Serialize(0.001f, true)]
|
||||
public float SkillIncreasePerFriendlyHealed
|
||||
{
|
||||
get { return skillIncreasePerFriendlyHealed * GetCurrentSkillGainMultiplier(); }
|
||||
set { skillIncreasePerFriendlyHealed = value; }
|
||||
}
|
||||
|
||||
[Serialize(1.1f, true)]
|
||||
public float AssistantSkillIncreaseMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private SkillSettings(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
@@ -39,7 +39,10 @@ namespace Barotrauma
|
||||
TraitorMissions,
|
||||
EventManagerSettings,
|
||||
Orders,
|
||||
SkillSettings
|
||||
SkillSettings,
|
||||
Wreck,
|
||||
Corpses,
|
||||
WreckAIConfig
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
@@ -63,8 +66,11 @@ namespace Barotrauma
|
||||
ContentType.LevelObjectPrefabs,
|
||||
ContentType.RuinConfig,
|
||||
ContentType.Outpost,
|
||||
ContentType.Wreck,
|
||||
ContentType.WreckAIConfig,
|
||||
ContentType.Afflictions,
|
||||
ContentType.Orders
|
||||
ContentType.Orders,
|
||||
ContentType.Corpses
|
||||
};
|
||||
|
||||
//at least one file of each these types is required in core content packages
|
||||
@@ -75,6 +81,8 @@ namespace Barotrauma
|
||||
ContentType.Character,
|
||||
ContentType.Structure,
|
||||
ContentType.Outpost,
|
||||
ContentType.Wreck,
|
||||
ContentType.WreckAIConfig,
|
||||
ContentType.Text,
|
||||
ContentType.Executable,
|
||||
ContentType.ServerExecutable,
|
||||
@@ -87,7 +95,8 @@ namespace Barotrauma
|
||||
ContentType.Afflictions,
|
||||
ContentType.UIStyle,
|
||||
ContentType.EventManagerSettings,
|
||||
ContentType.Orders
|
||||
ContentType.Orders,
|
||||
ContentType.Corpses
|
||||
};
|
||||
|
||||
public static IEnumerable<ContentType> CorePackageRequiredFiles
|
||||
@@ -284,6 +293,7 @@ namespace Barotrauma
|
||||
case ContentType.None:
|
||||
case ContentType.Outpost:
|
||||
case ContentType.Submarine:
|
||||
case ContentType.Wreck:
|
||||
break;
|
||||
default:
|
||||
try
|
||||
@@ -364,7 +374,10 @@ namespace Barotrauma
|
||||
{
|
||||
if (Files.Find(file => file.Path == path && file.Type == type) != null) return null;
|
||||
|
||||
ContentFile cf = new ContentFile(path, type);
|
||||
ContentFile cf = new ContentFile(path, type)
|
||||
{
|
||||
ContentPackage = this
|
||||
};
|
||||
Files.Add(cf);
|
||||
|
||||
return cf;
|
||||
@@ -508,14 +521,22 @@ namespace Barotrauma
|
||||
return IsModFilePathAllowed(path);
|
||||
}
|
||||
/// <summary>
|
||||
/// Are mods allowed to install a file into the specified path. If a content package XML includes files
|
||||
/// with a prohibited path, they are treated as references to external files. For example, a mod could include
|
||||
/// some vanilla files in the XML, in which case the game will simply use the vanilla files present in the game folder.
|
||||
/// Returns whether mods are allowed to install a file into the specified path.
|
||||
/// Currently mods are only allowed to install files into the Mods folder.
|
||||
/// The only exception to this rule is the Vanilla content package.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsModFilePathAllowed(string path)
|
||||
{
|
||||
if (GameMain.VanillaContent.Files.Any(f => string.Equals(System.IO.Path.GetFullPath(f.Path).CleanUpPath(),
|
||||
System.IO.Path.GetFullPath(path).CleanUpPath(),
|
||||
StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
//file is in vanilla package, this is allowed
|
||||
return true;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
string temp = System.IO.Path.GetDirectoryName(path);
|
||||
@@ -573,7 +594,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (System.IO.Path.GetFileName(modDirectory.TrimEnd(System.IO.Path.DirectorySeparatorChar)) == "ExampleMod") { continue; }
|
||||
string modFilePath = System.IO.Path.Combine(modDirectory, Steam.SteamManager.MetadataFileName);
|
||||
if (File.Exists(modFilePath))
|
||||
string copyingFilePath = System.IO.Path.Combine(modDirectory, Steam.SteamManager.CopyIndicatorFileName);
|
||||
if (File.Exists(copyingFilePath))
|
||||
{
|
||||
//this mod didn't clean up its copying file; assume it's corrupted and delete it
|
||||
Directory.Delete(modDirectory, true);
|
||||
}
|
||||
else if (File.Exists(modFilePath))
|
||||
{
|
||||
List.Add(new ContentPackage(modFilePath));
|
||||
}
|
||||
|
||||
@@ -475,10 +475,7 @@ namespace Barotrauma
|
||||
commands.Add(new Command("teleportcharacter|teleport", "teleport [character name]: Teleport the specified character to the position of the cursor. If the name parameter is omitted, the controlled character will be teleported.", null,
|
||||
() =>
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
};
|
||||
return new string[][] { ListCharacterNames() };
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("godmode", "godmode: Toggle submarine godmode. Makes the main submarine invulnerable to damage.", (string[] args) =>
|
||||
@@ -531,18 +528,17 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) =>
|
||||
{
|
||||
if (args.Length == 0) return;
|
||||
args[0] = args[0].ToLowerInvariant();
|
||||
if (args.Length == 0) { return; }
|
||||
foreach (MapEntity mapEntity in MapEntity.mapEntityList)
|
||||
{
|
||||
if (mapEntity.Name.ToLowerInvariant() == args[0])
|
||||
if (mapEntity.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ThrowError(mapEntity.ID + ": " + mapEntity.Name.ToString());
|
||||
}
|
||||
}
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Name.ToLowerInvariant() == args[0] || character.SpeciesName.ToLowerInvariant() == args[0])
|
||||
if (character.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) || character.SpeciesName.Equals(args[0], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ThrowError(character.ID + ": " + character.Name.ToString());
|
||||
}
|
||||
@@ -554,8 +550,8 @@ namespace Barotrauma
|
||||
if (args.Length < 2) return;
|
||||
|
||||
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
|
||||
a.Name.ToLowerInvariant() == args[0].ToLowerInvariant() ||
|
||||
a.Identifier.ToLowerInvariant() == args[0].ToLowerInvariant());
|
||||
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase));
|
||||
if (afflictionPrefab == null)
|
||||
{
|
||||
ThrowError("Affliction \"" + args[0] + "\" not found.");
|
||||
@@ -682,7 +678,7 @@ namespace Barotrauma
|
||||
NewMessage(Hull.EditFire ? "Fire spawning on" : "Fire spawning off", Color.White);
|
||||
}, isCheat: true));
|
||||
|
||||
commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [emp strength]: Creates an explosion at the position of the cursor.", null, isCheat: true));
|
||||
commands.Add(new Command("explosion", "explosion [range] [force] [damage] [structuredamage] [item damage] [emp strength]: Creates an explosion at the position of the cursor.", null, isCheat: true));
|
||||
|
||||
commands.Add(new Command("showseed|showlevelseed", "showseed: Show the seed of the current level.", (string[] args) =>
|
||||
{
|
||||
@@ -695,18 +691,20 @@ namespace Barotrauma
|
||||
NewMessage("Level seed: " + Level.Loaded.Seed);
|
||||
}
|
||||
},null));
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("crash", "crash: Crashes the game.", (string[] args) =>
|
||||
{
|
||||
throw new Exception("crash command issued");
|
||||
}));
|
||||
|
||||
|
||||
commands.Add(new Command("teleportsub", "teleportsub [start/end]: Teleport the submarine to the start or end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.", (string[] args) =>
|
||||
{
|
||||
if (Submarine.MainSub == null || Level.Loaded == null) return;
|
||||
|
||||
if (args.Length > 0 && args[0].ToLowerInvariant() == "start")
|
||||
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
#if SERVER
|
||||
ThrowError("Cannot teleport the sub to the position of the cursor. Use \"start\" or \"end\", or execute the command as a client.");
|
||||
#else
|
||||
Submarine.MainSub.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
||||
#endif
|
||||
}
|
||||
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
||||
}
|
||||
@@ -714,8 +712,21 @@ namespace Barotrauma
|
||||
{
|
||||
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
||||
}
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
new string[] { "start", "end", "cursor" }
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("crash", "crash: Crashes the game.", (string[] args) =>
|
||||
{
|
||||
throw new Exception("crash command issued");
|
||||
}));
|
||||
|
||||
commands.Add(new Command("removecharacter", "removecharacter [character name]: Immediately deletes the specified character.", (string[] args) =>
|
||||
{
|
||||
if (args.Length == 0) { return; }
|
||||
@@ -751,18 +762,18 @@ namespace Barotrauma
|
||||
|
||||
IEnumerable<object> TestLevels()
|
||||
{
|
||||
Submarine selectedSub = null;
|
||||
SubmarineInfo selectedSub = null;
|
||||
string subName = GameMain.Config.QuickStartSubmarineName;
|
||||
if (!string.IsNullOrEmpty(subName))
|
||||
{
|
||||
selectedSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name.ToLower() == subName.ToLower());
|
||||
selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.ToLower() == subName.ToLower());
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
while (true)
|
||||
{
|
||||
var gamesession = new GameSession(
|
||||
Submarine.SavedSubmarines.GetRandom(s => !s.HasTag(SubmarineTag.HideInMenus)),
|
||||
SubmarineInfo.SavedSubmarines.GetRandom(s => !s.HasTag(SubmarineTag.HideInMenus)),
|
||||
"Data/Saves/test.xml",
|
||||
GameModePreset.List.Find(gm => gm.Identifier == "devsandbox"),
|
||||
missionPrefab: null);
|
||||
@@ -776,7 +787,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (ruin.Area.Intersects(subWorldRect))
|
||||
{
|
||||
ThrowError("Ruins intersect with the sub. Seed: " + seed + ", Submarine: " + Submarine.MainSub.Name);
|
||||
ThrowError("Ruins intersect with the sub. Seed: " + seed + ", Submarine: " + Submarine.MainSub.Info.Name);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
@@ -797,7 +808,7 @@ namespace Barotrauma
|
||||
(int)(maxExtents.X - minExtents.X), (int)(maxExtents.Y - minExtents.Y));
|
||||
if (cellRect.Intersects(subWorldRect))
|
||||
{
|
||||
ThrowError("Level cells intersect with the sub. Seed: " + seed + ", Submarine: " + Submarine.MainSub.Name);
|
||||
ThrowError("Level cells intersect with the sub. Seed: " + seed + ", Submarine: " + Submarine.MainSub.Info.Name);
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
@@ -816,7 +827,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
|
||||
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
|
||||
{
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
@@ -1097,10 +1108,7 @@ namespace Barotrauma
|
||||
//TODO: alphabetical order?
|
||||
commands.Add(new Command("control", "control [character name]: Start controlling the specified character (client-only).", null, () =>
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Character.CharacterList.Select(c => c.Name).Distinct().ToArray()
|
||||
};
|
||||
return new string[][] { ListCharacterNames() };
|
||||
}));
|
||||
commands.Add(new Command("los", "Toggle the line of sight effect on/off (client-only).", null, isCheat: true));
|
||||
commands.Add(new Command("lighting|lights", "Toggle lighting on/off (client-only).", null, isCheat: true));
|
||||
@@ -1220,15 +1228,17 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (!splitCommand[0].ToLowerInvariant().Equals("admin"))
|
||||
string firstCommand = splitCommand[0].ToLowerInvariant();
|
||||
|
||||
if (!firstCommand.Equals("admin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
NewMessage(command, Color.White, true);
|
||||
}
|
||||
|
||||
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
Command matchingCommand = commands.Find(c => c.names.Contains(splitCommand[0].ToLowerInvariant()));
|
||||
Command matchingCommand = commands.Find(c => c.names.Contains(firstCommand));
|
||||
if (matchingCommand == null)
|
||||
{
|
||||
//if the command is not defined client-side, we'll relay it anyway because it may be a custom command at the server's side
|
||||
@@ -1236,7 +1246,7 @@ namespace Barotrauma
|
||||
NewMessage("Server command: " + command, Color.Cyan);
|
||||
return;
|
||||
}
|
||||
else if (GameMain.Client.HasConsoleCommandPermission(splitCommand[0].ToLowerInvariant()))
|
||||
else if (GameMain.Client.HasConsoleCommandPermission(firstCommand))
|
||||
{
|
||||
if (matchingCommand.RelayToServer)
|
||||
{
|
||||
@@ -1262,7 +1272,7 @@ namespace Barotrauma
|
||||
bool commandFound = false;
|
||||
foreach (Command c in commands)
|
||||
{
|
||||
if (!c.names.Contains(splitCommand[0].ToLowerInvariant())) continue;
|
||||
if (!c.names.Contains(firstCommand)) { continue; }
|
||||
c.Execute(splitCommand.Skip(1).ToArray());
|
||||
commandFound = true;
|
||||
break;
|
||||
@@ -1273,7 +1283,9 @@ namespace Barotrauma
|
||||
ThrowError("Command \"" + splitCommand[0] + "\" not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string[] ListCharacterNames() => Character.CharacterList.OrderBy(c => c.IsDead).ThenByDescending(c => c.IsHuman).Select(c => c.Name).Distinct().ToArray();
|
||||
|
||||
private static Character FindMatchingCharacter(string[] args, bool ignoreRemotePlayers = false, Client allowedRemotePlayer = null)
|
||||
{
|
||||
if (args.Length == 0) return null;
|
||||
@@ -1290,7 +1302,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var matchingCharacters = Character.CharacterList.FindAll(c =>
|
||||
c.Name.ToLowerInvariant() == characterName &&
|
||||
c.Name.Equals(characterName, StringComparison.OrdinalIgnoreCase) &&
|
||||
(!c.IsRemotePlayer || !ignoreRemotePlayers || allowedRemotePlayer?.Character == c));
|
||||
|
||||
if (!matchingCharacters.Any())
|
||||
@@ -1336,7 +1348,7 @@ namespace Barotrauma
|
||||
JobPrefab job = null;
|
||||
if (!JobPrefab.Prefabs.ContainsKey(characterLowerCase))
|
||||
{
|
||||
job = JobPrefab.Prefabs.Find(jp => jp.Name?.ToLowerInvariant() == characterLowerCase);
|
||||
job = JobPrefab.Prefabs.Find(jp => jp.Name != null && jp.Name.Equals(characterLowerCase, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1594,12 +1606,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Command FindCommand(string commandName)
|
||||
{
|
||||
commandName = commandName.ToLowerInvariant();
|
||||
return commands.Find(c => c.names.Any(n => n.ToLowerInvariant() == commandName));
|
||||
}
|
||||
|
||||
public static Command FindCommand(string commandName) => commands.Find(c => c.names.Any(n => n.Equals(commandName, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
public static void Log(string message)
|
||||
{
|
||||
@@ -1620,8 +1627,7 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (listBox == null) { NewMessage(error, Color.Red); return; }
|
||||
|
||||
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
|
||||
style: "InnerFrame", color: Color.White)
|
||||
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
@@ -1635,7 +1641,7 @@ namespace Barotrauma
|
||||
textBlock.SetTextPos();
|
||||
|
||||
listBox.UpdateScrollBarSize();
|
||||
listBox.BarScroll = 1.0f;
|
||||
listBox.BarScroll = 1.0f;
|
||||
|
||||
if (createMessageBox)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -53,7 +51,7 @@ namespace Barotrauma
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
spawnPos = Level.Loaded.GetRandomItemPos(
|
||||
(Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.5f) ? Level.PositionType.MainPath : Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
(Rand.Value(Rand.RandSync.Server) < 0.5f) ? Level.PositionType.MainPath : Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
500.0f, 10000.0f, 30.0f);
|
||||
|
||||
spawnPending = true;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -219,13 +218,44 @@ namespace Barotrauma
|
||||
|
||||
private void CreateEvents(ScriptedEventSet eventSet)
|
||||
{
|
||||
if (eventSet.ChooseRandom)
|
||||
int applyCount = 1;
|
||||
if (eventSet.PerRuin)
|
||||
{
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
applyCount = Level.Loaded.Ruins.Count();
|
||||
}
|
||||
else if (eventSet.PerWreck)
|
||||
{
|
||||
applyCount = Submarine.Loaded.Count(s => s.Info.IsWreck && (s.WreckAI == null || !s.WreckAI.IsAlive));
|
||||
}
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
if (eventSet.ChooseRandom)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
|
||||
if (eventPrefab != null)
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
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());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
}
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
@@ -236,30 +266,11 @@ namespace Barotrauma
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
}
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
|
||||
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet);
|
||||
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,11 +307,14 @@ namespace Barotrauma
|
||||
0.0f, 1.0f);
|
||||
|
||||
//don't create new events if within 50 meters of the start/end of the level
|
||||
if (distanceTraveled <= 0.0f ||
|
||||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
|
||||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
|
||||
if (!eventSet.AllowAtStart)
|
||||
{
|
||||
return false;
|
||||
if (distanceTraveled <= 0.0f ||
|
||||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
|
||||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
|
||||
@@ -368,17 +382,15 @@ namespace Barotrauma
|
||||
|
||||
pendingEventSets.RemoveAt(i);
|
||||
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
if (selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
//no events selected from this event set
|
||||
continue;
|
||||
//start events in this set
|
||||
foreach (ScriptedEvent scriptedEvent in selectedEvents[eventSet])
|
||||
{
|
||||
activeEvents.Add(scriptedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//start events in this set
|
||||
foreach (ScriptedEvent scriptedEvent in selectedEvents[eventSet])
|
||||
{
|
||||
activeEvents.Add(scriptedEvent);
|
||||
}
|
||||
//add child event sets to pending
|
||||
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
@@ -431,7 +443,7 @@ namespace Barotrauma
|
||||
enemyDanger = 0.0f;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead || character.IsUnconscious || !character.Enabled) continue;
|
||||
if (character.IsDead || character.IsIncapacitated || !character.Enabled) continue;
|
||||
|
||||
EnemyAIController enemyAI = character.AIController as EnemyAIController;
|
||||
if (enemyAI == null) continue;
|
||||
@@ -458,7 +470,7 @@ namespace Barotrauma
|
||||
int hullCount = 0;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine == null || hull.Submarine.IsOutpost) { continue; }
|
||||
if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
hullCount++;
|
||||
foreach (Gap gap in hull.ConnectedGaps)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, true);
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true);
|
||||
if (cargoSpawnPos == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found");
|
||||
|
||||
@@ -176,11 +176,11 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Pair<string, string> allowedLocationType in AllowedLocationTypes)
|
||||
{
|
||||
if (allowedLocationType.First.ToLowerInvariant() == "any" ||
|
||||
allowedLocationType.First.ToLowerInvariant() == from.Type.Identifier.ToLowerInvariant())
|
||||
if (allowedLocationType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedLocationType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowedLocationType.Second.ToLowerInvariant() == "any" ||
|
||||
allowedLocationType.Second.ToLowerInvariant() == to.Type.Identifier.ToLowerInvariant())
|
||||
if (allowedLocationType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedLocationType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -13,6 +15,17 @@ namespace Barotrauma
|
||||
private Item item;
|
||||
|
||||
private readonly Level.PositionType spawnPositionType;
|
||||
|
||||
private readonly string containerTag;
|
||||
|
||||
private readonly string existingItemTag;
|
||||
|
||||
private readonly bool showMessageWhenPickedUp;
|
||||
|
||||
/// <summary>
|
||||
/// Status effects executed on the target item when the mission starts. A random effect is chosen from each child list.
|
||||
/// </summary>
|
||||
private readonly List<List<StatusEffect>> statusEffects = new List<List<StatusEffect>>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
@@ -24,7 +37,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return ConvertUnits.ToDisplayUnits(item.SimPosition);
|
||||
yield return item.WorldPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +45,8 @@ namespace Barotrauma
|
||||
public SalvageMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
containerTag = prefab.ConfigElement.GetAttributeString("containertag", "");
|
||||
|
||||
if (prefab.ConfigElement.Attribute("itemname") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
|
||||
@@ -52,32 +67,118 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
existingItemTag = prefab.ConfigElement.GetAttributeString("existingitemtag", "");
|
||||
showMessageWhenPickedUp = prefab.ConfigElement.GetAttributeBool("showmessagewhenpickedup", false);
|
||||
|
||||
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
|
||||
foreach (XElement element in prefab.ConfigElement.Elements())
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
{
|
||||
var newEffect = StatusEffect.Load(element, parentDebugName: prefab.Name);
|
||||
if (newEffect == null) { continue; }
|
||||
statusEffects.Add(new List<StatusEffect> { newEffect });
|
||||
break;
|
||||
}
|
||||
case "chooserandom":
|
||||
statusEffects.Add(new List<StatusEffect>());
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
var newEffect = StatusEffect.Load(subElement, parentDebugName: prefab.Name);
|
||||
if (newEffect == null) { continue; }
|
||||
statusEffects.Last().Add(newEffect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
//ruin items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
//ruin/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Wreck ?
|
||||
0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
|
||||
if (item.HasTag("alien"))
|
||||
if (!string.IsNullOrEmpty(existingItemTag))
|
||||
{
|
||||
var suitableItems = Item.ItemList.Where(it => it.HasTag(existingItemTag));
|
||||
switch (spawnPositionType)
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f);
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
item = suitableItems.FirstOrDefault(it => it.ParentRuin != null && it.ParentRuin.Area.Contains(position));
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
foreach (Item it in suitableItems)
|
||||
{
|
||||
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; }
|
||||
Rectangle worldBorders = it.Submarine.Borders;
|
||||
worldBorders.Location += it.Submarine.WorldPosition.ToPoint();
|
||||
if (Submarine.RectContains(worldBorders, it.WorldPosition))
|
||||
{
|
||||
item = it;
|
||||
#if SERVER
|
||||
usedExistingItem = true;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
item.FindHull();
|
||||
}
|
||||
|
||||
for (int i = 0; i < statusEffects.Count; i++)
|
||||
{
|
||||
List<StatusEffect> effectList = statusEffects[i];
|
||||
if (effectList.Count == 0) { continue; }
|
||||
int effectIndex = Rand.Int(effectList.Count);
|
||||
var selectedEffect = effectList[effectIndex];
|
||||
item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: item.Position);
|
||||
#if SERVER
|
||||
executedEffectIndices.Add(new Pair<int, int>(i, effectIndex));
|
||||
#endif
|
||||
}
|
||||
|
||||
//try to find a container and place the item inside it
|
||||
if (!string.IsNullOrEmpty(containerTag) && item.ParentInventory == null)
|
||||
{
|
||||
//try to find an artifact holder and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
|
||||
|
||||
if (!it.HasTag(containerTag)) { continue; }
|
||||
switch (spawnPositionType)
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
if (it.Submarine != null || it.ParentRuin != null) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
if (it.ParentRuin == null) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; }
|
||||
break;
|
||||
}
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
if (itemContainer.Combine(item, user: null)) { break; } // Placement successful
|
||||
@@ -88,28 +189,46 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsClient)
|
||||
{
|
||||
if (item.ParentInventory != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
return;
|
||||
}
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (item.CurrentHull?.Submarine == null) { return; }
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (showMessageWhenPickedUp)
|
||||
{
|
||||
if (!(item.ParentInventory?.Owner is Character)) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.CurrentHull?.Submarine == null || item.CurrentHull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { return; }
|
||||
}
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (item.CurrentHull?.Submarine == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) { return; }
|
||||
if (item.CurrentHull?.Submarine == null || (!item.CurrentHull.Submarine.AtEndPosition && !item.CurrentHull.Submarine.AtStartPosition) || item.Removed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item?.Remove();
|
||||
item = null;
|
||||
|
||||
@@ -1,50 +1,46 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterEvent : ScriptedEvent
|
||||
{
|
||||
private string speciesName;
|
||||
|
||||
private int minAmount, maxAmount;
|
||||
|
||||
private readonly string speciesName;
|
||||
private readonly int minAmount, maxAmount;
|
||||
private List<Character> monsters;
|
||||
|
||||
private bool spawnDeep;
|
||||
private readonly bool spawnDeep;
|
||||
|
||||
private Vector2? spawnPos;
|
||||
|
||||
private bool disallowed;
|
||||
|
||||
private Level.PositionType spawnPosType;
|
||||
private readonly bool disallowed;
|
||||
|
||||
private readonly Level.PositionType spawnPosType;
|
||||
|
||||
private bool spawnPending;
|
||||
|
||||
private string characterFileName;
|
||||
|
||||
public override Vector2 DebugDrawPos
|
||||
{
|
||||
get { return spawnPos.HasValue ? spawnPos.Value : Vector2.Zero; }
|
||||
get { return spawnPos ?? Vector2.Zero; }
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (maxAmount <= 1)
|
||||
{
|
||||
return "MonsterEvent (" + characterFileName + ")";
|
||||
return "MonsterEvent (" + speciesName + ")";
|
||||
}
|
||||
else if (minAmount < maxAmount)
|
||||
{
|
||||
return "MonsterEvent (" + characterFileName + " x" + minAmount + "-" + maxAmount + ")";
|
||||
return "MonsterEvent (" + speciesName + " x" + minAmount + "-" + maxAmount + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "MonsterEvent (" + characterFileName + " x" + maxAmount + ")";
|
||||
return "MonsterEvent (" + speciesName + " x" + maxAmount + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +72,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
spawnDeep = prefab.ConfigElement.GetAttributeBool("spawndeep", false);
|
||||
characterFileName = Path.GetFileName(Path.GetDirectoryName(speciesName)).ToLower();
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
@@ -85,7 +80,10 @@ namespace Barotrauma
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tryKey))
|
||||
{
|
||||
if (!GameMain.NetworkMember.ServerSettings.MonsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
if (!GameMain.NetworkMember.ServerSettings.MonsterEnabled[tryKey])
|
||||
{
|
||||
disallowed = true; //spawn was disallowed by host
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,18 +104,8 @@ namespace Barotrauma
|
||||
|
||||
public override bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
float maxRange = Items.Components.Sonar.DefaultSonarRange * 0.8f;
|
||||
|
||||
List<Vector2> positions = GetAvailableSpawnPositions();
|
||||
foreach (Vector2 position in positions)
|
||||
{
|
||||
if (Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition) < maxRange * maxRange)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
float maxRange = Sonar.DefaultSonarRange * 0.8f;
|
||||
return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), Submarine.MainSub.WorldPosition) < maxRange * maxRange);
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
@@ -128,28 +116,44 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private List<Vector2> GetAvailableSpawnPositions()
|
||||
private List<Level.InterestingPosition> GetAvailableSpawnPositions()
|
||||
{
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType));
|
||||
|
||||
List<Vector2> positions = new List<Vector2>();
|
||||
foreach (var allowedPosition in availablePositions)
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType) && !Level.Loaded.UsedPositions.Contains(p));
|
||||
var removals = new List<Level.InterestingPosition>();
|
||||
foreach (var position in availablePositions)
|
||||
{
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(allowedPosition.Position.ToVector2())))) { continue; }
|
||||
positions.Add(allowedPosition.Position.ToVector2());
|
||||
}
|
||||
|
||||
if (spawnDeep)
|
||||
{
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
if (position.Submarine != null)
|
||||
{
|
||||
positions[i] = new Vector2(positions[i].X, positions[i].Y - Level.Loaded.Size.Y);
|
||||
if (position.Submarine.WreckAI != null && position.Submarine.WreckAI.IsAlive)
|
||||
{
|
||||
removals.Add(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (position.PositionType != Level.PositionType.MainPath) { continue; }
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(position.Position.ToVector2()))))
|
||||
{
|
||||
removals.Add(position);
|
||||
}
|
||||
if (spawnDeep)
|
||||
{
|
||||
for (int i = 0; i < availablePositions.Count; i++)
|
||||
{
|
||||
var pos = availablePositions[i].Position;
|
||||
pos = new Point(pos.X, pos.Y - Level.Loaded.Size.Y);
|
||||
availablePositions[i] = new Level.InterestingPosition(pos, availablePositions[i].PositionType);
|
||||
}
|
||||
}
|
||||
if (position.Position.Y < Level.Loaded.GetBottomPosition(position.Position.X).Y)
|
||||
{
|
||||
removals.Add(position);
|
||||
}
|
||||
}
|
||||
|
||||
positions.RemoveAll(pos => pos.Y < Level.Loaded.GetBottomPosition(pos.X).Y);
|
||||
|
||||
return positions;
|
||||
removals.ForEach(r => availablePositions.Remove(r));
|
||||
return availablePositions;
|
||||
}
|
||||
|
||||
private void FindSpawnPosition(bool affectSubImmediately)
|
||||
@@ -158,67 +162,100 @@ namespace Barotrauma
|
||||
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
if (affectSubImmediately && spawnPosType != Level.PositionType.Ruin)
|
||||
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
var removedPositions = new List<Level.InterestingPosition>();
|
||||
foreach (var position in availablePositions)
|
||||
{
|
||||
if (availablePositions.Count == 0)
|
||||
if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability)
|
||||
{
|
||||
removedPositions.Add(position);
|
||||
if (prefab.AllowOnlyOnce)
|
||||
{
|
||||
Level.Loaded.UsedPositions.Add(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
removedPositions.ForEach(p => availablePositions.Remove(p));
|
||||
bool isSubOrWreck = spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Wreck;
|
||||
if (affectSubImmediately && !isSubOrWreck)
|
||||
{
|
||||
if (availablePositions.None())
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
float closestDist = float.PositiveInfinity;
|
||||
//find the closest spawnposition that isn't too close to any of the subs
|
||||
foreach (Vector2 position in availablePositions)
|
||||
foreach (var position in availablePositions)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
|
||||
Vector2 pos = position.Position.ToVector2();
|
||||
float dist = Vector2.DistanceSquared(pos, Submarine.MainSub.WorldPosition);
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.IsOutpost) { continue; }
|
||||
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
float minDistToSub = GetMinDistanceToSub(sub);
|
||||
if (dist > minDistToSub * minDistToSub && dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
spawnPos = position;
|
||||
chosenPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//only found a spawnpos that's very far from the sub, pick one that's closer
|
||||
//and wait for the sub to move further before spawning
|
||||
if (closestDist > 15000.0f * 15000.0f)
|
||||
{
|
||||
foreach (Vector2 position in availablePositions)
|
||||
foreach (var position in availablePositions)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
|
||||
float dist = Vector2.DistanceSquared(position.Position.ToVector2(), Submarine.MainSub.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
spawnPos = position;
|
||||
chosenPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float minDist = spawnPosType == Level.PositionType.Ruin ? 0.0f : 20000.0f;
|
||||
availablePositions.RemoveAll(p => Vector2.Distance(Submarine.MainSub.WorldPosition, p) < minDist);
|
||||
if (availablePositions.Count == 0)
|
||||
if (!isSubOrWreck)
|
||||
{
|
||||
float minDistance = 20000;
|
||||
availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
|
||||
}
|
||||
if (availablePositions.None())
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
spawnPos = availablePositions[Rand.Int(availablePositions.Count, Rand.RandSync.Server)];
|
||||
chosenPosition = availablePositions.GetRandom();
|
||||
}
|
||||
if (chosenPosition.IsValid)
|
||||
{
|
||||
spawnPos = chosenPosition.Position.ToVector2();
|
||||
if (chosenPosition.Submarine != null || chosenPosition.Ruin != null)
|
||||
{
|
||||
var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, ruin: chosenPosition.Ruin, useSyncedRand: false);
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine);
|
||||
System.Diagnostics.Debug.Assert(spawnPoint.ParentRuin == chosenPosition.Ruin);
|
||||
spawnPos = spawnPoint.WorldPosition;
|
||||
}
|
||||
}
|
||||
spawnPending = true;
|
||||
if (prefab.AllowOnlyOnce)
|
||||
{
|
||||
Level.Loaded.UsedPositions.Add(chosenPosition);
|
||||
}
|
||||
}
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
private float GetMinDistanceToSub(Submarine submarine)
|
||||
{
|
||||
//9000 units is slightly less than the default range of the sonar
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), 9000.0f);
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), Sonar.DefaultSonarRange * 0.9f);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -243,9 +280,38 @@ namespace Barotrauma
|
||||
//wait until there are no submarines at the spawnpos
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.IsOutpost) { continue; }
|
||||
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
float minDist = GetMinDistanceToSub(submarine);
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) return;
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
|
||||
}
|
||||
|
||||
//if spawning in a ruin/cave, wait for someone to be close to it to spawning
|
||||
//unnecessary monsters in places the players might never visit during the round
|
||||
if (spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Cave || spawnPosType == Level.PositionType.Wreck)
|
||||
{
|
||||
bool someoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 0.8f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == Character.Controlled || c.IsRemotePlayer)
|
||||
{
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
@@ -280,7 +346,7 @@ namespace Barotrauma
|
||||
|
||||
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled;
|
||||
if (Character.Controlled != null) { targetEntity = Character.Controlled; }
|
||||
#endif
|
||||
|
||||
bool monstersDead = true;
|
||||
@@ -297,7 +363,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (monstersDead) Finished();
|
||||
if (monstersDead) { Finished(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -8,7 +7,7 @@ namespace Barotrauma
|
||||
{
|
||||
protected bool isFinished;
|
||||
|
||||
private readonly ScriptedEventPrefab prefab;
|
||||
protected readonly ScriptedEventPrefab prefab;
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
|
||||
@@ -7,12 +7,11 @@ namespace Barotrauma
|
||||
{
|
||||
class ScriptedEventPrefab
|
||||
{
|
||||
public readonly XElement ConfigElement;
|
||||
|
||||
public readonly Type EventType;
|
||||
|
||||
public readonly XElement ConfigElement;
|
||||
public readonly Type EventType;
|
||||
public readonly string MusicType;
|
||||
|
||||
public readonly float SpawnProbability;
|
||||
public readonly bool AllowOnlyOnce;
|
||||
public float Commonness;
|
||||
|
||||
public ScriptedEventPrefab(XElement element)
|
||||
@@ -34,6 +33,8 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
|
||||
}
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
|
||||
AllowOnlyOnce = element.GetAttributeBool("allowonlyonce", false);
|
||||
}
|
||||
|
||||
public ScriptedEvent CreateInstance()
|
||||
|
||||
@@ -25,6 +25,11 @@ namespace Barotrauma
|
||||
//the events in this set are delayed if the current EventManager intensity is not between these values
|
||||
public readonly float MinIntensity, MaxIntensity;
|
||||
|
||||
public readonly bool AllowAtStart;
|
||||
|
||||
public readonly bool PerRuin;
|
||||
public readonly bool PerWreck;
|
||||
|
||||
public readonly Dictionary<string, float> Commonness;
|
||||
|
||||
public readonly List<ScriptedEventPrefab> EventPrefabs;
|
||||
@@ -54,6 +59,10 @@ namespace Barotrauma
|
||||
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
|
||||
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
|
||||
|
||||
AllowAtStart = element.GetAttributeBool("allowatstart", false);
|
||||
PerRuin = element.GetAttributeBool("perruin", false);
|
||||
PerWreck = element.GetAttributeBool("perwreck", false);
|
||||
|
||||
Commonness[""] = 1.0f;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -63,7 +72,7 @@ namespace Barotrauma
|
||||
Commonness[""] = subElement.GetAttributeFloat("commonness", 0.0f);
|
||||
foreach (XElement overrideElement in subElement.Elements())
|
||||
{
|
||||
if (overrideElement.Name.ToString().ToLowerInvariant() == "override")
|
||||
if (overrideElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string levelType = overrideElement.GetAttributeString("leveltype", "");
|
||||
if (!Commonness.ContainsKey(levelType))
|
||||
@@ -116,7 +125,7 @@ namespace Barotrauma
|
||||
int i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
if (element.Name.ToString().ToLowerInvariant() != "eventset") { continue; }
|
||||
if (!element.Name.ToString().Equals("eventset", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
List.Add(new ScriptedEventSet(element, i.ToString()));
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -7,24 +7,23 @@ namespace Barotrauma.Extensions
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Randomizes the collection and returns it.
|
||||
/// Randomizes the collection (using OrderBy) and returns it.
|
||||
/// </summary>
|
||||
public static IOrderedEnumerable<T> Randomize<T>(this IEnumerable<T> source)
|
||||
public static IOrderedEnumerable<T> Randomize<T>(this IEnumerable<T> source, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
return source.OrderBy(i => Rand.Value());
|
||||
return source.OrderBy(i => Rand.Value(randSync));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes the list in place.
|
||||
/// Randomizes the list in place without creating a new collection, using a Fisher-Yates-based algorithm.
|
||||
/// </summary>
|
||||
public static void RandomizeList<T>(this List<T> list)
|
||||
public static void Shuffle<T>(this IList<T> list, Rand.RandSync randSync = Rand.RandSync.Unsynced)
|
||||
{
|
||||
//Fisher-Yates shuffle
|
||||
int n = list.Count;
|
||||
while (n > 1)
|
||||
{
|
||||
n--;
|
||||
int k = Rand.Int(n + 1);
|
||||
int k = Rand.Int(n + 1, randSync);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
|
||||
@@ -57,5 +57,43 @@ namespace Barotrauma.Extensions
|
||||
var size = rect.MultiplySize(scale);
|
||||
return new Rectangle(rect.X, rect.Y, size.X, size.Y);
|
||||
}
|
||||
|
||||
public static bool IntersectsWorld(this Rectangle rect, Rectangle value)
|
||||
{
|
||||
int bottom = rect.Y - rect.Height;
|
||||
int otherBottom = value.Y - value.Height;
|
||||
return value.Left < rect.Right && rect.Left < value.Right &&
|
||||
value.Top > bottom && rect.Top > otherBottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like the XNA method, but treats the y-coordinate so that up is greater and down is lower.
|
||||
/// </summary>
|
||||
public static bool ContainsWorld(this Rectangle rect, Rectangle other)
|
||||
{
|
||||
return
|
||||
(rect.X <= other.X) && ((other.X + other.Width) <= (rect.X + rect.Width)) &&
|
||||
(rect.Y >= other.Y) && ((other.Y - other.Height) >= (rect.Y - rect.Height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like the XNA method, but treats the y-coordinate so that up is greater and down is lower.
|
||||
/// </summary>
|
||||
public static bool ContainsWorld(this Rectangle rect, Vector2 point)
|
||||
{
|
||||
return
|
||||
(rect.X <= point.X) && (point.X < (rect.X + rect.Width)) &&
|
||||
(rect.Y >= point.Y) && (point.Y > (rect.Y - rect.Height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like the XNA method, but treats the y-coordinate so that up is greater and down is lower.
|
||||
/// </summary>
|
||||
public static bool ContainsWorld(this Rectangle rect, Point point)
|
||||
{
|
||||
return
|
||||
(rect.X <= point.X) && (point.X < (rect.X + rect.Width)) &&
|
||||
(rect.Y >= point.Y) && (point.Y > (rect.Y - rect.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -42,7 +43,7 @@ namespace Barotrauma
|
||||
public float GetAverageElapsedMillisecs(string identifier)
|
||||
{
|
||||
if (!avgTicksPerFrame.ContainsKey(identifier)) return 0.0f;
|
||||
return avgTicksPerFrame[identifier] / (float)TimeSpan.TicksPerMillisecond;
|
||||
return avgTicksPerFrame[identifier] * 1000.0f / (float)Stopwatch.Frequency;
|
||||
}
|
||||
|
||||
public bool Update(double deltaTime)
|
||||
|
||||
@@ -23,11 +23,18 @@ namespace Barotrauma
|
||||
{
|
||||
if (Submarine.MainSubs[i] == null) { continue; }
|
||||
List<Submarine> subs = new List<Submarine>() { Submarine.MainSubs[i] };
|
||||
subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.IsOutpost));
|
||||
subs.AddRange(Submarine.MainSubs[i].DockedTo.Where(d => !d.Info.IsOutpost));
|
||||
Place(subs);
|
||||
}
|
||||
if (campaign != null) { campaign.InitialSuppliesSpawned = true; }
|
||||
}
|
||||
}
|
||||
foreach (var wreck in Submarine.Loaded)
|
||||
{
|
||||
if (wreck.Info.IsWreck)
|
||||
{
|
||||
Place(wreck.ToEnumerable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Place(IEnumerable<Submarine> subs)
|
||||
@@ -38,10 +45,10 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int sizeApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(100);
|
||||
var prefabsWithContainer = new List<ItemPrefab>(sizeApprox / 3);
|
||||
var prefabsWithoutContainer = new List<ItemPrefab>(sizeApprox);
|
||||
int itemCountApprox = MapEntityPrefab.List.Count() / 3;
|
||||
var containers = new List<ItemContainer>(70 + 30 * subs.Count());
|
||||
var prefabsWithContainer = new List<ItemPrefab>(itemCountApprox / 3);
|
||||
var prefabsWithoutContainer = new List<ItemPrefab>(itemCountApprox);
|
||||
var removals = new List<ItemPrefab>();
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
@@ -49,6 +56,7 @@ namespace Barotrauma
|
||||
if (!subs.Contains(item.Submarine)) { continue; }
|
||||
containers.AddRange(item.GetComponents<ItemContainer>());
|
||||
}
|
||||
containers.Shuffle();
|
||||
|
||||
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
|
||||
{
|
||||
@@ -66,7 +74,7 @@ namespace Barotrauma
|
||||
|
||||
spawnedItems.Clear();
|
||||
var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
|
||||
prefabsWithContainer.RandomizeList();
|
||||
prefabsWithContainer.Shuffle();
|
||||
// Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
|
||||
for (int i = 0; i < prefabsWithContainer.Count; i++)
|
||||
{
|
||||
@@ -82,12 +90,13 @@ namespace Barotrauma
|
||||
// Another pass for items with containers because also they can spawn inside other items (like smg magazine)
|
||||
prefabsWithContainer.ForEach(i => SpawnItems(i));
|
||||
// Spawn items that don't have containers last
|
||||
prefabsWithoutContainer.RandomizeList();
|
||||
prefabsWithoutContainer.Shuffle();
|
||||
prefabsWithoutContainer.ForEach(i => SpawnItems(i));
|
||||
|
||||
if (OutputDebugInfo)
|
||||
{
|
||||
DebugConsole.NewMessage("Automatically placed items: ");
|
||||
var subNames = subs.Select(s => s.Info.Name).ToList();
|
||||
DebugConsole.NewMessage($"Automatically placed items in { string.Join(", ", subNames) }:");
|
||||
foreach (string itemName in spawnedItems.Select(it => it.Name).Distinct())
|
||||
{
|
||||
DebugConsole.NewMessage(" - " + itemName + " x" + spawnedItems.Count(it => it.Name == itemName));
|
||||
@@ -149,7 +158,12 @@ namespace Barotrauma
|
||||
private static bool SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
bool success = false;
|
||||
if (Rand.Value() > validContainer.Value.SpawnProbability) { return success; }
|
||||
if (Rand.Value() > validContainer.Value.SpawnProbability) { return false; }
|
||||
// Don't add dangerously reactive materials in thalamus wrecks
|
||||
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -20,8 +21,9 @@ namespace Barotrauma
|
||||
|
||||
class CargoManager
|
||||
{
|
||||
private readonly List<PurchasedItem> purchasedItems;
|
||||
public const int MaxQuantity = 100;
|
||||
|
||||
private readonly List<PurchasedItem> purchasedItems;
|
||||
private readonly CampaignMode campaign;
|
||||
|
||||
public Action OnItemsChanged;
|
||||
@@ -115,9 +117,25 @@ namespace Barotrauma
|
||||
ItemPrefab containerPrefab = null;
|
||||
foreach (PurchasedItem pi in itemsToSpawn)
|
||||
{
|
||||
float floorPos = cargoRoom.Rect.Y - cargoRoom.Rect.Height;
|
||||
|
||||
Vector2 position = new Vector2(
|
||||
Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + pi.ItemPrefab.Size.Y / 2);
|
||||
floorPos);
|
||||
|
||||
//check where the actual floor structure is in case the bottom of the hull extends below it
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(new Vector2(position.X, cargoRoom.Rect.Y - cargoRoom.Rect.Height / 2)),
|
||||
ConvertUnits.ToSimUnits(position),
|
||||
collisionCategory: Physics.CollisionWall) != null)
|
||||
{
|
||||
float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y);
|
||||
if (floorStructurePos > floorPos)
|
||||
{
|
||||
floorPos = floorStructurePos;
|
||||
}
|
||||
}
|
||||
position.Y = floorPos + pi.ItemPrefab.Size.Y / 2;
|
||||
|
||||
ItemContainer itemContainer = null;
|
||||
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Barotrauma
|
||||
{
|
||||
const float ConversationIntervalMin = 100.0f;
|
||||
const float ConversationIntervalMax = 180.0f;
|
||||
const float ConversationIntervalMultiplierMultiplayer = 5.0f;
|
||||
private float conversationTimer, conversationLineTimer;
|
||||
private List<Pair<Character, string>> pendingConversationLines = new List<Pair<Character, string>>();
|
||||
|
||||
@@ -74,11 +75,17 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateConversations(float deltaTime)
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.ServerSettings.DisableBotConversations) { return; }
|
||||
|
||||
conversationTimer -= deltaTime;
|
||||
if (conversationTimer <= 0.0f)
|
||||
{
|
||||
CreateRandomConversation();
|
||||
conversationTimer = Rand.Range(ConversationIntervalMin, ConversationIntervalMax);
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
conversationTimer *= ConversationIntervalMultiplierMultiplayer;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingConversationLines.Count > 0)
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
return Submarine.Loaded.FindAll(s =>
|
||||
s != leavingSub &&
|
||||
!leavingSub.DockedTo.Contains(s) &&
|
||||
s != Level.Loaded.StartOutpost && s != Level.Loaded.EndOutpost &&
|
||||
s.Info.Type == SubmarineInfo.SubmarineType.Player &&
|
||||
(s.AtEndPosition != leavingSub.AtEndPosition || s.AtStartPosition != leavingSub.AtStartPosition));
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine == null || wall.Submarine.IsOutpost) { continue; }
|
||||
if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine))
|
||||
{
|
||||
for (int i = 0; i < wall.SectionCount; i++)
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null || item.Submarine.IsOutpost) { continue; }
|
||||
if (item.Submarine == null || item.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine))
|
||||
{
|
||||
if (item.GetComponent<Items.Components.Repairable>() != null)
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Barotrauma
|
||||
{
|
||||
#if CLIENT
|
||||
new GameModePreset("singleplayercampaign", typeof(SinglePlayerCampaign), true);
|
||||
new GameModePreset("subtest", typeof(SubTestMode), true);
|
||||
new GameModePreset("tutorial", typeof(TutorialMode), true);
|
||||
new GameModePreset("devsandbox", typeof(GameMode), true);
|
||||
#endif
|
||||
|
||||
+13
-7
@@ -69,9 +69,18 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
bool success =
|
||||
GameMain.Client.ConnectedClients.Any(c => c.Character != null && !c.Character.IsDead);
|
||||
|
||||
GameMain.GameSession.EndRound("");
|
||||
GameMain.GameSession.CrewManager.EndRound();
|
||||
return;
|
||||
|
||||
if (success)
|
||||
{
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -104,17 +113,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (c.Character?.Info != null && !c.Character.IsDead)
|
||||
{
|
||||
c.Character.ResetCurrentOrder();
|
||||
c.CharacterInfo = c.Character.Info;
|
||||
characterData.Add(new CharacterCampaignData(c));
|
||||
}
|
||||
}
|
||||
|
||||
//remove all items that are in someone's inventory
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
c.Inventory?.DeleteAllItems();
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
bool atEndPosition = Submarine.MainSub.AtEndPosition;
|
||||
@@ -142,6 +146,8 @@ namespace Barotrauma
|
||||
}
|
||||
map.ProgressWorld();
|
||||
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class GameSession
|
||||
{
|
||||
public enum InfoFrameTab { Crew, Mission, MyCharacter, ManagePlayers };
|
||||
public enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor };
|
||||
|
||||
public readonly EventManager EventManager;
|
||||
|
||||
@@ -65,44 +65,51 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public SubmarineInfo SubmarineInfo { get; set; }
|
||||
|
||||
public Submarine Submarine { get; set; }
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public GameSession(Submarine submarine, string savePath, GameModePreset gameModePreset, MissionType missionType = MissionType.None)
|
||||
: this(submarine, savePath)
|
||||
public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, MissionType missionType = MissionType.None)
|
||||
: this(submarineInfo, savePath)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = gameModePreset.Instantiate(missionType);
|
||||
}
|
||||
|
||||
public GameSession(Submarine submarine, string savePath, GameModePreset gameModePreset, MissionPrefab missionPrefab)
|
||||
: this(submarine, savePath)
|
||||
public GameSession(SubmarineInfo submarineInfo, string savePath, GameModePreset gameModePreset, MissionPrefab missionPrefab)
|
||||
: this(submarineInfo, savePath)
|
||||
{
|
||||
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
|
||||
GameMode = gameModePreset.Instantiate(missionPrefab);
|
||||
|
||||
#if CLIENT
|
||||
if (GameMode is SubTestMode) { EventManager = null; }
|
||||
#endif
|
||||
}
|
||||
|
||||
private GameSession(Submarine submarine, string savePath)
|
||||
private GameSession(SubmarineInfo submarineInfo, string savePath)
|
||||
{
|
||||
InitProjSpecific();
|
||||
Submarine.MainSub = submarine;
|
||||
this.Submarine = submarine;
|
||||
SubmarineInfo = submarineInfo;
|
||||
/*Submarine = new Submarine(submarineInfo);
|
||||
Submarine.MainSub = Submarine;*/
|
||||
GameMain.GameSession = this;
|
||||
EventManager = new EventManager();
|
||||
this.SavePath = savePath;
|
||||
}
|
||||
|
||||
|
||||
public GameSession(Submarine selectedSub, string saveFile, XDocument doc)
|
||||
: this(selectedSub, saveFile)
|
||||
public GameSession(SubmarineInfo selectedSubInfo, string saveFile, XDocument doc)
|
||||
: this(selectedSubInfo, saveFile)
|
||||
{
|
||||
Submarine.MainSub = Submarine;
|
||||
|
||||
GameMain.GameSession = this;
|
||||
selectedSub.Name = doc.Root.GetAttributeString("submarine", selectedSub.Name);
|
||||
//selectedSub.Name = doc.Root.GetAttributeString("submarine", selectedSub.Name);
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
@@ -154,10 +161,10 @@ namespace Barotrauma
|
||||
{
|
||||
Level randomLevel = Level.CreateRandom(levelSeed, difficulty);
|
||||
|
||||
StartRound(randomLevel, true);
|
||||
StartRound(randomLevel);
|
||||
}
|
||||
|
||||
public void StartRound(Level level, bool reloadSub = true, bool mirrorLevel = false)
|
||||
public void StartRound(Level level, bool mirrorLevel = false)
|
||||
{
|
||||
//make sure no status effects have been carried on from the next round
|
||||
//(they should be stopped in EndRound, this is a safeguard against cases where the round is ended ungracefully)
|
||||
@@ -169,33 +176,26 @@ namespace Barotrauma
|
||||
#endif
|
||||
this.Level = level;
|
||||
|
||||
if (Submarine == null)
|
||||
if (SubmarineInfo == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine not selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadSub || Submarine.MainSub != Submarine) { Submarine.Load(true); }
|
||||
Submarine.MainSub = Submarine;
|
||||
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1)
|
||||
{
|
||||
if (Submarine.MainSubs[1] == null)
|
||||
{
|
||||
Submarine.MainSubs[1] = new Submarine(Submarine.MainSub.FilePath, Submarine.MainSub.MD5Hash.Hash, true);
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
else if (reloadSub)
|
||||
{
|
||||
Submarine.MainSubs[1].Load(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (Submarine.IsFileCorrupted)
|
||||
if (SubmarineInfo.IsFileCorrupted)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't start game session, submarine file corrupted.");
|
||||
return;
|
||||
}
|
||||
|
||||
Submarine.Unload();
|
||||
Submarine = Submarine.MainSub = new Submarine(SubmarineInfo);
|
||||
Submarine.MainSub = Submarine;
|
||||
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1 && Submarine.MainSubs[1] == null)
|
||||
{
|
||||
Submarine.MainSubs[1] = new Submarine(SubmarineInfo, true);
|
||||
}
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
level.Generate(mirrorLevel);
|
||||
@@ -231,7 +231,7 @@ namespace Barotrauma
|
||||
if (port.Item.WorldPosition.Y < Submarine.WorldPosition.Y) { continue; }
|
||||
|
||||
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, level.StartOutpost.WorldPosition);
|
||||
if (myPort == null || dist < closestDistance)
|
||||
if (myPort == null || dist < closestDistance || (port.MainDockingPort && !myPort.MainDockingPort))
|
||||
{
|
||||
myPort = port;
|
||||
closestDistance = dist;
|
||||
@@ -254,14 +254,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (var sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.IsOutpost)
|
||||
if (sub.Info.IsOutpost)
|
||||
{
|
||||
sub.DisableObstructedWayPoints();
|
||||
}
|
||||
}
|
||||
|
||||
Entity.Spawner = new EntitySpawner();
|
||||
|
||||
|
||||
if (GameMode.Mission != null) { Mission = GameMode.Mission; }
|
||||
if (GameMode != null) { GameMode.Start(); }
|
||||
if (GameMode.Mission != null)
|
||||
@@ -277,7 +277,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
EventManager.StartRound(level);
|
||||
EventManager?.StartRound(level);
|
||||
SteamAchievementManager.OnStartRound();
|
||||
|
||||
if (GameMode != null)
|
||||
@@ -286,8 +286,9 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
//only autoplace items here in single player
|
||||
//only place items and corpses here in single player
|
||||
//the server does this after loading the respawn shuttle
|
||||
Level?.SpawnCorpses();
|
||||
AutoItemPlacer.PlaceIfNeeded(GameMode);
|
||||
}
|
||||
if (GameMode is MultiPlayerCampaign mpCampaign && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
@@ -296,18 +297,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Name);
|
||||
GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level.Seed));
|
||||
GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Info.Name);
|
||||
GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(level?.Seed ?? "[NO_LEVEL]"));
|
||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start,
|
||||
GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString()));
|
||||
|
||||
#if CLIENT
|
||||
if (GameMode is SinglePlayerCampaign) { SteamAchievementManager.OnBiomeDiscovered(level.Biome); }
|
||||
RoundSummary = new RoundSummary(this);
|
||||
if (!(GameMode is SubTestMode)) { RoundSummary = new RoundSummary(this); }
|
||||
|
||||
GameMain.GameScreen.ColorFade(Color.Black, Color.TransparentBlack, 5.0f);
|
||||
|
||||
if (!(GameMode is TutorialMode))
|
||||
if (!(GameMode is TutorialMode) && !(GameMode is SubTestMode))
|
||||
{
|
||||
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
||||
GUI.AddMessage(level.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, level.Difficulty / 100.0f), 5.0f, playSound: false);
|
||||
@@ -322,7 +323,7 @@ namespace Barotrauma
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
EventManager.Update(deltaTime);
|
||||
EventManager?.Update(deltaTime);
|
||||
GameMode?.Update(deltaTime);
|
||||
Mission?.Update(deltaTime);
|
||||
|
||||
@@ -350,9 +351,11 @@ namespace Barotrauma
|
||||
OnClicked = (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; }
|
||||
};
|
||||
}
|
||||
|
||||
TabMenu.OnRoundEnded();
|
||||
#endif
|
||||
|
||||
EventManager.EndRound();
|
||||
EventManager?.EndRound();
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
Mission = null;
|
||||
@@ -451,7 +454,7 @@ namespace Barotrauma
|
||||
XDocument doc = new XDocument(new XElement("Gamesession"));
|
||||
|
||||
doc.Root.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal));
|
||||
doc.Root.Add(new XAttribute("submarine", Submarine == null ? "" : Submarine.Name));
|
||||
doc.Root.Add(new XAttribute("submarine", SubmarineInfo == null ? "" : SubmarineInfo.Name));
|
||||
doc.Root.Add(new XAttribute("mapseed", Map.Seed));
|
||||
doc.Root.Add(new XAttribute("selectedcontentpackages",
|
||||
string.Join("|", GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).Select(cp => cp.Path))));
|
||||
|
||||
@@ -27,10 +27,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public partial class GameSettings
|
||||
{
|
||||
const string savePath = "config.xml";
|
||||
const string playerSavePath = "config_player.xml";
|
||||
const string vanillaContentPackagePath = "Data/ContentPackages/Vanilla";
|
||||
{
|
||||
public const string SavePath = "config.xml";
|
||||
public const string PlayerSavePath = "config_player.xml";
|
||||
public const string VanillaContentPackagePath = "Data/ContentPackages/Vanilla";
|
||||
|
||||
public int GraphicsWidth { get; set; }
|
||||
public int GraphicsHeight { get; set; }
|
||||
@@ -91,6 +91,7 @@ namespace Barotrauma
|
||||
set { /*do nothing*/ }
|
||||
}
|
||||
#endif
|
||||
public bool UseDualModeSockets { get; set; } = true;
|
||||
|
||||
public bool AutoUpdateWorkshopItems;
|
||||
|
||||
@@ -117,6 +118,19 @@ namespace Barotrauma
|
||||
set { jobPreferences = value; }
|
||||
}
|
||||
|
||||
public bool AreJobPreferencesEqual(List<Pair<string, int>> compareTo)
|
||||
{
|
||||
if (jobPreferences == null || compareTo == null) return false;
|
||||
if (jobPreferences.Count != compareTo.Count) return false;
|
||||
|
||||
for (int i = 0; i < jobPreferences.Count; i++)
|
||||
{
|
||||
if (jobPreferences[i].First != compareTo[i].First || jobPreferences[i].Second != compareTo[i].Second) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int CharacterHeadIndex { get; set; }
|
||||
public int CharacterHairIndex { get; set; }
|
||||
public int CharacterBeardIndex { get; set; }
|
||||
@@ -138,6 +152,13 @@ namespace Barotrauma
|
||||
public bool CrewMenuOpen { get; set; } = true;
|
||||
public bool ChatOpen { get; set; } = true;
|
||||
|
||||
public float CorpseDespawnDelay { get; set; } = 10.0f * 60.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How many corpses there can be in a sub before they start to get despawned
|
||||
/// </summary>
|
||||
public int CorpsesPerSubDespawnThreshold { get; set; } = 5;
|
||||
|
||||
private string overrideSaveFolder, overrideMultiplayerSaveFolder;
|
||||
|
||||
private bool unsavedSettings;
|
||||
@@ -196,13 +217,20 @@ namespace Barotrauma
|
||||
get { return voiceChatVolume; }
|
||||
set
|
||||
{
|
||||
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
voiceChatVolume = MathHelper.Clamp(value, 0.0f, 2.0f);
|
||||
#if CLIENT
|
||||
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", voiceChatVolume * 30.0f, 0);
|
||||
GameMain.SoundManager?.SetCategoryGainMultiplier("voip", Math.Min(voiceChatVolume, 1.0f), 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int VoiceChatCutoffPrevention
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public const float MaxMicrophoneVolume = 10.0f;
|
||||
public float MicrophoneVolume
|
||||
{
|
||||
@@ -236,6 +264,7 @@ namespace Barotrauma
|
||||
|
||||
#if DEBUG
|
||||
public bool AutomaticQuickStartEnabled { get; set; }
|
||||
public bool TextManagerDebugModeEnabled { get; set; }
|
||||
#endif
|
||||
|
||||
private FileSystemWatcher modsFolderWatcher;
|
||||
@@ -311,7 +340,7 @@ namespace Barotrauma
|
||||
ref shouldRefreshAfflictions);
|
||||
|
||||
if (shouldRefreshAfflictions) { AfflictionPrefab.LoadAll(GameMain.Instance.GetFilesOfType(ContentType.Afflictions)); }
|
||||
if (shouldRefreshSubs) { Submarine.RefreshSavedSubs(); }
|
||||
if (shouldRefreshSubs) { SubmarineInfo.RefreshSavedSubs(); }
|
||||
if (shouldRefreshFabricationRecipes) { ItemPrefab.InitFabricationRecipes(); }
|
||||
if (shouldRefreshRuinGenerationParams) { RuinGeneration.RuinGenerationParams.ClearAll(); }
|
||||
if (shouldRefreshScriptedEventSets) { ScriptedEventSet.LoadPrefabs(); }
|
||||
@@ -359,7 +388,7 @@ namespace Barotrauma
|
||||
ref shouldRefreshAfflictions);
|
||||
|
||||
if (shouldRefreshAfflictions) { AfflictionPrefab.LoadAll(GameMain.Instance.GetFilesOfType(ContentType.Afflictions)); }
|
||||
if (shouldRefreshSubs) { Submarine.RefreshSavedSubs(); }
|
||||
if (shouldRefreshSubs) { SubmarineInfo.RefreshSavedSubs(); }
|
||||
if (shouldRefreshFabricationRecipes) { ItemPrefab.InitFabricationRecipes(); }
|
||||
if (shouldRefreshRuinGenerationParams) { RuinGeneration.RuinGenerationParams.ClearAll(); }
|
||||
if (shouldRefreshScriptedEventSets) { ScriptedEventSet.LoadPrefabs(); }
|
||||
@@ -408,7 +437,7 @@ namespace Barotrauma
|
||||
ref shouldRefreshAfflictions);
|
||||
|
||||
if (shouldRefreshAfflictions) { AfflictionPrefab.LoadAll(GameMain.Instance.GetFilesOfType(ContentType.Afflictions)); }
|
||||
if (shouldRefreshSubs) { Submarine.RefreshSavedSubs(); }
|
||||
if (shouldRefreshSubs) { SubmarineInfo.RefreshSavedSubs(); }
|
||||
if (shouldRefreshFabricationRecipes) { ItemPrefab.InitFabricationRecipes(); }
|
||||
if (shouldRefreshRuinGenerationParams) { RuinGeneration.RuinGenerationParams.ClearAll(); }
|
||||
if (shouldRefreshScriptedEventSets) { ScriptedEventSet.LoadPrefabs(); }
|
||||
@@ -496,10 +525,10 @@ namespace Barotrauma
|
||||
shouldRefreshSoundPlayer = true;
|
||||
break;
|
||||
case ContentType.Particles:
|
||||
GameMain.ParticleManager.LoadPrefabsFromFile(file);
|
||||
GameMain.ParticleManager?.LoadPrefabsFromFile(file);
|
||||
break;
|
||||
case ContentType.Decals:
|
||||
GameMain.DecalManager.LoadFromFile(file);
|
||||
GameMain.DecalManager?.LoadFromFile(file);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
@@ -579,10 +608,10 @@ namespace Barotrauma
|
||||
shouldRefreshSoundPlayer = true;
|
||||
break;
|
||||
case ContentType.Particles:
|
||||
GameMain.ParticleManager.RemovePrefabsByFile(file.Path);
|
||||
GameMain.ParticleManager?.RemovePrefabsByFile(file.Path);
|
||||
break;
|
||||
case ContentType.Decals:
|
||||
GameMain.DecalManager.RemoveByFile(file.Path);
|
||||
GameMain.DecalManager?.RemoveByFile(file.Path);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
@@ -615,6 +644,7 @@ namespace Barotrauma
|
||||
case ContentType.Particles:
|
||||
case ContentType.Decals:
|
||||
case ContentType.Outpost:
|
||||
case ContentType.Wreck:
|
||||
case ContentType.BackgroundCreaturePrefabs:
|
||||
case ContentType.ServerExecutable:
|
||||
case ContentType.None:
|
||||
@@ -643,7 +673,7 @@ namespace Barotrauma
|
||||
ItemAssemblyPrefab.Prefabs.SortAll();
|
||||
StructurePrefab.Prefabs.SortAll();
|
||||
|
||||
Submarine.RefreshSavedSubs();
|
||||
SubmarineInfo.RefreshSavedSubs();
|
||||
ItemPrefab.InitFabricationRecipes();
|
||||
RuinGeneration.RuinGenerationParams.ClearAll();
|
||||
ScriptedEventSet.LoadPrefabs();
|
||||
@@ -724,6 +754,10 @@ namespace Barotrauma
|
||||
public bool ShowLanguageSelectionPrompt { get; set; }
|
||||
|
||||
private bool showTutorialSkipWarning = true;
|
||||
|
||||
public static bool EnableSubmarineAutoSave { get; set; }
|
||||
public static Color SubEditorBackgroundColor { get; set; }
|
||||
|
||||
public bool ShowTutorialSkipWarning
|
||||
{
|
||||
get { return showTutorialSkipWarning && CompletedTutorialNames.Count == 0; }
|
||||
@@ -757,7 +791,7 @@ namespace Barotrauma
|
||||
|
||||
private void OnModFolderUpdate(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (SuppressModFolderWatcher || !(GameMain.NetworkMember?.IsClient ?? false)) { return; }
|
||||
if (SuppressModFolderWatcher || (GameMain.NetworkMember?.IsClient ?? false)) { return; }
|
||||
switch (e.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Created:
|
||||
@@ -818,7 +852,7 @@ namespace Barotrauma
|
||||
|
||||
private void LoadDefaultConfig(bool setLanguage = true)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(savePath);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(SavePath);
|
||||
if (doc == null)
|
||||
{
|
||||
GraphicsWidth = 1024;
|
||||
@@ -875,8 +909,11 @@ namespace Barotrauma
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("microphonevolume", microphoneVolume),
|
||||
new XAttribute("voicechatvolume", voiceChatVolume),
|
||||
new XAttribute("voicechatcutoffprevention", VoiceChatCutoffPrevention),
|
||||
new XAttribute("verboselogging", VerboseLogging),
|
||||
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
|
||||
new XAttribute("submarineautosave", EnableSubmarineAutoSave),
|
||||
new XAttribute("subeditorbackground", XMLExtensions.ColorToString(SubEditorBackgroundColor)),
|
||||
new XAttribute("enablesplashscreen", EnableSplashScreen),
|
||||
new XAttribute("usesteammatchmaking", UseSteamMatchmaking),
|
||||
new XAttribute("quickstartsub", QuickStartSubmarineName),
|
||||
@@ -931,7 +968,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (ContentPackage contentPackage in SelectedContentPackages)
|
||||
{
|
||||
if (contentPackage.Path.Contains(vanillaContentPackagePath))
|
||||
if (contentPackage.Path.Contains(VanillaContentPackagePath))
|
||||
{
|
||||
doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path)));
|
||||
break;
|
||||
@@ -986,7 +1023,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
using (var writer = XmlWriter.Create(savePath, settings))
|
||||
using (var writer = XmlWriter.Create(SavePath, settings))
|
||||
{
|
||||
doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
@@ -1021,7 +1058,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private bool LoadPlayerConfigInternal()
|
||||
{
|
||||
XDocument doc = XMLExtensions.LoadXml(playerSavePath);
|
||||
XDocument doc = XMLExtensions.LoadXml(PlayerSavePath);
|
||||
if (doc == null || doc.Root == null)
|
||||
{
|
||||
ShowUserStatisticsPrompt = true;
|
||||
@@ -1187,6 +1224,8 @@ namespace Barotrauma
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("verboselogging", VerboseLogging),
|
||||
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
|
||||
new XAttribute("submarineautosave", EnableSubmarineAutoSave),
|
||||
new XAttribute("subeditorbackground", XMLExtensions.ColorToString(SubEditorBackgroundColor)),
|
||||
new XAttribute("enablesplashscreen", EnableSplashScreen),
|
||||
new XAttribute("usesteammatchmaking", UseSteamMatchmaking),
|
||||
new XAttribute("quickstartsub", QuickStartSubmarineName),
|
||||
@@ -1199,9 +1238,13 @@ namespace Barotrauma
|
||||
new XAttribute("crewmenuopen", CrewMenuOpen),
|
||||
new XAttribute("campaigndisclaimershown", CampaignDisclaimerShown),
|
||||
new XAttribute("editordisclaimershown", EditorDisclaimerShown),
|
||||
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning)
|
||||
new XAttribute("tutorialskipwarning", ShowTutorialSkipWarning),
|
||||
new XAttribute("corpsedespawndelay", CorpseDespawnDelay),
|
||||
new XAttribute("corpsespersubdespawnthreshold", CorpsesPerSubDespawnThreshold),
|
||||
new XAttribute("usedualmodesockets", UseDualModeSockets)
|
||||
#if DEBUG
|
||||
, new XAttribute("automaticquickstartenabled", AutomaticQuickStartEnabled)
|
||||
, new XAttribute("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled)
|
||||
#endif
|
||||
);
|
||||
|
||||
@@ -1250,6 +1293,7 @@ namespace Barotrauma
|
||||
new XAttribute("musicvolume", musicVolume),
|
||||
new XAttribute("soundvolume", soundVolume),
|
||||
new XAttribute("voicechatvolume", voiceChatVolume),
|
||||
new XAttribute("voicechatcutoffprevention", VoiceChatCutoffPrevention),
|
||||
new XAttribute("microphonevolume", microphoneVolume),
|
||||
new XAttribute("muteonfocuslost", MuteOnFocusLost),
|
||||
new XAttribute("dynamicrangecompressionenabled", DynamicRangeCompressionEnabled),
|
||||
@@ -1349,7 +1393,7 @@ namespace Barotrauma
|
||||
|
||||
try
|
||||
{
|
||||
using (var writer = XmlWriter.Create(playerSavePath, settings))
|
||||
using (var writer = XmlWriter.Create(PlayerSavePath, settings))
|
||||
{
|
||||
doc.WriteTo(writer);
|
||||
writer.Flush();
|
||||
@@ -1374,6 +1418,8 @@ namespace Barotrauma
|
||||
AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
|
||||
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", sendUserStatistics);
|
||||
QuickStartSubmarineName = doc.Root.GetAttributeString("quickstartsub", QuickStartSubmarineName);
|
||||
EnableSubmarineAutoSave = doc.Root.GetAttributeBool("submarineautosave", true);
|
||||
SubEditorBackgroundColor = doc.Root.GetAttributeColor("subeditorbackground", new Color(0.051f, 0.149f, 0.271f, 1.0f));
|
||||
UseSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", UseSteamMatchmaking);
|
||||
RequireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", RequireSteamAuthentication);
|
||||
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
|
||||
@@ -1382,11 +1428,15 @@ namespace Barotrauma
|
||||
EnableMouseLook = doc.Root.GetAttributeBool("enablemouselook", EnableMouseLook);
|
||||
CrewMenuOpen = doc.Root.GetAttributeBool("crewmenuopen", CrewMenuOpen);
|
||||
ChatOpen = doc.Root.GetAttributeBool("chatopen", ChatOpen);
|
||||
CorpseDespawnDelay = doc.Root.GetAttributeInt("corpsedespawndelay", 10 * 60);
|
||||
CorpsesPerSubDespawnThreshold = doc.Root.GetAttributeInt("corpsespersubdespawnthreshold", 5);
|
||||
CampaignDisclaimerShown = doc.Root.GetAttributeBool("campaigndisclaimershown", CampaignDisclaimerShown);
|
||||
EditorDisclaimerShown = doc.Root.GetAttributeBool("editordisclaimershown", EditorDisclaimerShown);
|
||||
ShowTutorialSkipWarning = doc.Root.GetAttributeBool("tutorialskipwarning", true);
|
||||
UseDualModeSockets = doc.Root.GetAttributeBool("usedualmodesockets", true);
|
||||
#if DEBUG
|
||||
AutomaticQuickStartEnabled = doc.Root.GetAttributeBool("automaticquickstartenabled", AutomaticQuickStartEnabled);
|
||||
TextManagerDebugModeEnabled = doc.Root.GetAttributeBool("textmanagerdebugmodeenabled", TextManagerDebugModeEnabled);
|
||||
#endif
|
||||
XElement gameplayElement = doc.Root.Element("gameplay");
|
||||
jobPreferences = new List<Pair<string, int>>();
|
||||
@@ -1472,6 +1522,7 @@ namespace Barotrauma
|
||||
DynamicRangeCompressionEnabled = audioSettings.GetAttributeBool("dynamicrangecompressionenabled", DynamicRangeCompressionEnabled);
|
||||
VoipAttenuationEnabled = audioSettings.GetAttributeBool("voipattenuationenabled", VoipAttenuationEnabled);
|
||||
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
|
||||
VoiceChatCutoffPrevention = audioSettings.GetAttributeInt("voicechatcutoffprevention", VoiceChatCutoffPrevention);
|
||||
MuteOnFocusLost = audioSettings.GetAttributeBool("muteonfocuslost", MuteOnFocusLost);
|
||||
|
||||
UseDirectionalVoiceChat = audioSettings.GetAttributeBool("usedirectionalvoicechat", UseDirectionalVoiceChat);
|
||||
@@ -1560,6 +1611,8 @@ namespace Barotrauma
|
||||
InventoryScale = 1;
|
||||
AutoUpdateWorkshopItems = true;
|
||||
CampaignDisclaimerShown = false;
|
||||
CorpseDespawnDelay = 10 * 60;
|
||||
CorpsesPerSubDespawnThreshold = 5;
|
||||
if (resetLanguage)
|
||||
{
|
||||
Language = "English";
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
SelectNextCharacter,
|
||||
SelectPreviousCharacter,
|
||||
Voice,
|
||||
LocalVoice,
|
||||
Deselect,
|
||||
Shoot,
|
||||
Command,
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "item") continue;
|
||||
if (!subElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
ItemPrefab itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
|
||||
@@ -58,6 +58,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "If set to true, this docking port is used when spawning the submarine docked to an outpost (if possible).")]
|
||||
public bool MainDockingPort
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public DockingPort DockingTarget { get; private set; }
|
||||
|
||||
public bool Docked
|
||||
@@ -234,12 +241,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.IsOutpost)
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.IsOutpost)
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
@@ -873,12 +880,12 @@ namespace Barotrauma.Items.Components
|
||||
float closestDist = 30.0f * 30.0f;
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != item.Submarine) continue;
|
||||
if (it.Submarine != item.Submarine) { continue; }
|
||||
|
||||
var doorComponent = it.GetComponent<Door>();
|
||||
if (doorComponent == null) continue;
|
||||
if (doorComponent == null || doorComponent.IsHorizontal == IsHorizontal) { continue; }
|
||||
|
||||
float distSqr = Vector2.Distance(item.Position, it.Position);
|
||||
float distSqr = Vector2.DistanceSquared(item.Position, it.Position);
|
||||
if (distSqr < closestDist)
|
||||
{
|
||||
door = doorComponent;
|
||||
@@ -951,12 +958,12 @@ namespace Barotrauma.Items.Components
|
||||
if (docked)
|
||||
{
|
||||
if (item.Submarine != null && DockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + DockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + " docked " + item.Submarine.Info.Name + " to " + DockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null)
|
||||
GameServer.Log(sender.LogName + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + " undocked " + item.Submarine.Info.Name + " from " + prevDockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -594,7 +594,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (sender != null && wasOpen != isOpen)
|
||||
{
|
||||
GameServer.Log(sender.LogName + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(sender) + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
|
||||
#if SERVER
|
||||
if (!alreadyEquipped) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
if (!alreadyEquipped) GameServer.Log(GameServer.CharacterLogName(character) + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
picker.DeselectItem(item);
|
||||
#if SERVER
|
||||
GameServer.Log(character.LogName + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(character) + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
item.body.PhysEnabled = true;
|
||||
@@ -419,7 +419,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
if (picker != null)
|
||||
{
|
||||
GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FarseerPhysics;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -54,7 +55,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") { continue; }
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
|
||||
}
|
||||
item.IsShootable = true;
|
||||
@@ -310,70 +311,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attack != null)
|
||||
{
|
||||
if (targetLimb == null && targetCharacter == null && targetStructure == null && (targetItem == null || ! targetItem.Prefab.DamagedByMeleeWeapons))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetLimb != null)
|
||||
{
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
targetCharacter.LastDamageSource = item;
|
||||
attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
|
||||
{
|
||||
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; }
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
{
|
||||
|
||||
GameMain.Server.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.ApplyStatusEffect,
|
||||
ActionType.OnUse,
|
||||
null, //itemcomponent
|
||||
targetCharacter.ID, targetLimb
|
||||
});
|
||||
|
||||
string logStr = picker?.LogName + " used " + item.Name;
|
||||
if (item.ContainedItems != null && item.ContainedItems.Any())
|
||||
{
|
||||
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
|
||||
}
|
||||
logStr += " on " + targetCharacter.LogName + ".";
|
||||
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: User);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(item);
|
||||
}
|
||||
impactQueue.Enqueue(f2);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -414,7 +352,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetStructure.Removed) { return; }
|
||||
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
|
||||
+58
-44
@@ -29,6 +29,13 @@ namespace Barotrauma.Items.Components
|
||||
set { reload = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
|
||||
public int ProjectileCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
@@ -110,55 +117,62 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
}
|
||||
|
||||
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
|
||||
if (projectile == null) { return true; }
|
||||
|
||||
float spread = GetSpread(character);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
rotation += spread * Rand.Range(-0.5f, 0.5f);
|
||||
|
||||
projectile.User = character;
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
projectile.IgnoredBodies = new List<Body>(limbBodies);
|
||||
|
||||
Vector2 projectilePos = item.SimPosition;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = barrelPos;
|
||||
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
|
||||
if (projectile == null) { return true; }
|
||||
|
||||
float spread = GetSpread(character);
|
||||
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
|
||||
rotation += spread * Rand.Range(-0.5f, 0.5f);
|
||||
|
||||
projectile.User = character;
|
||||
//add the limbs of the shooter to the list of bodies to be ignored
|
||||
//so that the player can't shoot himself
|
||||
projectile.IgnoredBodies = new List<Body>(limbBodies);
|
||||
|
||||
Vector2 projectilePos = item.SimPosition;
|
||||
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
|
||||
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
|
||||
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
|
||||
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
|
||||
{
|
||||
//no obstacles -> we can spawn the projectile at the barrel
|
||||
projectilePos = barrelPos;
|
||||
}
|
||||
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
|
||||
}
|
||||
|
||||
projectile.Item.body.ResetDynamics();
|
||||
projectile.Item.SetTransform(projectilePos, rotation);
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(item, projectile.Item);
|
||||
if (projectile.Item.Removed) { continue; }
|
||||
projectile.User = character;
|
||||
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
projectile.Item.SetTransform(projectilePos,
|
||||
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
|
||||
|
||||
item.RemoveContained(projectile.Item);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
//recoil
|
||||
item.body.ApplyLinearImpulse(
|
||||
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
}
|
||||
}
|
||||
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
|
||||
{
|
||||
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
|
||||
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
|
||||
}
|
||||
|
||||
projectile.Item.body.ResetDynamics();
|
||||
projectile.Item.SetTransform(projectilePos, rotation);
|
||||
|
||||
projectile.Use(deltaTime);
|
||||
if (projectile.Item.Removed) { return true; }
|
||||
projectile.User = character;
|
||||
|
||||
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
|
||||
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
projectile.Item.SetTransform(projectilePos,
|
||||
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
|
||||
|
||||
//recoil
|
||||
item.body.ApplyLinearImpulse(
|
||||
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
|
||||
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||
|
||||
LaunchProjSpecific();
|
||||
|
||||
item.RemoveContained(projectile.Item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -440,17 +440,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
{
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
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.Attached &&
|
||||
levelResource.requiredItems.Any() &&
|
||||
@@ -464,7 +454,22 @@ namespace Barotrauma.Items.Components
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!targetItem.Prefab.DamagedByRepairTools) { return false; }
|
||||
|
||||
targetItem.IsHighlighted = true;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
|
||||
|
||||
if (picker.IsUnconscious || picker.IsDead || !picker.AllowInput)
|
||||
if (picker.IsDead || !picker.AllowInput)
|
||||
{
|
||||
throwing = false;
|
||||
aim = false;
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!MathUtils.IsValid(throwVector)) { throwVector = Vector2.UnitY; }
|
||||
|
||||
#if SERVER
|
||||
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
Character thrower = picker;
|
||||
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected const float CorrectionDelay = 1.0f;
|
||||
protected CoroutineHandle delayedCorrectionCoroutine;
|
||||
protected float correctionTimer;
|
||||
|
||||
[Editable, Serialize(0.0f, false, description: "How long it takes to pick up the item (in seconds).")]
|
||||
public float PickingTime
|
||||
@@ -81,7 +80,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Action<bool> OnActiveStateChanged;
|
||||
|
||||
public float IsActiveTimer;
|
||||
public virtual bool IsActive
|
||||
{
|
||||
get { return isActive; }
|
||||
@@ -222,7 +220,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
@@ -632,7 +629,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only checks the id card(s). Much simpler and a bit different than HasRequiredItems.
|
||||
/// Only checks if any of the Picked requirements are matched (used for checking id card(s)). Much simpler and a bit different than HasRequiredItems.
|
||||
/// </summary>
|
||||
public bool HasAccess(Character character)
|
||||
{
|
||||
@@ -641,7 +638,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Item item in character.Inventory.Items)
|
||||
{
|
||||
if (item?.Prefab.Identifier == "idcard" && requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(item))))
|
||||
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -741,14 +738,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XAttribute attribute in componentElement.Attributes())
|
||||
{
|
||||
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
|
||||
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
|
||||
property.TrySetValue(this, attribute.Value);
|
||||
}
|
||||
ParseMsg();
|
||||
OverrideRequiredItems(componentElement);
|
||||
}
|
||||
|
||||
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); }
|
||||
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.Info.GameVersion); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -52,6 +52,9 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool AccessOnlyWhenBroken { get; set; }
|
||||
|
||||
[Serialize(5, false, description: "How many inventory slots the inventory has per row.")]
|
||||
public int SlotsPerRow { get; set; }
|
||||
|
||||
@@ -197,10 +200,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (item.Container != null) { return false; }
|
||||
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained && character.SelectedConstruction == null)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
@@ -218,6 +232,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained)
|
||||
{
|
||||
foreach (Item contained in Inventory.Items)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -209,7 +210,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
{
|
||||
if (this.user != character)
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -55,6 +55,15 @@ namespace Barotrauma.Items.Components
|
||||
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
|
||||
}
|
||||
|
||||
public float CurrentBrokenVolume
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item.ConditionPercentage > 10.0f) { return 0.0f; }
|
||||
return Math.Abs(targetForce / 100.0f) * (1.0f - item.ConditionPercentage / 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Engine(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
|
||||
if (subElement.Name.ToString().Equals("fabricableitem", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
|
||||
break;
|
||||
@@ -180,7 +180,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -64,11 +64,6 @@ namespace Barotrauma.Items.Components
|
||||
float hullPercentage = 0.0f;
|
||||
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
|
||||
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
|
||||
|
||||
if (pumpSpeedLockTimer <= 0.0f)
|
||||
{
|
||||
targetLevel = null;
|
||||
}
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
@@ -112,6 +107,7 @@ namespace Barotrauma.Items.Components
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
|
||||
{
|
||||
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
|
||||
targetLevel = null;
|
||||
pumpSpeedLockTimer = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -131,7 +127,7 @@ namespace Barotrauma.Items.Components
|
||||
if (GameMain.Client != null) { return false; }
|
||||
#endif
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "stoppumping")
|
||||
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
#if SERVER
|
||||
if (FlowPercentage > 0.0f)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -189,7 +190,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
|
||||
{
|
||||
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
|
||||
GameServer.Log(GameServer.CharacterLogName(lastUser) + " adjusted reactor settings: " +
|
||||
"Temperature: " + (int)(temperature * 100.0f) +
|
||||
", Fission rate: " + (int)targetFissionRate +
|
||||
", Turbine output: " + (int)targetTurbineOutput +
|
||||
@@ -651,6 +652,20 @@ namespace Barotrauma.Items.Components
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
case "set_fissionrate":
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
|
||||
{
|
||||
FissionRate = newFissionRate;
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
case "set_turbineoutput":
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
|
||||
{
|
||||
TurbineOutput = newTurbineOutput;
|
||||
unsentChanges = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,6 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
#region Docking
|
||||
public List<DockingPort> DockingSources = new List<DockingPort>();
|
||||
public DockingPort ActiveDockingSource, DockingTarget;
|
||||
private bool searchedConnectedDockingPort;
|
||||
|
||||
private bool dockingModeEnabled;
|
||||
@@ -200,7 +199,7 @@ namespace Barotrauma.Items.Components
|
||||
if (dockingConnection != null)
|
||||
{
|
||||
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection);
|
||||
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.IsOutpost));
|
||||
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.Info.IsOutpost));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -344,6 +343,7 @@ namespace Barotrauma.Items.Components
|
||||
autopilotRecalculatePathTimer = RecalculatePathInterval;
|
||||
}
|
||||
|
||||
if (steeringPath == null) { return; }
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
|
||||
|
||||
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
|
||||
@@ -475,6 +475,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
|
||||
Vector2 target;
|
||||
@@ -536,7 +538,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool aiDockingToggled;
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (objective.Override)
|
||||
@@ -572,7 +573,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigateback":
|
||||
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
@@ -586,7 +587,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigatetodestination":
|
||||
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (HasBeenTuned) { return true; }
|
||||
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
|
||||
if (string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
|
||||
{
|
||||
|
||||
@@ -140,6 +140,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class Projectile : ItemComponent
|
||||
partial class Projectile : ItemComponent, IServerSerializable
|
||||
{
|
||||
struct HitscanResult
|
||||
{
|
||||
@@ -32,11 +32,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public Fixture Fixture;
|
||||
public Vector2 Normal;
|
||||
public Vector2 LinearVelocity;
|
||||
|
||||
public Impact(Fixture fixture, Vector2 normal)
|
||||
public Impact(Fixture fixture, Vector2 normal, Vector2 velocity)
|
||||
{
|
||||
Fixture = fixture;
|
||||
Normal = normal;
|
||||
LinearVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +49,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//a duration during which the projectile won't drop from the body it's stuck to
|
||||
private const float PersistentStickJointDuration = 1.0f;
|
||||
|
||||
private float launchImpulse;
|
||||
|
||||
private PrismaticJoint stickJoint;
|
||||
private Body stickTarget;
|
||||
|
||||
private readonly Attack attack;
|
||||
|
||||
private Vector2 launchPos;
|
||||
|
||||
private readonly HashSet<Body> hits = new HashSet<Body>();
|
||||
|
||||
public List<Body> IgnoredBodies;
|
||||
|
||||
private Character user;
|
||||
@@ -70,14 +70,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Body> Hits
|
||||
{
|
||||
get { return hits; }
|
||||
}
|
||||
|
||||
private float persistentStickJointTimer;
|
||||
|
||||
[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; }
|
||||
}
|
||||
public float LaunchImpulse { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
|
||||
public float LaunchRotation
|
||||
@@ -100,6 +101,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "When set to true, the item won't fall of a target it's stuck to unless removed.")]
|
||||
public bool StickPermanently
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the item stick to the character it hits.")]
|
||||
public bool StickToCharacters
|
||||
{
|
||||
@@ -138,6 +146,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How many targets the projectile can hit before it stops.")]
|
||||
public int MaxTargetsToHit
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Should the item be deleted when it hits something.")]
|
||||
public bool RemoveOnHit
|
||||
{
|
||||
@@ -152,6 +167,17 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsStuckToTarget
|
||||
{
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
|
||||
public Projectile(Item item, XElement element)
|
||||
: base (item, element)
|
||||
{
|
||||
@@ -159,7 +185,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
attack = new Attack(subElement, item.Name + ", Projectile");
|
||||
}
|
||||
}
|
||||
@@ -203,7 +229,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Launch(launchDir * launchImpulse * item.body.Mass);
|
||||
Launch(launchDir * LaunchImpulse * item.body.Mass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +240,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void Launch(Vector2 impulse)
|
||||
{
|
||||
hits.Clear();
|
||||
MaxTargetsToHit = 2;
|
||||
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
|
||||
@@ -237,7 +266,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
stickTarget = null;
|
||||
StickTarget = null;
|
||||
GameMain.World.Remove(stickJoint);
|
||||
stickJoint = null;
|
||||
}
|
||||
@@ -265,6 +294,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//shooting indoors, do a hitscan outside as well
|
||||
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition));
|
||||
//also in the coordinate space of docked subs
|
||||
foreach (Submarine dockedSub in item.Submarine.DockedTo)
|
||||
{
|
||||
if (dockedSub == item.Submarine) { continue; }
|
||||
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition - dockedSub.SimPosition, rayEnd + item.Submarine.SimPosition - dockedSub.SimPosition));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -291,7 +326,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (HitscanResult h in hits)
|
||||
{
|
||||
item.body.SetTransform(h.Point, rotation);
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal))
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
hitSomething = true;
|
||||
break;
|
||||
@@ -325,7 +360,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return true; }
|
||||
if (fixture.Body.UserData is Item) { return true; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
|
||||
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
@@ -345,7 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
//ignore sensors and items
|
||||
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
|
||||
|
||||
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null) { return -1; }
|
||||
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
|
||||
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
|
||||
|
||||
//ignore everything else than characters, sub walls and level walls
|
||||
@@ -368,7 +403,7 @@ namespace Barotrauma.Items.Components
|
||||
while (impactQueue.Count > 0)
|
||||
{
|
||||
var impact = impactQueue.Dequeue();
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal);
|
||||
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
||||
}
|
||||
|
||||
if (item.body != null && item.body.FarseerBody.IsBullet)
|
||||
@@ -383,25 +418,31 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
if (persistentStickJointTimer > 0.0f)
|
||||
if (persistentStickJointTimer > 0.0f && !StickPermanently)
|
||||
{
|
||||
persistentStickJointTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
stickTarget = null;
|
||||
if (stickJoint != null)
|
||||
if (StickTargetRemoved() ||
|
||||
(!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)))
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(stickJoint))
|
||||
{
|
||||
GameMain.World.Remove(stickJoint);
|
||||
}
|
||||
stickJoint = null;
|
||||
Unstick();
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool StickTargetRemoved()
|
||||
{
|
||||
if (StickTarget == null) { return true; }
|
||||
if (StickTarget.UserData is Limb limb) { return limb.character.Removed; }
|
||||
if (StickTarget.UserData is Entity entity) { return entity.Removed; }
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,6 +450,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; return false; }
|
||||
if (IgnoredBodies.Contains(target.Body)) { return false; }
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (target.Body.UserData is Submarine sub)
|
||||
{
|
||||
Vector2 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
|
||||
@@ -424,6 +471,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -446,18 +494,23 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal));
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
|
||||
return true;
|
||||
hits.Add(target.Body);
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal)
|
||||
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
|
||||
{
|
||||
if (User != null && User.Removed) { User = null; }
|
||||
|
||||
if (IgnoredBodies.Contains(target.Body)) { return false; }
|
||||
|
||||
//ignore character colliders (the projectile only hits limbs)
|
||||
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
|
||||
{
|
||||
@@ -478,7 +531,6 @@ namespace Barotrauma.Items.Components
|
||||
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
|
||||
if (limb.IsSevered)
|
||||
{
|
||||
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -488,7 +540,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Item targetItem)
|
||||
{
|
||||
if (attack != null && targetItem.Prefab.DamagedByProjectiles)
|
||||
if (attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
|
||||
{
|
||||
attackResult = attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
}
|
||||
@@ -558,21 +610,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
|
||||
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
|
||||
IgnoredBodies.Clear();
|
||||
|
||||
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
}
|
||||
IgnoredBodies.Clear();
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles))
|
||||
{
|
||||
item.body.LinearVelocity *= 0.1f;
|
||||
}
|
||||
else if (Vector2.Dot(item.body.LinearVelocity, collisionNormal) < 0.0f &&
|
||||
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && target.Body.UserData is Limb) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
@@ -581,8 +642,24 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
(float)Math.Sin(item.body.Rotation));
|
||||
|
||||
StickToTarget(target.Body, dir);
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (target.Body.UserData is Structure structure && structure.Submarine != item.Submarine && structure.Submarine != null)
|
||||
{
|
||||
StickToTarget(structure.Submarine.PhysicsBody.FarseerBody, dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
StickToTarget(target.Body, dir);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
|
||||
return Hitscan;
|
||||
@@ -614,7 +691,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void StickToTarget(Body targetBody, Vector2 axis)
|
||||
{
|
||||
if (stickJoint != null) return;
|
||||
if (stickJoint != null) { return; }
|
||||
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
|
||||
{
|
||||
@@ -622,19 +699,39 @@ namespace Barotrauma.Items.Components
|
||||
MaxMotorForce = 30.0f,
|
||||
LimitEnabled = true
|
||||
};
|
||||
if (item.Sprite != null)
|
||||
|
||||
if (StickPermanently)
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f);
|
||||
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
|
||||
item.body.ResetDynamics();
|
||||
}
|
||||
else if (item.Sprite != null)
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f * item.Scale);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f * item.Scale);
|
||||
}
|
||||
|
||||
persistentStickJointTimer = PersistentStickJointDuration;
|
||||
stickTarget = targetBody;
|
||||
StickTarget = targetBody;
|
||||
GameMain.World.Add(stickJoint);
|
||||
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
private void Unstick()
|
||||
{
|
||||
StickTarget = null;
|
||||
if (stickJoint != null)
|
||||
{
|
||||
if (GameMain.World.JointList.Contains(stickJoint))
|
||||
{
|
||||
GameMain.World.Remove(stickJoint);
|
||||
}
|
||||
stickJoint = null;
|
||||
}
|
||||
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
if (stickJoint != null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -111,7 +112,7 @@ namespace Barotrauma.Items.Components
|
||||
element.GetAttributeString("name", "");
|
||||
|
||||
//backwards compatibility
|
||||
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().ToLowerInvariant() == "showrepairuithreshold");
|
||||
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase));
|
||||
if (showRepairUIAttribute != null)
|
||||
{
|
||||
float repairThreshold;
|
||||
@@ -130,7 +131,26 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if the character manages to succesfully repair the item
|
||||
/// </summary>
|
||||
public bool CheckCharacterSuccess(Character character)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
|
||||
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
|
||||
|
||||
// unpowered (electrical) items can be repaired without a risk of electrical shock
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
|
||||
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
|
||||
|
||||
if (Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(character)) { return true; }
|
||||
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool StartRepairing(Character character, FixActions action)
|
||||
{
|
||||
if (character == null || character.IsDead || action == FixActions.None)
|
||||
@@ -143,8 +163,15 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (CurrentFixer != character || currentFixerAction != action)
|
||||
{
|
||||
if (!CheckCharacterSuccess(character))
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
|
||||
return false;
|
||||
}
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#else
|
||||
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; }
|
||||
#endif
|
||||
CurrentFixer = character;
|
||||
CurrentFixerAction = action;
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Rope : ItemComponent, IServerSerializable
|
||||
{
|
||||
private Item source, target;
|
||||
|
||||
private float snapTimer;
|
||||
private const float SnapAnimDuration = 1.0f;
|
||||
|
||||
private float raycastTimer;
|
||||
private const float RayCastInterval = 0.2f;
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the projectile the rope is attached to.")]
|
||||
public float ProjectilePullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the target the rope is attached to.")]
|
||||
public float TargetPullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much force is applied to pull the source the rope is attached to.")]
|
||||
public float SourcePullForce
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, false, description: "How far the source item can be from the projectile until the rope breaks.")]
|
||||
public float MaxLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, false, description: "Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
|
||||
public bool SnapOnCollision
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool snapped;
|
||||
public bool Snapped
|
||||
{
|
||||
get { return snapped; }
|
||||
set
|
||||
{
|
||||
if (snapped == value) { return; }
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
snapped = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Rope(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
|
||||
public void Attach(Item source, Item target)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(source != null);
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (source == null || source.Removed || target == null || target.Removed)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Snapped)
|
||||
{
|
||||
snapTimer += deltaTime;
|
||||
if (snapTimer >= SnapAnimDuration)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 diff = target.WorldPosition - source.WorldPosition;
|
||||
if (diff.LengthSquared() > MaxLength * MaxLength)
|
||||
{
|
||||
Snapped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
|
||||
if (SnapOnCollision)
|
||||
{
|
||||
raycastTimer += deltaTime;
|
||||
if (raycastTimer > RayCastInterval)
|
||||
{
|
||||
if (Submarine.PickBody(ConvertUnits.ToSimUnits(source.WorldPosition), ConvertUnits.ToSimUnits(target.WorldPosition),
|
||||
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
var projectile = target?.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
foreach (Body body in projectile.Hits)
|
||||
{
|
||||
Submarine alreadyHitSub = null;
|
||||
if (body.UserData is Structure hitStructure)
|
||||
{
|
||||
alreadyHitSub = hitStructure.Submarine;
|
||||
}
|
||||
else if (body.UserData is Submarine hitSub)
|
||||
{
|
||||
alreadyHitSub = hitSub;
|
||||
}
|
||||
if (alreadyHitSub != null)
|
||||
{
|
||||
if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; }
|
||||
if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Submarine targetSub = target?.GetComponent<Projectile>()?.StickTarget?.UserData as Submarine ?? target.Submarine;
|
||||
|
||||
if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine != null)
|
||||
{
|
||||
if (mapEntity.Submarine == targetSub || mapEntity.Submarine == source.Submarine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (f.Body?.UserData is Submarine sub)
|
||||
{
|
||||
if (sub == targetSub || sub == source.Submarine)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}) != null)
|
||||
{
|
||||
Snapped = true;
|
||||
return;
|
||||
}
|
||||
raycastTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 forceDir = diff;
|
||||
if (forceDir.LengthSquared() > 0.01f)
|
||||
{
|
||||
forceDir = Vector2.Normalize(forceDir);
|
||||
}
|
||||
|
||||
if (Math.Abs(ProjectilePullForce) > 0.001f)
|
||||
{
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
projectile?.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
|
||||
}
|
||||
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
sourceBody.ApplyForce(forceDir * SourcePullForce);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
targetBody.ApplyForce(-forceDir * TargetPullForce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
if (Snapped)
|
||||
{
|
||||
snapTimer += deltaTime;
|
||||
if (snapTimer >= SnapAnimDuration)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsBody GetBodyToPull(Item target)
|
||||
{
|
||||
if (target.ParentInventory is CharacterInventory characterInventory &&
|
||||
characterInventory.Owner is Character ownerCharacter)
|
||||
{
|
||||
if (ownerCharacter.Removed) { return null; }
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
}
|
||||
var projectile = target.GetComponent<Projectile>();
|
||||
if (projectile != null)
|
||||
{
|
||||
if (projectile.StickTarget?.UserData is Structure structure)
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Submarine sub)
|
||||
{
|
||||
return sub?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget?.UserData is Character character)
|
||||
{
|
||||
return character.AnimController.Collider;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (target.body != null) { return target.body; }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (XElement subElement in item.Prefab.ConfigElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "connectionpanel") { continue; }
|
||||
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
foreach (XElement connectionElement in subElement.Elements())
|
||||
{
|
||||
|
||||
+25
-1
@@ -192,6 +192,8 @@ namespace Barotrauma.Items.Components
|
||||
public bool CheckCharacterSuccess(Character character)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
//no electrocution in sub editor
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
|
||||
|
||||
var powered = item.GetComponent<Powered>();
|
||||
if (powered != null)
|
||||
@@ -203,7 +205,7 @@ namespace Barotrauma.Items.Components
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) { return true; }
|
||||
|
||||
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -262,7 +264,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wire.OtherConnection(null) == null) //wire not connected to anything else
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
wire.Item.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
#else
|
||||
wire.Item.Drop(null);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +288,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (wire.OtherConnection(c) == null) //wire not connected to anything else
|
||||
{
|
||||
#if CLIENT
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
{
|
||||
wire.Item.Remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
wire.Item.Drop(null);
|
||||
}
|
||||
#else
|
||||
wire.Item.Drop(null);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+17
-2
@@ -12,6 +12,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string ConnectionName;
|
||||
public string PropertyName;
|
||||
public Connection Connection;
|
||||
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
|
||||
public string Label { get; set; }
|
||||
@@ -28,11 +29,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Label = element.GetAttributeString("text", "");
|
||||
ConnectionName = element.GetAttributeString("connection", "");
|
||||
PropertyName = element.GetAttributeString("propertyname", "").ToLowerInvariant();
|
||||
Signal = element.GetAttributeString("signal", "1");
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() == "statuseffect")
|
||||
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
|
||||
}
|
||||
@@ -89,6 +91,7 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "button":
|
||||
case "textbox":
|
||||
var button = new CustomInterfaceElement(subElement)
|
||||
{
|
||||
ContinuousSignal = false
|
||||
@@ -106,7 +109,7 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
if (string.IsNullOrEmpty(tickBox.Label))
|
||||
{
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(tickBox);
|
||||
break;
|
||||
@@ -168,6 +171,18 @@ namespace Barotrauma.Items.Components
|
||||
tickBoxElement.State = state;
|
||||
}
|
||||
|
||||
private void TextChanged(CustomInterfaceElement textElement, string text)
|
||||
{
|
||||
textElement.Signal = text;
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (e.SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
{
|
||||
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateProjSpecific();
|
||||
|
||||
+33
-23
@@ -15,13 +15,13 @@ namespace Barotrauma.Items.Components
|
||||
private float lightBrightness;
|
||||
private float blinkFrequency;
|
||||
private float range;
|
||||
private float flicker;
|
||||
private float flicker, flickerState;
|
||||
private bool castShadows;
|
||||
private bool drawBehindSubs;
|
||||
|
||||
private float blinkTimer;
|
||||
|
||||
private bool itemLoaded;
|
||||
private double lastToggleSignalTime;
|
||||
|
||||
public PhysicsBody ParentBody;
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
if (light != null) { light.Range = range; }
|
||||
#endif
|
||||
}
|
||||
@@ -78,13 +79,11 @@ namespace Barotrauma.Items.Components
|
||||
if (IsActive == value) { return; }
|
||||
|
||||
IsActive = value;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && itemLoaded) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
|
||||
[Editable, 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; }
|
||||
@@ -94,6 +93,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
|
||||
public float FlickerSpeed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
|
||||
public float BlinkFrequency
|
||||
{
|
||||
@@ -117,6 +123,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "If enabled, the component will ignore continuous signals received in the toggle input (i.e. a continuous signal will only toggle it once).")]
|
||||
public bool IgnoreContinuousToggle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -158,14 +171,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
itemLoaded = true;
|
||||
SetLightSourceState(IsActive, lightBrightness);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
@@ -219,7 +225,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(Voltage, 1.0f), 0.1f);
|
||||
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
|
||||
}
|
||||
|
||||
if (blinkFrequency > 0.0f)
|
||||
@@ -233,7 +239,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
|
||||
flickerState += deltaTime * FlickerSpeed;
|
||||
flickerState %= 255;
|
||||
float noise = PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * flicker;
|
||||
SetLightSourceState(true, lightBrightness * (1.0f - noise));
|
||||
}
|
||||
|
||||
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
|
||||
@@ -249,15 +258,21 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
IsActive = !IsActive;
|
||||
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
|
||||
{
|
||||
IsOn = !IsOn;
|
||||
}
|
||||
lastToggleSignalTime = Timing.TotalTime;
|
||||
break;
|
||||
case "set_state":
|
||||
IsActive = (signal != "0");
|
||||
IsOn = signal != "0";
|
||||
break;
|
||||
case "set_color":
|
||||
LightColor = XMLExtensions.ParseColor(signal, false);
|
||||
@@ -265,11 +280,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsOn);
|
||||
}
|
||||
|
||||
private void UpdateAITarget(AITarget target)
|
||||
{
|
||||
if (!IsActive) { return; }
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.")]
|
||||
|
||||
@@ -26,7 +26,13 @@ namespace Barotrauma.Items.Components
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = Math.Max(value, 0.0f); }
|
||||
set
|
||||
{
|
||||
range = Math.Max(value, 0.0f);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.")]
|
||||
@@ -39,6 +45,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serialize(false, false, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.")]
|
||||
public bool AllowCrossTeamCommunication
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[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
|
||||
@@ -84,7 +98,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (sender == null || sender.channel != channel) { return false; }
|
||||
|
||||
if (sender.TeamID != TeamID)
|
||||
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -97,6 +111,10 @@ namespace Barotrauma.Items.Components
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
chatMsgCooldown -= deltaTime;
|
||||
if (chatMsgCooldown <= 0.0f)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
|
||||
@@ -164,7 +182,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chatMsgSent) chatMsgCooldown = MinChatMessageInterval;
|
||||
if (chatMsgSent)
|
||||
{
|
||||
chatMsgCooldown = MinChatMessageInterval;
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
prevSignal = signal;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
#if CLIENT
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
#endif
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -39,7 +42,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
const float MaxAttachDistance = 150.0f;
|
||||
|
||||
const float MinNodeDistance = 15.0f;
|
||||
const float MinNodeDistance = 7.0f;
|
||||
|
||||
const int MaxNodeCount = 255;
|
||||
const int MaxNodesPerNetworkEvent = 30;
|
||||
@@ -177,6 +180,8 @@ namespace Barotrauma.Items.Components
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
|
||||
nodePos = RoundNode(nodePos);
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
|
||||
|
||||
@@ -334,7 +339,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
bool disableGrid = SubEditorScreen.IsSubEditor() && (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift));
|
||||
newNodePos = disableGrid ? item.Position : RoundNode(item.Position);
|
||||
#else
|
||||
newNodePos = RoundNode(item.Position);
|
||||
#endif
|
||||
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
|
||||
canPlaceNode = true;
|
||||
}
|
||||
@@ -497,6 +507,9 @@ namespace Barotrauma.Items.Components
|
||||
sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y - item.Position.Y), sectionExtents.Y);
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ClearConnections(Character user = null)
|
||||
@@ -507,7 +520,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this))
|
||||
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this) && !item.Removed)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(connectionPanel);
|
||||
@@ -526,18 +539,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (connections[0] != null && connections[1] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[0].Item.Name + " (" + connections[0].Name + ") to "+
|
||||
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else if (connections[0] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
else if (connections[1] != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " disconnected a wire from " +
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
||||
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private int failedLaunchAttempts;
|
||||
|
||||
private readonly List<Item> activeProjectiles = new List<Item>();
|
||||
public IEnumerable<Item> ActiveProjectiles => activeProjectiles;
|
||||
|
||||
private Character user;
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
}
|
||||
|
||||
[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
|
||||
@@ -72,6 +80,41 @@ namespace Barotrauma.Items.Components
|
||||
set { reloadTime = value; }
|
||||
}
|
||||
|
||||
[Editable(0.1f, 10f), Serialize(1.0f, false, description: "Modifies the duration of retraction of the barrell after recoil to get back to the original position after shooting. Reload time affects this too.")]
|
||||
public float RetractionDurationMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(0.1f, 10f), Serialize(0.1f, false, description: "How quickly the recoil moves the barrel after launching.")]
|
||||
public float RecoilTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(0f, 1000f), Serialize(0f, false, description: "How long the barrell stays in place after the recoil and before retracting back to the original position.")]
|
||||
public float RetractionDelay
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
|
||||
public int ProjectileCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false, description: "Can the turret be fired without projectiles (causing it just to execute the OnUse effects and the firing animation without actually firing anything).")]
|
||||
public bool LaunchWithoutProjectile
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
|
||||
public Vector2 RotationLimits
|
||||
{
|
||||
@@ -95,6 +138,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles (in degrees).")]
|
||||
public float Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[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.")]
|
||||
@@ -155,7 +205,21 @@ namespace Barotrauma.Items.Components
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serialize(3000.0f, true, description: "How close to a target the turret has to be for an AI character to fire it.")]
|
||||
public float AIRange
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1, true, description: "The turret won't fire additional projectiles if the number of previously fired, still active projectiles reaches this limit. If set to -1, there is no limit to the number of projectiles.")]
|
||||
public int MaxActiveProjectiles
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Turret(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -187,6 +251,7 @@ namespace Barotrauma.Items.Components
|
||||
//if (item.FlippedY) flippedRotation = 180.0f - flippedRotation;
|
||||
transformedBarrelPos = MathUtils.RotatePointAroundTarget(barrelPos * item.Scale, new Vector2(item.Rect.Width / 2, item.Rect.Height / 2), flippedRotation);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
item.SpriteRotation = MathHelper.ToRadians(flippedRotation);
|
||||
#endif
|
||||
}
|
||||
@@ -213,13 +278,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
this.cam = cam;
|
||||
|
||||
if (reload > 0.0f) reload -= deltaTime;
|
||||
if (reload > 0.0f) { reload -= deltaTime; }
|
||||
|
||||
if (user != null && user.Removed)
|
||||
{
|
||||
user = null;
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
if (minRotation == maxRotation) return;
|
||||
if (minRotation == maxRotation) { return; }
|
||||
|
||||
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
|
||||
|
||||
@@ -230,12 +300,19 @@ namespace Barotrauma.Items.Components
|
||||
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
|
||||
}
|
||||
|
||||
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
|
||||
if (degreeOfSuccess < 0.5f) degreeOfSuccess *= degreeOfSuccess; //the ease of aiming drops quickly with insufficient skill levels
|
||||
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
|
||||
if (degreeOfSuccess < 0.5f) { degreeOfSuccess *= degreeOfSuccess; } //the ease of aiming drops quickly with insufficient skill levels
|
||||
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
|
||||
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
|
||||
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
|
||||
|
||||
if (user?.Info != null)
|
||||
{
|
||||
user.Info.IncreaseSkillLevel("weapons",
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f),
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
angularVelocity +=
|
||||
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
|
||||
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
|
||||
@@ -265,95 +342,110 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (!characterUsable && character != null) return false;
|
||||
if (!characterUsable && character != null) { return false; }
|
||||
return TryLaunch(deltaTime, character);
|
||||
}
|
||||
|
||||
private bool TryLaunch(float deltaTime, Character character = null)
|
||||
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null) return false;
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
|
||||
|
||||
if (reload > 0.0f) return false;
|
||||
if (reload > 0.0f) { return false; }
|
||||
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
if (MaxActiveProjectiles >= 0)
|
||||
{
|
||||
#if CLIENT
|
||||
if (!flashLowPower && character != null && character == Character.Controlled)
|
||||
activeProjectiles.RemoveAll(it => it.Removed);
|
||||
if (activeProjectiles.Count >= MaxActiveProjectiles)
|
||||
{
|
||||
flashLowPower = true;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
if (!ignorePower)
|
||||
{
|
||||
//use linked projectile containers in case they have to react to the turret being launched somehow
|
||||
//(play a sound, spawn more projectiles)
|
||||
if (!(e is Item linkedItem)) continue;
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
if (GetAvailableBatteryPower() < powerConsumption)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
var repairable = linkedItem.GetComponent<Repairable>();
|
||||
if (repairable != null)
|
||||
{
|
||||
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var projectiles = GetLoadedProjectiles(true);
|
||||
if (projectiles.Count == 0)
|
||||
{
|
||||
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
|
||||
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
|
||||
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
|
||||
failedLaunchAttempts++;
|
||||
#if CLIENT
|
||||
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
failedLaunchAttempts = 0;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
failedLaunchAttempts = 0;
|
||||
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
|
||||
while (neededPower > 0.0001f && batteries.Count > 0)
|
||||
{
|
||||
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
|
||||
float takePower = neededPower / batteries.Count;
|
||||
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
neededPower -= takePower;
|
||||
battery.Charge -= takePower / 3600.0f;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
if (!flashLowPower && character != null && character == Character.Controlled)
|
||||
{
|
||||
battery.Item.CreateServerEvent(battery);
|
||||
flashLowPower = true;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Launch(projectiles[0].Item, character);
|
||||
Projectile launchedProjectile = null;
|
||||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
//use linked projectile containers in case they have to react to the turret being launched somehow
|
||||
//(play a sound, spawn more projectiles)
|
||||
if (!(e is Item linkedItem)) { continue; }
|
||||
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (projectileContainer != null)
|
||||
{
|
||||
linkedItem.Use(deltaTime, null);
|
||||
var repairable = linkedItem.GetComponent<Repairable>();
|
||||
if (repairable != null && failedLaunchAttempts < 2)
|
||||
{
|
||||
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
var projectiles = GetLoadedProjectiles(true);
|
||||
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
|
||||
{
|
||||
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
|
||||
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
|
||||
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
|
||||
failedLaunchAttempts++;
|
||||
#if CLIENT
|
||||
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
|
||||
{
|
||||
flashNoAmmo = true;
|
||||
failedLaunchAttempts = 0;
|
||||
GUI.PlayUISound(GUISoundType.PickItemFail);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
failedLaunchAttempts = 0;
|
||||
launchedProjectile = projectiles.FirstOrDefault();
|
||||
|
||||
if (!ignorePower)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
float neededPower = powerConsumption;
|
||||
while (neededPower > 0.0001f && batteries.Count > 0)
|
||||
{
|
||||
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
|
||||
float takePower = neededPower / batteries.Count;
|
||||
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
neededPower -= takePower;
|
||||
battery.Charge -= takePower / 3600.0f;
|
||||
#if SERVER
|
||||
battery.Item.CreateServerEvent(battery);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (launchedProjectile != null || LaunchWithoutProjectile)
|
||||
{
|
||||
Launch(launchedProjectile?.Item, character);
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (character != null)
|
||||
if (character != null && launchedProjectile != null)
|
||||
{
|
||||
string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name;
|
||||
var containedItems = projectiles[0].Item.ContainedItems;
|
||||
string msg = GameServer.CharacterLogName(character) + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
|
||||
var containedItems = launchedProjectile.Item.ContainedItems;
|
||||
if (containedItems == null || !containedItems.Any())
|
||||
{
|
||||
msg += ")";
|
||||
@@ -369,29 +461,38 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Launch(Item projectile, Character user = null)
|
||||
private void Launch(Item projectile, Character user = null, float? launchRotation = null)
|
||||
{
|
||||
reload = reloadTime;
|
||||
|
||||
projectile.Drop(null);
|
||||
if (projectile.body == null) { return; }
|
||||
|
||||
projectile.body.Dir = 1.0f;
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body.Submarine;
|
||||
|
||||
Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
if (projectile != null)
|
||||
{
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectileComponent.User = user;
|
||||
}
|
||||
activeProjectiles.Add(projectile);
|
||||
projectile.Drop(null);
|
||||
if (projectile.body != null)
|
||||
{
|
||||
projectile.body.Dir = 1.0f;
|
||||
projectile.body.ResetDynamics();
|
||||
projectile.body.Enabled = true;
|
||||
}
|
||||
|
||||
if (projectile.Container != null) projectile.Container.RemoveContained(projectile);
|
||||
|
||||
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
|
||||
projectile.SetTransform(
|
||||
ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)),
|
||||
-(launchRotation ?? rotation) + spread);
|
||||
projectile.UpdateTransform();
|
||||
projectile.Submarine = projectile.body?.Submarine;
|
||||
|
||||
Projectile projectileComponent = projectile.GetComponent<Projectile>();
|
||||
if (projectileComponent != null)
|
||||
{
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
}
|
||||
|
||||
if (projectile.Container != null) { projectile.Container.RemoveContained(projectile); }
|
||||
}
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
|
||||
@@ -403,6 +504,196 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void LaunchProjSpecific();
|
||||
|
||||
private float waitTimer;
|
||||
private float disorderTimer;
|
||||
|
||||
private float prevTargetRotation;
|
||||
private float updateTimer;
|
||||
private bool updatePending;
|
||||
public void ThalamusOperate(WreckAI ai, float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
|
||||
{
|
||||
if (ai == null) { return; }
|
||||
|
||||
IsActive = true;
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatePending)
|
||||
{
|
||||
if (updateTimer < 0.0f)
|
||||
{
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
prevTargetRotation = targetRotation;
|
||||
updateTimer = 0.25f;
|
||||
}
|
||||
updateTimer -= deltaTime;
|
||||
}
|
||||
|
||||
if (!ignoreDelay && waitTimer > 0)
|
||||
{
|
||||
waitTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
Submarine closestSub = null;
|
||||
float maxDistance = 10000.0f;
|
||||
float shootDistance = AIRange;
|
||||
ISpatialEntity target = null;
|
||||
float closestDist = shootDistance * shootDistance;
|
||||
if (targetHumans || targetOtherCreatures)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (character == null || character.Removed || character.IsDead) { continue; }
|
||||
if (character.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
bool isHuman = character.IsHuman || character.Params.Group.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
|
||||
if (isHuman)
|
||||
{
|
||||
if (!targetHumans)
|
||||
{
|
||||
// Don't target humans if not defined to.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!targetOtherCreatures)
|
||||
{
|
||||
// Don't target other creatures if not defined to.
|
||||
continue;
|
||||
}
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
target = character;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (targetSubmarines)
|
||||
{
|
||||
if (target == null || target.Submarine != null)
|
||||
{
|
||||
closestDist = maxDistance * maxDistance;
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
|
||||
float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
closestSub = sub;
|
||||
closestDist = dist;
|
||||
}
|
||||
closestDist = shootDistance * shootDistance;
|
||||
if (closestSub != null)
|
||||
{
|
||||
foreach (var hull in Hull.hullList)
|
||||
{
|
||||
if (!closestSub.IsEntityFoundOnThisSub(hull, true)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(hull.WorldPosition, item.WorldPosition);
|
||||
if (dist > closestDist) { continue; }
|
||||
target = hull;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ignoreDelay)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
// Random movement
|
||||
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
|
||||
targetRotation = Rand.Range(minRotation, maxRotation);
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
if (disorderTimer < 0)
|
||||
{
|
||||
// Random disorder
|
||||
disorderTimer = Rand.Range(0f, 3f);
|
||||
waitTimer = Rand.Range(0.25f, 1f);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-1f, 1f));
|
||||
updatePending = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
disorderTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
if (target == null) { return; }
|
||||
|
||||
float angle = -MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
|
||||
targetRotation = MathUtils.WrapAngleTwoPi(angle);
|
||||
|
||||
if (Math.Abs(targetRotation - prevTargetRotation) > 0.1f) { updatePending = true; }
|
||||
|
||||
if (target is Hull targetHull)
|
||||
{
|
||||
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
|
||||
if (!MathUtils.GetLineRectangleIntersection(item.WorldPosition, item.WorldPosition + barrelDir * AIRange, targetHull.WorldRect, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float midRotation = (minRotation + maxRotation) / 2.0f;
|
||||
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
|
||||
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
|
||||
if (angle < minRotation || angle > maxRotation) { return; }
|
||||
float enemyAngle = MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
|
||||
float turretAngle = -rotation;
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return; }
|
||||
}
|
||||
|
||||
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
|
||||
Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition);
|
||||
if (target.Submarine != null)
|
||||
{
|
||||
start -= target.Submarine.SimPosition;
|
||||
end -= target.Submarine.SimPosition;
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody == null) { return; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
{
|
||||
targetCharacter = c;
|
||||
}
|
||||
else if (pickedBody.UserData is Limb limb)
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pickedBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
Submarine sub = e.Submarine;
|
||||
if (sub == null) { return; }
|
||||
if (!targetSubmarines) { return; }
|
||||
if (sub == Item.Submarine) { return; }
|
||||
// Don't shoot non-player submarines, i.e. wrecks or outposts.
|
||||
if (!sub.Info.IsPlayer) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return;
|
||||
}
|
||||
}
|
||||
TryLaunch(deltaTime, ignorePower: true);
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
|
||||
@@ -484,7 +775,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//enough shells and power
|
||||
Character closestEnemy = null;
|
||||
float closestDist = 3000 * 3000;
|
||||
float closestDist = AIRange * AIRange;
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
// Ignore dead, friendly, and those that are inside the same sub
|
||||
@@ -526,7 +817,7 @@ namespace Barotrauma.Items.Components
|
||||
end -= closestEnemy.Submarine.SimPosition;
|
||||
}
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories);
|
||||
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody == null) { return false; }
|
||||
Character targetCharacter = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
@@ -537,39 +828,40 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetCharacter = limb.character;
|
||||
}
|
||||
if (targetCharacter != null && HumanAIController.IsFriendly(character, targetCharacter))
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return false;
|
||||
if (HumanAIController.IsFriendly(character, targetCharacter))
|
||||
{
|
||||
// Don't shoot friendly characters
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (targetCharacter == null && !(pickedBody.UserData is Structure) && !(pickedBody.UserData is Item))
|
||||
else
|
||||
{
|
||||
// Hit something else than a wall or an item (probably a level wall)
|
||||
return false;
|
||||
if (pickedBody.UserData is ISpatialEntity e)
|
||||
{
|
||||
Submarine sub = e.Submarine;
|
||||
if (sub == null) { return false; }
|
||||
if (sub == Item.Submarine) { return false; }
|
||||
// Don't shoot non-player submarines, i.e. wrecks or outposts.
|
||||
if (!sub.Info.IsPlayer) { return false; }
|
||||
// Don't shoot friendly submarines.
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit something else, probably a level wall
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "fireatwill")
|
||||
if (objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
|
||||
character.SetInput(InputType.Shoot, true, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
|
||||
{
|
||||
var batteries = item.GetConnectedComponents<PowerContainer>();
|
||||
|
||||
availableCharge = 0.0f;
|
||||
availableCapacity = 0.0f;
|
||||
foreach (PowerContainer battery in batteries)
|
||||
{
|
||||
availableCharge += battery.Charge;
|
||||
availableCapacity += battery.Capacity;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
@@ -614,7 +906,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//check if the contained item is another itemcontainer with projectiles inside it
|
||||
if (containedItem.ContainedItems == null) continue;
|
||||
if (containedItem.ContainedItems == null) { continue; }
|
||||
foreach (Item subContainedItem in containedItem.ContainedItems)
|
||||
{
|
||||
projectileComponent = subContainedItem.GetComponent<Projectile>();
|
||||
@@ -695,7 +987,6 @@ namespace Barotrauma.Items.Components
|
||||
TryLaunch((float)Timing.Step, sender);
|
||||
}
|
||||
break;
|
||||
case "toggle":
|
||||
case "toggle_light":
|
||||
if (lightComponent != null)
|
||||
{
|
||||
@@ -707,8 +998,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
Item item = (Item)extraData[2];
|
||||
msg.Write(item.Removed ? (ushort)0 : item.ID);
|
||||
if (extraData.Length > 2)
|
||||
{
|
||||
msg.Write(!(extraData[2] is Item item) || item.Removed ? ushort.MaxValue : item.ID);
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((ushort)0);
|
||||
float wrappedTargetRotation = targetRotation;
|
||||
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
{
|
||||
wrappedTargetRotation += MathHelper.TwoPi;
|
||||
}
|
||||
while (wrappedTargetRotation > maxRotation && MathUtils.IsValid(wrappedTargetRotation))
|
||||
{
|
||||
wrappedTargetRotation -= MathHelper.TwoPi;
|
||||
}
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(wrappedTargetRotation, minRotation, maxRotation), minRotation, maxRotation, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user