(bf212a41f) v0.9.2.0 pre-release test version

This commit is contained in:
Joonas Rikkonen
2019-07-27 21:06:07 +03:00
parent afa2137bd2
commit 0f63da27b2
154 changed files with 3959 additions and 1428 deletions
@@ -64,6 +64,12 @@ namespace Barotrauma
}
}
public float SonarDisruption
{
get;
set;
}
public string SonarLabel;
public bool Enabled = true;
@@ -127,6 +133,7 @@ namespace Barotrauma
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime);
SonarDisruption = element.GetAttributeFloat("sonardisruption", 0.0f);
SonarLabel = element.GetAttributeString("sonarlabel", "");
string typeString = element.GetAttributeString("type", "Any");
if (Enum.TryParse(typeString, out TargetType t))
@@ -841,7 +841,7 @@ namespace Barotrauma
private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null)
{
AttackContext currentContext = Character.GetAttackContext();
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity;
Limb selectedLimb = null;
float currentPriority = 0;
foreach (Limb limb in Character.AnimController.Limbs)
@@ -163,7 +163,7 @@ namespace Barotrauma
{
Weapon = null;
}
else if (!WeaponComponent.HasRequiredContainedItems(false))
else if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
{
// Seek ammunition only if cannot find a new weapon
if (!Reload(!HoldPosition, () => GetWeapon(out _) == null))
@@ -234,14 +234,14 @@ namespace Barotrauma
{
if (component is RangedWeapon rw)
{
if (ignoreRequiredItems || rw.HasRequiredContainedItems(false))
if (ignoreRequiredItems || rw.HasRequiredContainedItems(character, addMessage: false))
{
weapons.Add(rw);
}
}
else if (component is MeleeWeapon mw)
{
if (ignoreRequiredItems || mw.HasRequiredContainedItems(false))
if (ignoreRequiredItems || mw.HasRequiredContainedItems(character, addMessage: false))
{
weapons.Add(mw);
}
@@ -257,7 +257,7 @@ namespace Barotrauma
{
if (statusEffect.Afflictions.Any())
{
if (ignoreRequiredItems || component.HasRequiredContainedItems(false))
if (ignoreRequiredItems || component.HasRequiredContainedItems(character, addMessage: false))
{
weapons.Add(component);
}
@@ -284,7 +284,7 @@ namespace Barotrauma
private bool Equip()
{
if (character.LockHands) { return false; }
if (!WeaponComponent.HasRequiredContainedItems(false))
if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
{
Mode = CombatMode.Retreat;
return false;
@@ -428,7 +428,7 @@ namespace Barotrauma
}
}
}
if (WeaponComponent.HasRequiredContainedItems(false))
if (WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
{
return true;
}
@@ -108,6 +108,7 @@ namespace Barotrauma
else
{
move = false;
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
@@ -4,7 +4,6 @@ using System;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics;
using Barotrauma.Items.Components;
namespace Barotrauma
{
@@ -207,7 +207,7 @@ namespace Barotrauma
bool remove = false;
foreach (ItemComponent ic in item.Components)
{
if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; }
if (!ic.HasRequiredContainedItems(user: character, addMessage: false)) { continue; }
#if CLIENT
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
#endif
@@ -50,23 +50,13 @@ namespace Barotrauma
if (!IsRemotePlayer)
{
float characterDist = Vector2.DistanceSquared(cam.WorldViewCenter, WorldPosition);
#if SERVER
float characterDist = float.MaxValue;
#if CLIENT
characterDist = Vector2.DistanceSquared(cam.GetPosition(), WorldPosition);
#elif SERVER
if (GameMain.Server != null)
{
//get the distance from the closest player to this character
foreach (Character c in CharacterList)
{
if (c != this && c.IsRemotePlayer)
{
float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition);
if (dist < characterDist)
{
characterDist = dist;
if (characterDist < DisableSimplePhysicsDistSqr) break;
}
}
}
characterDist = GetClosestDistance();
}
#endif
@@ -90,5 +80,50 @@ namespace Barotrauma
aiController.Update(deltaTime);
}
}
#if SERVER
// Gets the closest distance, either an active player character or spectator
private float GetClosestDistance()
{
float minDist = float.MaxValue;
for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++)
{
var spectatePos = GameMain.Server.ConnectedClients[i].SpectatePos;
if (spectatePos != null)
{
float dist = Vector2.DistanceSquared(spectatePos.Value, WorldPosition);
if (dist < minDist)
{
minDist = dist;
}
if (dist < DisableSimplePhysicsDistSqr)
{
return dist;
}
}
}
foreach (Character c in CharacterList)
{
if (c != this && c.IsRemotePlayer)
{
float dist = Vector2.DistanceSquared(c.WorldPosition, WorldPosition);
if (dist < minDist)
{
minDist = dist;
}
if (dist < DisableSimplePhysicsDistSqr)
{
return dist;
}
}
}
return minDist;
}
#endif
}
}
@@ -139,8 +139,9 @@ namespace Barotrauma
Collider.FarseerBody.FixedRotation = false;
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
Collider.LinearVelocity = MainLimb.LinearVelocity;
Collider.Enabled = false;
Collider.FarseerBody.FixedRotation = false;
Collider.LinearVelocity = MainLimb.LinearVelocity;
Collider.SetTransformIgnoreContacts(MainLimb.SimPosition, MainLimb.Rotation);
}
if (character.IsDead && deathAnimTimer < deathAnimDuration)
@@ -286,7 +286,7 @@ namespace Barotrauma
var waistJoint = GetJointBetweenLimbs(LimbType.Waist, upperLegType);
Vector2 localAnchorWaist = Vector2.Zero;
Vector2 localAnchorKnee = Vector2.Zero;
if (shoulder != null)
if (waistJoint != null)
{
localAnchorWaist = waistJoint.LimbA.type == upperLegType ? waistJoint.LocalAnchorA : waistJoint.LocalAnchorB;
}
@@ -298,6 +298,7 @@ namespace Barotrauma
upperLegLength = Vector2.Distance(localAnchorWaist, localAnchorKnee);
LimbJoint ankleJoint = GetJointBetweenLimbs(lowerLegType, footType);
if (ankleJoint == null || kneeJoint == null) { return; }
lowerLegLength = Vector2.Distance(
kneeJoint.LimbA.type == lowerLegType ? kneeJoint.LocalAnchorA : kneeJoint.LocalAnchorB,
ankleJoint.LimbA.type == lowerLegType ? ankleJoint.LocalAnchorA : ankleJoint.LocalAnchorB);
@@ -537,14 +538,13 @@ namespace Barotrauma
float limpAmount =
character.CharacterHealth.GetAfflictionStrength("damage", leftFoot, true) +
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true);
character.CharacterHealth.GetAfflictionStrength("damage", rightFoot, true) +
character.CharacterHealth.GetAfflictionStrength("spaceherpes");
limpAmount = MathHelper.Clamp(limpAmount / 100.0f, 0.0f, 1.0f);
float walkCycleMultiplier = 1.0f;
if (Stairs != null)
{
//TODO: allow editing these values in character editor?
bool running = Math.Abs(targetMovement.X) > 2.0f;
TargetMovement = new Vector2(MathHelper.Clamp(TargetMovement.X, -1.7f, 1.7f), TargetMovement.Y);
walkCycleMultiplier *= 1.5f;
}
@@ -579,7 +579,7 @@ namespace Barotrauma
if (limpAmount > 0.0f)
{
//make the footpos oscillate when limping
footMid += (Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f));
footMid += (Math.Max(Math.Abs(walkPosX) * limpAmount, 0.0f) * Math.Min(Math.Abs(TargetMovement.X), 0.3f)) * Dir;
}
movement = overrideTargetMovement == Vector2.Zero ?
@@ -663,7 +663,13 @@ namespace Barotrauma
}
}
if (TorsoAngle.HasValue) torso.body.SmoothRotate(TorsoAngle.Value * Dir, 50.0f);
if (TorsoAngle.HasValue)
{
float torsoAngle = TorsoAngle.Value;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
torsoAngle -= herpesStrength / 150.0f;
torso.body.SmoothRotate(torsoAngle * Dir, 50.0f);
}
if (HeadAngle.HasValue) head.body.SmoothRotate(HeadAngle.Value * Dir, 50.0f);
if (!onGround)
@@ -689,7 +695,6 @@ namespace Barotrauma
for (int i = -1; i < 2; i += 2)
{
Limb foot = i == -1 ? leftFoot : rightFoot;
Limb leg = i == -1 ? leftLeg : rightLeg;
Vector2 footPos = stepSize * -i;
footPos += new Vector2(Math.Sign(movement.X) * FootMoveOffset.X, FootMoveOffset.Y);
@@ -706,6 +711,15 @@ namespace Barotrauma
}
footPos.Y = Math.Min(waistPos.Y - colliderPos.Y - 0.4f, footPos.Y);
#if CLIENT
if ((i == 1 && Math.Sign(Math.Sin(WalkPos)) > 0 && Math.Sign(walkPosY) < 0) ||
(i == -1 && Math.Sign(Math.Sin(WalkPos)) < 0 && Math.Sign(walkPosY) > 0))
{
PlayImpactSound(foot);
}
#endif
if (!foot.Disabled)
{
foot.DebugRefPos = colliderPos;
@@ -766,7 +780,6 @@ namespace Barotrauma
}
var foot = i == -1 ? rightFoot : leftFoot;
Limb leg = i == -1 ? rightLeg : leftLeg;
if (!foot.Disabled)
{
@@ -96,16 +96,16 @@ namespace Barotrauma
public virtual AnimationType AnimationType { get; protected set; }
public static string GetDefaultFileName(string speciesName, AnimationType animType) => $"{speciesName.CapitaliseFirstInvariant()}{animType.ToString()}";
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/";
public static string GetDefaultFile(string speciesName, AnimationType animType, ContentPackage contentPackage = null) =>
$"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName, animType)}.xml";
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName, contentPackage))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
folder = GetDefaultFolder(speciesName);
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Animations");
}
return folder;
}
@@ -66,7 +66,6 @@ namespace Barotrauma
.Concat(Joints.Select(j => j as RagdollSubParams)));
public static string GetDefaultFileName(string speciesName) => $"{speciesName.CapitaliseFirstInvariant()}DefaultRagdoll";
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Ragdolls/";
public static string GetDefaultFile(string speciesName, ContentPackage contentPackage = null) => $"{GetFolder(speciesName, contentPackage)}{GetDefaultFileName(speciesName)}.xml";
private static readonly object[] dummyParams = new object[]
@@ -84,11 +83,11 @@ namespace Barotrauma
public static string GetFolder(string speciesName, ContentPackage contentPackage = null)
{
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName, contentPackage))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
string configFilePath = Character.GetConfigFile(speciesName, contentPackage);
var folder = XMLExtensions.TryLoadXml(configFilePath)?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
{
//DebugConsole.NewMessage("[RagollParams] Using the default folder.");
folder = GetDefaultFolder(speciesName);
folder = Path.Combine(Path.GetDirectoryName(configFilePath), "Ragdolls") + Path.DirectorySeparatorChar;
}
return folder;
}
@@ -1047,11 +1047,16 @@ namespace Barotrauma
protected bool levitatingCollider = true;
/// <summary>
/// How long has the ragdoll stayed motionless
/// </summary>
private float bodyInRestTimer;
public bool forceStanding;
public void Update(float deltaTime, Camera cam)
{
if (!character.Enabled || Frozen || Invalid) return;
if (!character.Enabled || Frozen || Invalid) { return; }
CheckValidity();
@@ -1063,6 +1068,8 @@ namespace Barotrauma
FindHull();
PreventOutsideCollision();
CheckBodyInRest(deltaTime);
splashSoundTimer -= deltaTime;
@@ -1315,6 +1322,29 @@ namespace Barotrauma
UpdateProjSpecific(deltaTime);
}
private void CheckBodyInRest(float deltaTime)
{
if (Collider.LinearVelocity.LengthSquared() > 0.01f || character.SelectedBy != null || !character.IsDead)
{
bodyInRestTimer = 0.0f;
foreach (Limb limb in Limbs)
{
limb.body.PhysEnabled = true;
}
}
else if (Limbs.All(l => l != null && !l.body.Enabled || l.LinearVelocity.LengthSquared() < 0.001f))
{
bodyInRestTimer += deltaTime;
if (bodyInRestTimer > 1.0f)
{
foreach (Limb limb in Limbs)
{
limb.body.PhysEnabled = false;
}
}
}
}
public bool Invalid { get; private set; }
private int validityResets;
private bool CheckValidity()
@@ -298,7 +298,10 @@ namespace Barotrauma
}
float afflictionStrength = subElement.GetAttributeFloat(1.0f, "amount", "strength");
Afflictions.Add(afflictionPrefab.Instantiate(afflictionStrength));
var affliction = afflictionPrefab.Instantiate(afflictionStrength);
affliction.ApplyProbability = subElement.GetAttributeFloat("probability", 1.0f);
Afflictions.Add(affliction);
break;
case "conditional":
foreach (XAttribute attribute in subElement.Attributes())
@@ -743,31 +743,33 @@ namespace Barotrauma
PressureProtection = 100.0f;
}
List<XElement> inventoryElements = new List<XElement>();
List<float> inventoryCommonness = new List<float>();
List<XElement> healthElements = new List<XElement>();
List<float> healthCommonness = new List<float>();
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "inventory":
Inventory = new CharacterInventory(subElement, this);
inventoryElements.Add(subElement);
inventoryCommonness.Add(subElement.GetAttributeFloat("commonness", 1.0f));
break;
case "health":
CharacterHealth = new CharacterHealth(subElement, this);
healthElements.Add(subElement);
healthCommonness.Add(subElement.GetAttributeFloat("commonness", 1.0f));
break;
case "statuseffect":
statusEffects.Add(StatusEffect.Load(subElement, Name));
break;
}
}
List<XElement> healthElements = new List<XElement>();
List<float> healthCommonness = new List<float>();
foreach (XElement element in doc.Root.Elements())
if (inventoryElements.Count > 0)
{
if (element.Name.ToString().ToLowerInvariant() != "health") continue;
healthElements.Add(element);
healthCommonness.Add(element.GetAttributeFloat("commonness", 1.0f));
Inventory = new CharacterInventory(
inventoryElements.Count == 1 ? inventoryElements[0] : ToolBox.SelectWeightedRandom(inventoryElements, inventoryCommonness, random),
this);
}
if (healthElements.Count == 0)
{
CharacterHealth = new CharacterHealth(this);
@@ -834,6 +836,7 @@ namespace Barotrauma
#if CLIENT
head.LoadHuskSprite();
head.LoadHerpesSprite();
#endif
}
@@ -977,7 +980,30 @@ namespace Barotrauma
return false;
}
#endif
if (inputType == InputType.Up || inputType == InputType.Down ||
inputType == InputType.Left || inputType == InputType.Right)
{
var invertControls = CharacterHealth.GetAffliction("invertcontrols");
if (invertControls != null)
{
switch (inputType)
{
case InputType.Left:
inputType = InputType.Right;
break;
case InputType.Right:
inputType = InputType.Left;
break;
case InputType.Up:
inputType = InputType.Down;
break;
case InputType.Down:
inputType = InputType.Up;
break;
}
}
}
return keys[(int)inputType].Held;
}
@@ -1579,9 +1605,16 @@ namespace Barotrauma
//locked wires are never interactable
if (wire.Locked) return false;
//wires are interactable if the character has selected either of the items the wire is connected to
if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item) return true;
if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item) return true;
//wires are interactable if the character has selected an item the wire is connected to,
//and it's disconnected from the other end
if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item)
{
return wire.Connections[1] == null;
}
if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item)
{
return wire.Connections[0] == null;
}
}
if (checkLinked && item.DisplaySideBySideWhenLinked)
@@ -1891,15 +1924,30 @@ namespace Barotrauma
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(otherCharacter.WorldPosition, c.WorldPosition));
}
#if SERVER
for (int i = 0; i < GameMain.Server.ConnectedClients.Count; i++)
{
var spectatePos = GameMain.Server.ConnectedClients[i].SpectatePos;
if (spectatePos != null)
{
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(spectatePos.Value, c.WorldPosition));
}
}
#endif
if (distSqr > NetConfig.DisableCharacterDistSqr)
{
c.Enabled = false;
if (c.IsDead && c.AIController is EnemyAIController)
{
Entity.Spawner?.AddToRemoveQueue(c);
}
}
else if (distSqr < NetConfig.EnableCharacterDistSqr)
{
c.Enabled = true;
}
}
}
}
else if (Submarine.MainSub != null)
{
@@ -1907,19 +1955,22 @@ namespace Barotrauma
float distSqr = Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition);
if (Controlled != null)
{
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(Controlled.WorldPosition, c.WorldPosition));
distSqr = Math.Min(distSqr, Vector2.DistanceSquared(GameMain.GameScreen.Cam.GetPosition(), c.WorldPosition));
}
if (distSqr > NetConfig.DisableCharacterDistSqr)
{
c.Enabled = false;
if (c.IsDead && c.AIController is EnemyAIController)
{
Entity.Spawner?.AddToRemoveQueue(c);
}
}
else if ( distSqr < NetConfig.EnableCharacterDistSqr)
else if (distSqr < NetConfig.EnableCharacterDistSqr)
{
c.Enabled = true;
}
}
}
}
@@ -2261,8 +2312,6 @@ namespace Barotrauma
speechBubbleColor = color;
}
partial void AdjustKarma(Character attacker, AttackResult attackResult);
partial void DamageHUD(float amount);
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
@@ -2358,7 +2407,12 @@ namespace Barotrauma
{
hitLimb = null;
if (Removed) return new AttackResult();
if (Removed) { return new AttackResult(); }
if (attacker != null && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
}
float closestDistance = 0.0f;
foreach (Limb limb in AnimController.Limbs)
@@ -2376,7 +2430,12 @@ namespace Barotrauma
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, List<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null)
{
if (Removed) return new AttackResult();
if (Removed) { return new AttackResult(); }
if (attacker != null && attacker != this && GameMain.NetworkMember != null && !GameMain.NetworkMember.ServerSettings.AllowFriendlyFire)
{
if (attacker.TeamID == TeamID) { return new AttackResult(); }
}
SetStun(stun);
Vector2 dir = hitLimb.WorldPosition - worldPosition;
@@ -2395,7 +2454,6 @@ namespace Barotrauma
OnAttacked?.Invoke(attacker, attackResult);
OnAttackedProjSpecific(attacker, attackResult);
};
AdjustKarma(attacker, attackResult);
if (attacker != null && attackResult.Damage > 0.0f)
{
@@ -18,6 +18,11 @@ namespace Barotrauma
public float StrengthDiminishMultiplier = 1.0f;
public Affliction MultiplierSource;
/// <summary>
/// Probability for the affliction to be applied. Used by attacks.
/// </summary>
public float ApplyProbability;
/// <summary>
/// Which character gave this affliction
/// </summary>
@@ -162,6 +167,7 @@ namespace Barotrauma
foreach (StatusEffect statusEffect in currentEffect.StatusEffects)
{
statusEffect.SetUser(Source);
if (statusEffect.HasTargetType(StatusEffect.TargetType.Character))
{
statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, characterHealth.Character);
@@ -157,6 +157,9 @@ namespace Barotrauma
//how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f;
//how much karma changes when a player applies this affliction to someone (per strength of the affliction)
public float KarmaChangeOnApplied;
public float BurnOverlayAlpha;
public float DamageOverlayAlpha;
@@ -265,9 +268,12 @@ namespace Barotrauma
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
KarmaChangeOnApplied = element.GetAttributeFloat("karmachangeonapplied", 0.0f);
CauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeath." + Identifier, true) ?? element.GetAttributeString("causeofdeathdescription", "");
SelfCauseOfDeathDescription = TextManager.Get("AfflictionCauseOfDeathSelf." + Identifier, true) ?? element.GetAttributeString("selfcauseofdeathdescription", "");
AchievementOnRemoved = element.GetAttributeString("achievementonremoved", "");
foreach (XElement subElement in element.Elements())
@@ -0,0 +1,47 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma
{
class AfflictionSpaceHerpes : Affliction
{
private float invertControlsCooldown = 60.0f;
private float stunCoolDown = 60.0f;
public AfflictionSpaceHerpes(AfflictionPrefab prefab, float strength) : base(prefab, strength)
{
}
public override void Update(CharacterHealth characterHealth, Limb targetLimb, float deltaTime)
{
base.Update(characterHealth, targetLimb, deltaTime);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
invertControlsCooldown -= deltaTime;
if (invertControlsCooldown <= 0.0f)
{
//invert controls every 126-234 seconds when strength is close to 0
//every 56-104 seconds when strength is close to 100
invertControlsCooldown = (180.0f - Strength) * Rand.Range(0.7f, 1.3f);
var invertControlsAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "invertcontrols");
float invertControlsDuration = MathHelper.Lerp(10.0f, 60.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f);
characterHealth.ApplyAffliction(null, new Affliction(invertControlsAffliction, invertControlsDuration));
}
if (Strength > 50.0f)
{
stunCoolDown -= deltaTime;
if (stunCoolDown <= 0.0f)
{
//stun every 126-234 seconds when strength is close to 0
//stun 56-104 seconds when strength is close to 100
stunCoolDown = (180.0f - Strength) * Rand.Range(0.7f, 1.3f);
float stunDuration = MathHelper.Lerp(3.0f, 10.0f, Strength / 100.0f) * Rand.Range(0.7f, 1.3f);
characterHealth.Character.SetStun(stunDuration);
}
}
}
}
}
@@ -411,7 +411,7 @@ namespace Barotrauma
amount -= reduceAmount;
}
}
CalculateVitality();
}
public void ApplyDamage(Limb hitLimb, AttackResult attackResult)
@@ -407,7 +407,7 @@ namespace Barotrauma
{
List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
//create a copy of the original affliction list to prevent modifying the afflictions of an Attack/StatusEffect etc
afflictions = new List<Affliction>(afflictions);
afflictions = new List<Affliction>(afflictions.Where(a => Rand.Range(0.0f, 1.0f) <= a.ApplyProbability));
for (int i = 0; i < afflictions.Count; i++)
{
foreach (DamageModifier damageModifier in damageModifiers)
@@ -485,6 +485,22 @@ namespace Barotrauma
List.Add(package);
}
}
public void Delete()
{
try
{
File.Delete(Path);
GameMain.Config.SelectedContentPackages.Remove(this);
GameMain.Config.SaveNewPlayerConfig();
}
catch (IOException e)
{
DebugConsole.ThrowError("Failed to delete content package \"" + Name + "\".", e);
return;
}
List.Remove(this);
}
}
public class ContentFile
@@ -136,7 +136,7 @@ namespace Barotrauma
if (!handle.Coroutine.MoveNext()) return;
}
}
catch (ThreadAbortException tae)
catch (ThreadAbortException)
{
//not an error, don't worry about it
}
@@ -110,7 +110,15 @@ namespace Barotrauma
private static void AssignOnExecute(string names, Action<string[]> onExecute)
{
commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0).OnExecute = onExecute;
var matchingCommand = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0);
if (matchingCommand == null)
{
throw new Exception("AssignOnExecute failed. Command matching the name(s) \""+names+"\" not found.");
}
else
{
matchingCommand.OnExecute = onExecute;
}
}
static DebugConsole()
@@ -251,16 +259,11 @@ namespace Barotrauma
spawnPosParams.ToArray()
};
}, isCheat: true));
commands.Add(new Command("disablecrewai", "disablecrewai: Disable the AI of the NPCs in the crew.", (string[] args) =>
{
HumanAIController.DisableCrewAI = true;
NewMessage("Crew AI disabled", Color.Red);
// This is probably not where it should be?
//ThrowError("Karma has not been fully implemented yet, and is disabled in this version of Barotrauma.");
/*if (GameMain.Server == null) return;
GameMain.Server.KarmaEnabled = !GameMain.Server.KarmaEnabled;*/
}));
commands.Add(new Command("enablecrewai", "enablecrewai: Enable the AI of the NPCs in the crew.", (string[] args) =>
@@ -304,8 +307,31 @@ namespace Barotrauma
commands.Add(new Command("revokecommandperm", "revokecommandperm [id]: Revokes permission to use the specified console commands from the player with the specified client ID.", null));
commands.Add(new Command("showperm", "showperm [id]: Shows the current administrative permissions of the client with the specified client ID.", null));
commands.Add(new Command("respawnnow", "respawnnow: Trigger a respawn immediately if there are any clients waiting to respawn.", null));
//commands.Add(new Command("togglekarma", "togglekarma: Toggles the karma system.", null));
commands.Add(new Command("showkarma", "showkarma: Show the current karma values of the players.", null));
commands.Add(new Command("togglekarma", "togglekarma: Toggle the karma system on/off.", null));
commands.Add(new Command("resetkarma", "resetkarma [client]: Resets the karma value of the specified client to 100.", null,
() =>
{
if (GameMain.NetworkMember?.ConnectedClients == null) { return null; }
return new string[][]
{
GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray()
};
}));
commands.Add(new Command("setkarma", "setkarma [client] [0-100]: Sets the karma of the specified client to the specified value.", null,
() =>
{
if (GameMain.NetworkMember?.ConnectedClients == null) { return null; }
return new string[][]
{
GameMain.NetworkMember.ConnectedClients.Select(c => c.Name).ToArray(),
new string[] { "50" }
};
}));
commands.Add(new Command("togglekarmatestmode", "togglekarmatestmode: Toggle the karma test mode on/off. When test mode is enabled, clients get notified when their karma value changes (including the reason for the increase/decrease) and the server doesn't ban clients whose karma decreases below the ban threshold.", null));
commands.Add(new Command("kick", "kick [name]: Kick a player out of the server.", (string[] args) =>
{
@@ -654,11 +680,11 @@ namespace Barotrauma
if (args.Length > 0 && args[0].ToLowerInvariant() == "start")
{
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition);
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
else
{
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition);
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
}, isCheat: true));
@@ -280,16 +280,23 @@ namespace Barotrauma
float holeCount = 0.0f;
floodingAmount = 0.0f;
int hullCount = 0;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine == null || hull.Submarine.IsOutpost) { continue; }
hullCount++;
foreach (Gap gap in hull.ConnectedGaps)
{
if (!gap.IsRoomToRoom) holeCount += gap.Open;
}
floodingAmount += hull.WaterVolume / hull.Volume / Hull.hullList.Count;
floodingAmount += hull.WaterVolume / hull.Volume;
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
}
if (hullCount > 0)
{
floodingAmount = floodingAmount / hullCount;
}
//hull integrity at 0.0 if there are 10 or more wide-open holes
avgHullIntegrity = MathHelper.Clamp(1.0f - holeCount / 10.0f, 0.0f, 1.0f);
@@ -1,9 +1,5 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -12,9 +8,6 @@ namespace Barotrauma
private Submarine[] subs;
private List<Character>[] crews;
private bool initialized = false;
private int state = 0;
private string[] descriptions;
private static string[] teamNames = { "Team A", "Team B" };
@@ -81,10 +81,8 @@ namespace Barotrauma
bool success =
GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
#if CLIENT
success = success || (GameMain.Server.Character != null && !GameMain.Server.Character.IsDead);
#endif
/*if (success)
{
@@ -119,11 +117,7 @@ namespace Barotrauma
{
c.Inventory?.DeleteAllItems();
}
#if CLIENT
GameMain.GameSession.CrewManager.EndRound();
#endif
if (success)
{
bool atEndPosition = Submarine.MainSub.AtEndPosition;
@@ -110,7 +110,25 @@ namespace Barotrauma
public override bool CanBePut(Item item, int i)
{
return base.CanBePut(item, i) && item.AllowedSlots.Contains(SlotTypes[i]);
}
}
/// <summary>
/// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot
/// </summary>
public bool TryPutItemWithAutoEquipCheck(Item item, Character user, List<InvSlotType> allowedSlots = null, bool createNetworkEvent = true)
{
// Does not auto-equip the item if specified and no suitable any slot found (for example handcuffs are not auto-equipped)
if (item.AllowedSlots.Contains(InvSlotType.Any))
{
var wearable = item.GetComponent<Wearable>();
if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1)
{
return false;
}
}
return TryPutItem(item, user, allowedSlots, createNetworkEvent);
}
/// <summary>
/// If there is room, puts the item in the inventory and returns true, otherwise returns false
@@ -139,29 +157,10 @@ namespace Barotrauma
//try to place the item in a LimbSlot.Any slot if that's allowed
if (allowedSlots.Contains(InvSlotType.Any))
{
for (int i = 0; i < capacity; i++)
int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot);
if (freeIndex > -1)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (Items[i] == item)
{
PutItem(item, i, user, true, createNetworkEvent);
item.Unequip(character);
return true;
}
}
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (inWrongSlot)
{
if (Items[i] != item && Items[i] != null) continue;
}
else
{
if (Items[i] != null) continue;
}
PutItem(item, i, user, true, createNetworkEvent);
PutItem(item, freeIndex, user, true, createNetworkEvent);
item.Unequip(character);
return true;
}
@@ -242,10 +241,37 @@ namespace Barotrauma
}
}
return placedInSlot > -1;
}
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot)
{
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (Items[i] == item)
{
return i;
}
}
for (int i = 0; i < capacity; i++)
{
if (SlotTypes[i] != InvSlotType.Any) continue;
if (inWrongSlot)
{
if (Items[i] != item && Items[i] != null) continue;
}
else
{
if (Items[i] != null) continue;
}
return i;
}
return -1;
}
public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true)
{
if (index < 0 || index >= Items.Length)
@@ -312,6 +312,30 @@ namespace Barotrauma.Items.Components
joint.CollideConnected = true;
}
public int GetDir()
{
if (DockingDir != 0) { return DockingDir; }
if (door != null)
{
if (door.LinkedGap.linkedTo.Count == 1)
{
return IsHorizontal ?
Math.Sign(door.Item.WorldPosition.X - door.LinkedGap.linkedTo[0].WorldPosition.X) :
Math.Sign(door.Item.WorldPosition.Y - door.LinkedGap.linkedTo[0].WorldPosition.Y);
}
}
if (item.Submarine != null)
{
return IsHorizontal ?
Math.Sign(item.WorldPosition.X - item.Submarine.WorldPosition.X) :
Math.Sign(item.WorldPosition.Y - item.Submarine.WorldPosition.Y);
}
return 0;
}
private void ConnectWireBetweenPorts()
{
Wire wire = item.GetComponent<Wire>();
@@ -250,17 +250,14 @@ namespace Barotrauma.Items.Components
if (item.Condition <= RepairThreshold) { return true; }
if (requiredItems.Any() && !hasValidIdCard)
{
ForceOpen(ActionType.OnPicked);
ToggleState(ActionType.OnPicked);
}
return false;
}
private void ForceOpen(ActionType actionType)
private void ToggleState(ActionType actionType)
{
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function
#if CLIENT
PlaySound(actionType, item.WorldPosition, picker);
#endif
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true, forcedOpen: actionType == ActionType.OnPicked);
}
public override bool Select(Character character)
@@ -272,7 +269,7 @@ namespace Barotrauma.Items.Components
{
float originalPickingTime = PickingTime;
PickingTime = 0;
ForceOpen(ActionType.OnUse);
ToggleState(ActionType.OnUse);
PickingTime = originalPickingTime;
}
else if (hasRequiredItems)
@@ -538,11 +535,11 @@ namespace Barotrauma.Items.Components
if (connection.Name == "toggle")
{
SetState(!wasOpen, false, true);
SetState(!wasOpen, false, true, forcedOpen: false);
}
else if (connection.Name == "set_state")
{
SetState(signal != "0", false, true);
SetState(signal != "0", false, true, forcedOpen: false);
}
#if SERVER
@@ -555,9 +552,9 @@ namespace Barotrauma.Items.Components
public void TrySetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false)
{
SetState(open, isNetworkMessage, sendNetworkMessage);
SetState(open, isNetworkMessage, sendNetworkMessage, forcedOpen: false);
}
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage);
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen);
}
}
@@ -558,6 +558,30 @@ namespace Barotrauma.Items.Components
}
}
public override XElement Save(XElement parentElement)
{
if (!attachable)
{
return base.Save(parentElement);
}
var tempMsg = DisplayMsg;
var tempPickKey = PickKey;
var tempRequiredItems = requiredItems;
DisplayMsg = prevMsg;
PickKey = prevPickKey;
requiredItems = prevRequiredItems;
XElement saveElement = base.Save(parentElement);
DisplayMsg = tempMsg;
PickKey = tempPickKey;
requiredItems = tempRequiredItems;
return saveElement;
}
public override void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
base.ServerWrite(msg, c, extraData);
@@ -17,6 +17,8 @@ namespace Barotrauma.Items.Components
private Character activePicker;
private CoroutineHandle pickingCoroutine;
public List<InvSlotType> AllowedSlots
{
get { return allowedSlots; }
@@ -69,7 +71,7 @@ namespace Barotrauma.Items.Components
#if SERVER
item.CreateServerEvent(this);
#endif
CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
pickingCoroutine = CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
}
return false;
}
@@ -81,7 +83,7 @@ namespace Barotrauma.Items.Components
public virtual bool OnPicked(Character picker)
{
if (picker.Inventory.TryPutItem(item, picker, allowedSlots))
if (picker.Inventory.TryPutItemWithAutoEquipCheck(item, picker, allowedSlots))
{
if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false;
this.picker = picker;
@@ -136,7 +138,7 @@ namespace Barotrauma.Items.Components
}
#if CLIENT
picker.UpdateHUDProgressBar(
Character.Controlled?.UpdateHUDProgressBar(
this,
item.WorldPosition,
pickTimer / requiredTime,
@@ -160,13 +162,18 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
private void StopPicking(Character picker)
protected void StopPicking(Character picker)
{
if (picker != null)
{
picker.AnimController.Anim = AnimController.Animation.None;
picker.PickingItem = null;
}
if (pickingCoroutine != null)
{
CoroutineManager.StopCoroutines(pickingCoroutine);
pickingCoroutine = null;
}
activePicker = null;
pickTimer = 0.0f;
}
@@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos;
//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) == null)
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;
@@ -14,12 +14,23 @@ namespace Barotrauma.Items.Components
{
partial class RepairTool : ItemComponent
{
public enum UseEnvironment
{
Air, Water, Both, None
};
private readonly List<string> fixableEntities;
private Vector2 pickedPosition;
private float activeTimer;
private Vector2 debugRayStartPos, debugRayEndPos;
[Serialize("Both", false)]
public UseEnvironment UsableIn
{
get; set;
}
[Serialize(0.0f, false)]
public float Range { get; set; }
@@ -43,6 +54,9 @@ namespace Barotrauma.Items.Components
[Serialize(false, false)]
public bool RepairMultiple { get; set; }
[Serialize(0.0f, false)]
public float FireProbability { get; set; }
public Vector2 TransformedBarrelPos
{
get
@@ -109,6 +123,29 @@ namespace Barotrauma.Items.Components
return false;
}
if (UsableIn == UseEnvironment.None)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
}
if (character.AnimController.InWater)
{
if (UsableIn == UseEnvironment.Air)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
}
}
else
{
if (UsableIn == UseEnvironment.Water)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
}
}
Vector2 targetPosition = item.WorldPosition;
targetPosition += new Vector2(
(float)Math.Cos(item.body.Rotation),
@@ -149,7 +186,7 @@ namespace Barotrauma.Items.Components
{
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
UseProjSpecific(deltaTime);
return true;
@@ -162,9 +199,12 @@ namespace Barotrauma.Items.Components
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
float lastPickedFraction = 0.0f;
if (RepairMultiple)
{
var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true);
lastPickedFraction = Submarine.LastPickedFraction;
Type lastHitType = null;
hitCharacters.Clear();
foreach (Body body in bodies)
@@ -194,6 +234,7 @@ namespace Barotrauma.Items.Components
if (FixBody(user, deltaTime, degreeOfSuccess, body))
{
lastPickedFraction = Submarine.LastPickedBodyDist(body);
if (bodyType != null) { lastHitType = bodyType; }
}
}
@@ -205,13 +246,14 @@ namespace Barotrauma.Items.Components
ignoredBodies, collisionCategories, ignoreSensors: false,
customPredicate: (Fixture f) => { return f?.Body?.UserData != null; },
allowInsideFixture: true));
lastPickedFraction = Submarine.LastPickedFraction;
}
if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
{
fireSourcesInRange.Clear();
//step along the ray in 10% intervals, collecting all fire sources in the range
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
for (float x = 0.0f; x <= lastPickedFraction; x += 0.1f)
{
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * x);
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
@@ -230,6 +272,20 @@ namespace Barotrauma.Items.Components
foreach (FireSource fs in fireSourcesInRange)
{
fs.Extinguish(deltaTime, ExtinguishAmount);
#if SERVER
GameMain.Server.KarmaManager.OnExtinguishingFire(user, deltaTime);
#endif
}
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (Rand.Range(0.0f, 1.0f) < FireProbability * deltaTime)
{
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * lastPickedFraction * 0.9f);
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
new FireSource(displayPos);
}
}
}
@@ -242,12 +298,12 @@ namespace Barotrauma.Items.Components
if (targetBody.UserData is Structure targetStructure)
{
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return false; }
if (targetStructure.IsPlatform) { return false; }
int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition));
if (sectionIndex < 0) { return false; }
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; }
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
@@ -283,9 +339,7 @@ namespace Barotrauma.Items.Components
else if (targetBody.UserData is Item targetItem)
{
targetItem.IsHighlighted = true;
float prevCondition = targetItem.Condition;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
var levelResource = targetItem.GetComponent<LevelResource>();
@@ -300,9 +354,9 @@ namespace Barotrauma.Items.Components
targetItem.WorldPosition,
levelResource.DeattachTimer / levelResource.DeattachDuration,
Color.Red, Color.Green);
#endif
#endif
}
FixItemProjSpecific(user, deltaTime, targetItem, prevCondition);
FixItemProjSpecific(user, deltaTime, targetItem);
return true;
}
return false;
@@ -310,13 +364,12 @@ namespace Barotrauma.Items.Components
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem);
private float sinTime;
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
Gap leak = objective.OperateTarget as Gap;
if (leak == null) return true;
if (!(objective.OperateTarget is Gap leak)) return true;
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
float dist = fromItemToLeak.Length();
@@ -461,7 +514,7 @@ namespace Barotrauma.Items.Components
}
}
}
#endif
#endif
}
}
}
@@ -64,14 +64,19 @@ namespace Barotrauma.Items.Components
return;
}
if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot))
throwing = true;
if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; }
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.IsKeyDown(InputType.Aim) && !throwing) throwPos = 0.0f;
if (picker.IsUnconscious || picker.IsDead || !picker.AllowInput)
{
throwing = false;
aim = false;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
if (item.body.Dir != picker.AnimController.Dir) Flip();
if (item.body.Dir != picker.AnimController.Dir) { Flip(); }
AnimController ac = picker.AnimController;
@@ -79,7 +84,6 @@ namespace Barotrauma.Items.Components
if (!throwing)
{
bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
if (aim)
{
throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
@@ -123,7 +127,8 @@ namespace Barotrauma.Items.Components
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower); //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, thrower, user: thrower);
}
throwing = false;
}
@@ -65,20 +65,25 @@ namespace Barotrauma.Items.Components
}
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
public float IsActiveTimer;
public virtual bool IsActive
{
get { return isActive; }
set
{
#if CLIENT
if (!value && isActive)
if (!value)
{
StopSounds(ActionType.OnActive);
IsActiveTimer = 0.0f;
if (isActive)
{
StopSounds(ActionType.OnActive);
}
}
#endif
if (AITarget != null) AITarget.Enabled = value;
isActive = value;
isActive = value;
}
}
@@ -384,7 +389,10 @@ namespace Barotrauma.Items.Components
item.Use(1.0f);
break;
case "toggle":
IsActive = !isActive;
if (signal != "0")
{
IsActive = !isActive;
}
break;
case "set_active":
case "set_state":
@@ -410,8 +418,10 @@ namespace Barotrauma.Items.Components
{
if (item.ParentInventory != null)
{
Character owner = (Character)item.ParentInventory.Owner;
if (owner != null && owner.HasSelectedItem(item)) item.Unequip(owner);
if (item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(item))
{
item.Unequip(owner);
}
item.ParentInventory.RemoveItem(item);
}
Entity.Spawner.AddToRemoveQueue(item);
@@ -424,8 +434,10 @@ namespace Barotrauma.Items.Components
{
if (this.Item.ParentInventory != null)
{
Character owner = (Character)this.Item.ParentInventory.Owner;
if (owner != null && owner.HasSelectedItem(this.Item)) this.Item.Unequip(owner);
if (this.Item.ParentInventory.Owner is Character owner && owner.HasSelectedItem(this.Item))
{
this.Item.Unequip(owner);
}
this.Item.ParentInventory.RemoveItem(this.Item);
}
Entity.Spawner.AddToRemoveQueue(this.Item);
@@ -561,14 +573,14 @@ namespace Barotrauma.Items.Components
public virtual void FlipY(bool relativeToSub) { }
public bool HasRequiredContainedItems(bool addMessage, string msg = null)
public bool HasRequiredContainedItems(Character user, bool addMessage, string msg = null)
{
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) return true;
if (item.OwnInventory == null) return false;
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Contained])
{
if (!item.OwnInventory.Items.Any(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)))
if (!ri.CheckRequirements(user, item))
{
#if CLIENT
msg = msg ?? ri.Msg;
@@ -74,14 +74,14 @@ namespace Barotrauma.Items.Components
}
}
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(30.0f, true)]
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(120.0f, true)]
public float MeltdownDelay
{
get { return meltDownDelay; }
set { meltDownDelay = Math.Max(value, 0.0f); }
}
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(10.0f, true)]
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(30.0f, true)]
public float FireDelay
{
get { return fireDelay; }
@@ -132,6 +132,13 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, true)]
public bool TemperatureCritical
{
get { return temperature > allowedTemperature.Y; }
set { /*do nothing*/ }
}
private float correctTurbineOutput;
private float targetFissionRate;
@@ -384,6 +391,14 @@ namespace Barotrauma.Items.Components
float prevFireTimer = fireTimer;
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
#if SERVER
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item)
{
GameMain.Server.KarmaManager.OnReactorOverHeating(blameOnBroken.Character, deltaTime);
}
#endif
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
{
new FireSource(item.WorldPosition);
@@ -437,14 +452,8 @@ namespace Barotrauma.Items.Components
private void MeltDown()
{
if (item.Condition <= 0.0f) return;
#if CLIENT
if (GameMain.Client != null) return;
#endif
#if SERVER
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
#endif
if (item.Condition <= 0.0f) { return; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
item.Condition = 0.0f;
fireTimer = 0.0f;
@@ -461,9 +470,10 @@ namespace Barotrauma.Items.Components
}
#if SERVER
if (GameMain.Server != null && GameMain.Server.ConnectedClients.Contains(blameOnBroken))
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
if (GameMain.Server != null)
{
blameOnBroken.Karma = 0.0f;
GameMain.Server.KarmaManager.OnReactorMeltdown(blameOnBroken?.Character);
}
#endif
}
@@ -9,6 +9,12 @@ namespace Barotrauma.Items.Components
{
partial class Sonar : Powered, IServerSerializable, IClientSerializable
{
public enum Mode
{
Active,
Passive
};
public const float DefaultSonarRange = 10000.0f;
class ConnectedTransducer
@@ -35,18 +41,30 @@ namespace Barotrauma.Items.Components
private float range;
private float pingState;
private const float PingFrequency = 0.5f;
private Mode currentMode = Mode.Passive;
private class ActivePing
{
public float State;
public bool IsDirectional;
public Vector2 Direction;
public float PrevPingRadius;
}
// rotating list of currently active pings
private ActivePing[] activePings = new ActivePing[8];
// total number of currently active pings, range [0, activePings.Length[
private int activePingsCount;
// currently active ping index on the above list
private int currentPingIndex = -1;
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
private float zoom = 1.0f;
private bool useDirectionalPing = false;
private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f);
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
//was the last ping sent with directional pinging
private bool isLastPingDirectional;
private Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
private Sprite sonarBlip;
private Sprite lineSprite;
@@ -86,24 +104,24 @@ namespace Barotrauma.Items.Components
{
get { return zoom; }
}
public override bool IsActive
{
get
{
return base.IsActive;
}
public Mode CurrentMode
{
get => currentMode;
set
{
base.IsActive = value;
if (!value && item.CurrentHull != null)
currentMode = value;
if (value == Mode.Passive)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
currentPingIndex = -1;
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
}
#if CLIENT
if (activeTickBox != null) activeTickBox.Selected = value;
if (passiveTickBox != null) passiveTickBox.Selected = !value;
if (activeTickBox != null) activeTickBox.Selected = value == Mode.Active;
if (passiveTickBox != null) passiveTickBox.Selected = value == Mode.Passive;
#endif
}
}
@@ -112,8 +130,9 @@ namespace Barotrauma.Items.Components
: base(item, element)
{
connectedTransducers = new List<ConnectedTransducer>();
IsActive = false;
CurrentMode = Mode.Passive;
IsActive = true;
InitProjSpecific(element);
}
@@ -133,40 +152,80 @@ namespace Barotrauma.Items.Components
}
connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f);
}
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
(!UseTransducers || connectedTransducers.Count > 0))
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
{
pingState = pingState + deltaTime * 0.5f;
if (pingState > 1.0f)
activePings[pingIndex].State += deltaTime * PingFrequency;
}
if (currentMode == Mode.Active)
{
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
(!UseTransducers || connectedTransducers.Count > 0))
{
if (currentPingIndex != -1)
{
var activePing = activePings[currentPingIndex];
if (activePing.State > 1.0f)
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.CurrentHull.AiTarget.SoundRange);
item.CurrentHull.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f;
item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = Math.Max(Range * activePing.State / zoom, item.AiTarget.SoundRange);
item.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f;
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
aiPingCheckPending = true;
currentPingIndex = -1;
}
}
if (currentPingIndex == -1 && activePingsCount < activePings.Length)
{
currentPingIndex = activePingsCount++;
if (activePings[currentPingIndex] == null)
{
activePings[currentPingIndex] = new ActivePing();
}
activePings[currentPingIndex].IsDirectional = useDirectionalPing;
activePings[currentPingIndex].Direction = pingDirection;
activePings[currentPingIndex].State = 0.0f;
activePings[currentPingIndex].PrevPingRadius = 0.0f;
item.Use(deltaTime);
}
}
else
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange);
item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange);
item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
aiPingCheckPending = true;
isLastPingDirectional = useDirectionalPing;
lastPingDirection = pingDirection;
item.Use(deltaTime);
pingState = 0.0f;
currentPingIndex = -1;
aiPingCheckPending = false;
}
}
else
for (var pingIndex = 0; pingIndex < activePingsCount;)
{
if (item.CurrentHull != null)
if (activePings[pingIndex].State > 1.0f)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
var lastIndex = --activePingsCount;
var oldActivePing = activePings[pingIndex];
activePings[pingIndex] = activePings[lastIndex];
activePings[lastIndex] = oldActivePing;
if (currentPingIndex == lastIndex)
{
currentPingIndex = pingIndex;
}
}
else
{
++pingIndex;
}
aiPingCheckPending = false;
pingState = 0.0f;
}
Voltage -= deltaTime;
@@ -174,7 +233,7 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
return pingState > 1.0f;
return currentPingIndex != -1;
}
protected override void RemoveComponentSpecific()
@@ -189,7 +248,7 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (!IsActive || !aiPingCheckPending) return false;
if (currentMode == Mode.Passive || !aiPingCheckPending) return false;
Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
@@ -301,13 +360,13 @@ namespace Barotrauma.Items.Components
}
}
if (!item.CanClientAccess(c)) return;
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
CurrentMode = isActive ? Mode.Active : Mode.Passive;
//TODO: cleanup
#if CLIENT
activeTickBox.Selected = IsActive;
activeTickBox.Selected = currentMode == Mode.Active;
#endif
if (isActive)
{
@@ -331,8 +390,8 @@ namespace Barotrauma.Items.Components
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(IsActive);
if (IsActive)
msg.Write(currentMode == Mode.Active);
if (currentMode == Mode.Active)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
@@ -198,11 +198,7 @@ namespace Barotrauma.Items.Components
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
{
#if CLIENT
if (sparkSounds.Count > 0)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, pt.item.WorldPosition, sparkSound.Volume, sparkSound.Range, pt.item.CurrentHull);
}
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
@@ -333,22 +329,22 @@ namespace Barotrauma.Items.Components
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (recipient?.Item == null) continue;
if (recipient?.Item == null || !recipient.IsPower) { continue; }
Item it = recipient.Item;
if (it.Condition <= 0.0f) continue;
if (it.Condition <= 0.0f) { continue; }
foreach (ItemComponent ic in it.Components)
{
if (!(ic is Powered powered) || !powered.IsActive) continue;
if (connectedList.Contains(powered)) continue;
if (!(ic is Powered powered) || !powered.IsActive) { continue; }
if (connectedList.Contains(powered)) { continue; }
if (powered is PowerTransfer powerTransfer)
{
RelayComponent otherRelayComponent = powerTransfer as RelayComponent;
if ((thisRelayComponent == null) == (otherRelayComponent == null))
{
if (!powerTransfer.CanTransfer) continue;
if (!powerTransfer.CanTransfer) { continue; }
powerTransfer.CheckJunctions(deltaTime, increaseUpdateCount, clampPower, clampLoad);
}
else
@@ -358,7 +354,7 @@ namespace Barotrauma.Items.Components
float maxPowerOut = (thisRelayComponent != null && !c.IsOutput) ? 0.0f : clampLoad;
if (maxPowerIn > 0.0f || maxPowerOut > 0.0f)
{
powerTransfer.CheckJunctions(deltaTime, false, maxPowerIn, maxPowerOut);
powerTransfer.CheckJunctions(deltaTime, false, maxPowerIn, maxPowerOut);
}
}
@@ -455,7 +451,7 @@ namespace Barotrauma.Items.Components
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.effects)
foreach (StatusEffect effect in recipient.Effects)
{
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
@@ -45,7 +45,10 @@ namespace Barotrauma.Items.Components
set
{
base.IsActive = value;
if (!value) currPowerConsumption = 0.0f;
if (!value)
{
currPowerConsumption = 0.0f;
}
}
}
@@ -113,6 +113,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(1, false)]
public int HitScanCount
{
get;
set;
}
[Serialize(false, false)]
public bool RemoveOnHit
{
@@ -120,6 +127,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0.0f, false)]
public float Spread
{
get;
set;
}
public Projectile(Item item, XElement element)
: base (item, element)
{
@@ -154,17 +168,25 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (character != null && !characterUsable) return false;
if (character != null && !characterUsable) { return false; }
Vector2 launchDir = new Vector2((float)Math.Cos(item.body.Rotation), (float)Math.Sin(item.body.Rotation));
if (Hitscan)
for (int i = 0; i < HitScanCount; i++)
{
DoHitscan(launchDir);
}
else
{
Launch(launchDir * launchImpulse * item.body.Mass);
float launchAngle = item.body.Rotation + MathHelper.ToRadians(Rand.Range(-Spread, Spread));
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
if (Hitscan)
{
Vector2 prevSimpos = item.SimPosition;
DoHitscan(launchDir);
if (i < HitScanCount - 1)
{
item.SetTransform(prevSimpos, item.body.Rotation);
}
}
else
{
Launch(launchDir * launchImpulse * item.body.Mass);
}
}
User = character;
@@ -306,6 +328,9 @@ namespace Barotrauma.Items.Components
!fixture.CollisionCategories.HasFlag(Physics.CollisionWall) &&
!fixture.CollisionCategories.HasFlag(Physics.CollisionLevel)) return true;
fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
hits.Add(new HitscanResult(fixture, rayStart, -dir, 0.0f));
return true;
}, ref aabb);
@@ -189,7 +189,11 @@ namespace Barotrauma.Items.Components
}
else
{
item.Condition += deltaTime / (fixDuration / item.MaxCondition);
float conditionIncrease = deltaTime / (fixDuration / item.MaxCondition);
item.Condition += conditionIncrease;
#if SERVER
GameMain.Server.KarmaManager.OnItemRepaired(CurrentFixer, this, conditionIncrease);
#endif
}
if (wasBroken && item.IsFullCondition)
@@ -24,7 +24,7 @@ namespace Barotrauma.Items.Components
public readonly bool IsOutput;
public readonly List<StatusEffect> effects;
public readonly List<StatusEffect> Effects;
public readonly ushort[] wireId;
@@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
effects = new List<StatusEffect>();
Effects = new List<StatusEffect>();
wireId = new ushort[MaxLinked];
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
break;
case "statuseffect":
effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name));
Effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name));
break;
}
}
@@ -222,6 +222,7 @@ namespace Barotrauma.Items.Components
recipientsDirty = true;
if (wire != null)
{
ConnectionPanel.DisconnectedWires.Remove(wire);
var otherConnection = wire.OtherConnection(this);
if (otherConnection != null)
{
@@ -251,10 +252,10 @@ namespace Barotrauma.Items.Components
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.effects)
foreach (StatusEffect effect in recipient.Effects)
{
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false);
}
}
}
@@ -277,11 +278,9 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < MaxLinked; i++)
{
if (wireId[i] == 0) continue;
if (wireId[i] == 0) { continue; }
Item wireItem = Entity.FindEntityByID(wireId[i]) as Item;
if (wireItem == null) continue;
if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; }
wires[i] = wireItem.GetComponent<Wire>();
recipientsDirty = true;
@@ -15,6 +15,11 @@ namespace Barotrauma.Items.Components
private Character user;
/// <summary>
/// Wires that have been disconnected from the panel, but not removed completely (visible at the bottom of the connection panel).
/// </summary>
public readonly HashSet<Wire> DisconnectedWires = new HashSet<Wire>();
[Serialize(false, true), Editable(ToolTip = "Locked connection panels cannot be rewired in-game.")]
public bool Locked
{
@@ -103,6 +108,23 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
#if CLIENT
foreach (Wire wire in DisconnectedWires)
{
if (Rand.Range(0.0f, 500.0f) < 1.0f)
{
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = new Vector2(0.0f, -100.0f);
for (int i = 0; i < 5; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) { particle.Size *= Rand.Range(0.5f, 1.0f); }
}
}
}
#endif
if (user == null || user.SelectedConstruction != item)
{
user = null;
@@ -192,11 +214,12 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
DisconnectedWires.Clear();
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
{
if (wire == null) continue;
if (wire == null) { continue; }
if (wire.OtherConnection(c) == null) //wire not connected to anything else
{
@@ -219,6 +242,12 @@ namespace Barotrauma.Items.Components
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
msg.Write((ushort)DisconnectedWires.Count());
foreach (Wire disconnectedWire in DisconnectedWires)
{
msg.Write(disconnectedWire.Item.ID);
}
}
}
}
@@ -165,6 +165,10 @@ namespace Barotrauma.Items.Components
{
base.OnItemLoaded();
itemLoaded = true;
#if CLIENT
light.Color = IsActive ? lightColor : Color.Transparent;
if (!IsActive) lightBrightness = 0.0f;
#endif
}
public override void Update(float deltaTime, Camera cam)
@@ -217,10 +221,9 @@ namespace Barotrauma.Items.Components
if (Rand.Range(0.0f, 1.0f) < 0.05f && voltage < Rand.Range(0.0f, minVoltage))
{
#if CLIENT
if (voltage > 0.1f && sparkSounds.Count > 0)
if (voltage > 0.1f)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, item.WorldPosition, sparkSound.Volume, sparkSound.Range, item.CurrentHull);
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
}
#endif
lightBrightness = 0.0f;
@@ -1,6 +1,7 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -11,6 +12,17 @@ namespace Barotrauma.Items.Components
private bool isOn;
private static readonly Dictionary<string, string> connectionPairs = new Dictionary<string, string>
{
{ "power_in", "power_out"},
{ "signal_in", "signal_out" },
{ "signal_in1", "signal_out1" },
{ "signal_in2", "signal_out2" },
{ "signal_in3", "signal_out3" },
{ "signal_in4", "signal_out4" },
{ "signal_in5", "signal_out5" }
};
[Editable, Serialize(1000.0f, true)]
public float MaxPower
{
@@ -59,17 +71,11 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.IsPower || item.Condition <= 0.0f) return;
if (connection.IsPower || item.Condition <= 0.0f) { return; }
if (connection.Name.Contains("_in"))
if (connectionPairs.TryGetValue(connection.Name, out string outConnection))
{
if (!IsOn) return;
string outConnection = connection.Name.Contains("power_in") ? "power_out" : "signal_out";
int connectionNumber = -1;
int.TryParse(connection.Name.Substring(connection.Name.Length - 1, 1), out connectionNumber);
if (connectionNumber > 0) outConnection += connectionNumber;
if (!IsOn) { return; }
item.SendSignal(stepsTaken, signal, outConnection, sender, power, source, signalStrength);
}
else if (connection.Name == "toggle")
@@ -73,7 +73,7 @@ namespace Barotrauma.Items.Components
public bool CanTransmit()
{
return HasRequiredContainedItems(true);
return HasRequiredContainedItems(user: null, addMessage: false);
}
public IEnumerable<WifiComponent> GetReceiversInRange()
@@ -89,7 +89,7 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
return HasRequiredContainedItems(false);
return HasRequiredContainedItems(user: null, addMessage: false);
}
public override void Update(float deltaTime, Camera cam)
@@ -16,8 +16,8 @@ namespace Barotrauma.Items.Components
private Vector2 start;
private Vector2 end;
private float angle;
private float length;
private readonly float angle;
private readonly float length;
public Vector2 Start
{
@@ -45,7 +45,7 @@ namespace Barotrauma.Items.Components
const int MaxNodesPerNetworkEvent = 30;
private List<Vector2> nodes;
private List<WireSection> sections;
private readonly List<WireSection> sections;
private Connection[] connections;
@@ -85,24 +85,23 @@ namespace Barotrauma.Items.Components
#if CLIENT
if (wireSprite == null)
{
wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f));
wireSprite.Depth = 0.85f;
wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f))
{
Depth = 0.85f
};
}
#endif
nodes = new List<Vector2>();
sections = new List<WireSection>();
connections = new Connection[2];
connections = new Connection[2];
IsActive = false;
}
public Connection OtherConnection(Connection connection)
{
if (connection == null) return null;
if (connection == connections[0]) return connections[1];
if (connection == connections[1]) return connections[0];
if (connection == connections[0]) { return connections[1]; }
if (connection == connections[1]) { return connections[0]; }
return null;
}
@@ -133,8 +132,8 @@ namespace Barotrauma.Items.Components
public void RemoveConnection(Connection connection)
{
if (connection == connections[0]) connections[0] = null;
if (connection == connections[1]) connections[1] = null;
if (connection == connections[0]) { connections[0] = null; }
if (connection == connections[1]) { connections[1] = null; }
SetConnectedDirty();
}
@@ -143,10 +142,10 @@ namespace Barotrauma.Items.Components
{
for (int i = 0; i < 2; i++)
{
if (connections[i] == newConnection) return false;
if (connections[i] == newConnection) { return false; }
}
if (!connections.Any(c => c == null)) return false;
if (!connections.Any(c => c == null)) { return false; }
for (int i = 0; i < 2; i++)
{
@@ -156,37 +155,39 @@ namespace Barotrauma.Items.Components
break;
}
}
if (item.body != null) { item.Submarine = newConnection.Item.Submarine; }
if (item.body != null) item.Submarine = newConnection.Item.Submarine;
newConnection.ConnectionPanel.DisconnectedWires.Remove(this);
for (int i = 0; i < 2; i++)
{
if (connections[i] != null) continue;
if (connections[i] != null) { continue; }
connections[i] = newConnection;
FixNodeEnds();
if (!addNode) break;
if (!addNode) { break; }
Submarine refSub = newConnection.Item.Submarine;
if (refSub == null)
{
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
if (attachTarget == null) continue;
if (attachTarget == null) { continue; }
refSub = attachTarget.Submarine;
}
Vector2 nodePos = refSub == null ?
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
int newNodeIndex = 0;
if (nodes.Count > 1)
{
if (Vector2.DistanceSquared(nodes[nodes.Count-1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
{
newNodeIndex = nodes.Count;
}
@@ -244,21 +245,18 @@ namespace Barotrauma.Items.Components
public override void Equip(Character character)
{
ClearConnections(character);
IsActive = true;
}
public override void Unequip(Character character)
{
ClearConnections(character);
IsActive = false;
}
public override void Drop(Character dropper)
{
ClearConnections(dropper);
ClearConnections(dropper);
IsActive = false;
}
@@ -399,7 +397,6 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
ClearConnections(picker);
return true;
}
@@ -467,9 +464,26 @@ namespace Barotrauma.Items.Components
nodes.Clear();
sections.Clear();
foreach (Item item in Item.ItemList)
{
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this))
{
#if SERVER
item.CreateServerEvent(connectionPanel);
#endif
connectionPanel.DisconnectedWires.Remove(this);
}
}
#if SERVER
if (user != null)
{
if (connections[0] != null || connections[1] != null)
{
GameMain.Server.KarmaManager.OnWireDisconnected(user, this);
}
if (connections[0] != null && connections[1] != null)
{
GameServer.Log(user.LogName + " disconnected a wire from " +
@@ -488,17 +502,21 @@ namespace Barotrauma.Items.Components
}
}
#endif
SetConnectedDirty();
for (int i = 0; i < 2; i++)
{
if (connections[i] == null) continue;
if (connections[i] == null) { continue; }
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex == -1) continue;
if (wireIndex == -1) { continue; }
#if SERVER
if (!connections[i].Item.Removed)
{
connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent<ConnectionPanel>());
}
#endif
connections[i].SetWire(wireIndex, null);
connections[i] = null;
}
@@ -565,7 +583,27 @@ namespace Barotrauma.Items.Components
}
} while (removed);
}
private void FixNodeEnds()
{
if (connections[0] == null || connections[1] == null || nodes.Count == 0) { return; }
Vector2 nodePos = nodes[0];
Submarine refSub = connections[0].Item.Submarine ?? connections[1].Item.Submarine;
if (refSub != null) { nodePos += refSub.HiddenSubPosition; }
float dist1 = Vector2.DistanceSquared(connections[0].Item.Position, nodePos);
float dist2 = Vector2.DistanceSquared(connections[1].Item.Position, nodePos);
//first node is closer to the second item
//= the nodes are "backwards", need to reverse them
if (dist1 > dist2)
{
nodes.Reverse();
UpdateSections();
}
}
private int GetClosestNodeIndex(Vector2 pos, float maxDist, out float closestDist)
@@ -640,12 +678,8 @@ namespace Barotrauma.Items.Components
string[] nodeCoords = nodeString.Split(';');
for (int i = 0; i < nodeCoords.Length / 2; i++)
{
float x = 0.0f, y = 0.0f;
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out x);
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out y);
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
nodes.Add(new Vector2(x, y));
}
@@ -687,7 +721,6 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
ClearConnections();
base.RemoveComponentSpecific();
}
@@ -17,7 +17,8 @@ namespace Barotrauma
Moustache,
FaceAttachment,
JobIndicator,
Husk
Husk,
Herpes
}
class WearableSprite
@@ -101,6 +102,7 @@ namespace Barotrauma
case WearableType.FaceAttachment:
case WearableType.JobIndicator:
case WearableType.Husk:
case WearableType.Herpes:
Limb = LimbType.Head;
HideLimb = false;
HideOtherWearables = false;
@@ -207,6 +209,12 @@ namespace Barotrauma.Items.Components
{
get { return damageModifiers; }
}
private bool autoEquipWhenFull;
public bool AutoEquipWhenFull
{
get { return autoEquipWhenFull; }
}
public Wearable(Item item, XElement element) : base(item, element)
{
@@ -220,6 +228,7 @@ namespace Barotrauma.Items.Components
wearableSprites = new WearableSprite[spriteCount];
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
autoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
int i = 0;
foreach (XElement subElement in element.Elements())
{
@@ -206,7 +206,7 @@ namespace Barotrauma
set
{
if (scale == value) { return; }
scale = MathHelper.Clamp(value, 0.1f, 10.0f);
scale = MathHelper.Clamp(value, 0.01f, 10.0f);
float relativeScale = scale / prefab.Scale;
@@ -288,7 +288,7 @@ namespace Barotrauma
/// <summary>
/// Can be used by status effects or conditionals to modify the sound range
/// </summary>
public float SoundRange
public new float SoundRange
{
get { return aiTarget == null ? 0.0f : aiTarget.SoundRange; }
set { if (aiTarget != null) { aiTarget.SoundRange = Math.Max(0.0f, value); } }
@@ -298,7 +298,7 @@ namespace Barotrauma
/// <summary>
/// Can be used by status effects or conditionals to modify the sound range
/// </summary>
public float SightRange
public new float SightRange
{
get { return aiTarget == null ? 0.0f : aiTarget.SightRange; }
set { if (aiTarget != null) { aiTarget.SightRange = Math.Max(0.0f, value); } }
@@ -1154,7 +1154,14 @@ namespace Barotrauma
{
ic.Update(deltaTime, cam);
#if CLIENT
if (ic.IsActive) ic.PlaySound(ActionType.OnActive, WorldPosition);
if (ic.IsActive)
{
if (ic.IsActiveTimer > 0.02f)
{
ic.PlaySound(ActionType.OnActive, WorldPosition);
}
ic.IsActiveTimer += deltaTime;
}
#endif
}
}
@@ -1381,7 +1388,7 @@ namespace Barotrauma
return connectedComponents;
}
private static readonly Pair<string, string>[] connectionPairs = new Pair<string, string>[]
public static readonly Pair<string, string>[] connectionPairs = new Pair<string, string>[]
{
new Pair<string, string>("power_in", "power_out"),
new Pair<string, string>("signal_in1", "signal_out1"),
@@ -1453,11 +1460,11 @@ namespace Barotrauma
public void SendSignal(int stepsTaken, string signal, string connectionName, Character sender, float power = 0.0f, Item source = null, float signalStrength = 1.0f)
{
LastSentSignalRecipients.Clear();
if (connections == null) return;
if (connections == null) { return; }
stepsTaken++;
if (!connections.TryGetValue(connectionName, out Connection c)) return;
if (!connections.TryGetValue(connectionName, out Connection c)) { return; }
if (stepsTaken > 10)
{
@@ -1467,6 +1474,11 @@ namespace Barotrauma
}
else
{
foreach (StatusEffect effect in c.Effects)
{
if (condition <= 0.0f && effect.type != ActionType.OnBroken) { continue; }
if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); }
}
c.SendSignal(stepsTaken, signal, source ?? this, sender, power, signalStrength);
}
}
@@ -1519,47 +1531,50 @@ namespace Barotrauma
foreach (ItemComponent ic in components)
{
bool pickHit = false, selectHit = false;
if (Screen.Selected == GameMain.SubEditorScreen)
if (picker.IsKeyDown(InputType.Aim))
{
pickHit = picker.IsKeyHit(InputType.Select);
selectHit = picker.IsKeyHit(InputType.Select);
pickHit = false;
selectHit = false;
}
else
{
if (picker.IsKeyDown(InputType.Aim))
if (forceSelectKey)
{
pickHit = false;
selectHit = false;
if (ic.PickKey == InputType.Select) pickHit = true;
if (ic.SelectKey == InputType.Select) selectHit = true;
}
else if (forceActionKey)
{
if (ic.PickKey == InputType.Use) pickHit = true;
if (ic.SelectKey == InputType.Use) selectHit = true;
}
else
{
if (forceSelectKey)
{
if (ic.PickKey == InputType.Select) pickHit = true;
if (ic.SelectKey == InputType.Select) selectHit = true;
}
else if (forceActionKey)
{
if (ic.PickKey == InputType.Use) pickHit = true;
if (ic.SelectKey == InputType.Use) selectHit = true;
}
else
{
pickHit = picker.IsKeyHit(ic.PickKey);
selectHit = picker.IsKeyHit(ic.SelectKey);
pickHit = picker.IsKeyHit(ic.PickKey);
selectHit = picker.IsKeyHit(ic.SelectKey);
#if CLIENT
//if the cursor is on a UI component, disable interaction with the left mouse button
//to prevent accidentally selecting items when clicking UI elements
if (picker == Character.Controlled && GUI.MouseOn != null)
{
if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false;
if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false;
}
#endif
//if the cursor is on a UI component, disable interaction with the left mouse button
//to prevent accidentally selecting items when clicking UI elements
if (picker == Character.Controlled && GUI.MouseOn != null)
{
if (GameMain.Config.KeyBind(ic.PickKey).MouseButton == 0) pickHit = false;
if (GameMain.Config.KeyBind(ic.SelectKey).MouseButton == 0) selectHit = false;
}
#endif
}
}
#if CLIENT
//use the non-mouse interaction key (E on both default and legacy keybinds) in wiring mode
//LMB is used to manipulate wires, so using E to select connection panels is much easier
if (Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode)
{
pickHit = selectHit = GameMain.Config.KeyBind(InputType.Use).MouseButton == null ?
picker.IsKeyHit(InputType.Use) :
picker.IsKeyHit(InputType.Select);
}
#endif
if (!pickHit && !selectHit) continue;
@@ -1621,8 +1636,8 @@ namespace Barotrauma
return;
}
if (condition == 0.0f) return;
if (condition == 0.0f) { return; }
bool remove = false;
foreach (ItemComponent ic in components)
@@ -1631,7 +1646,7 @@ namespace Barotrauma
#if CLIENT
isControlled = character == Character.Controlled;
#endif
if (!ic.HasRequiredContainedItems(isControlled)) continue;
if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; }
if (ic.Use(deltaTime, character))
{
ic.WasUsed = true;
@@ -1642,7 +1657,7 @@ namespace Barotrauma
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb);
if (ic.DeleteOnUse) remove = true;
if (ic.DeleteOnUse) { remove = true; }
}
}
@@ -1654,7 +1669,7 @@ namespace Barotrauma
public void SecondaryUse(float deltaTime, Character character = null)
{
if (condition == 0.0f) return;
if (condition == 0.0f) { return; }
bool remove = false;
@@ -1664,7 +1679,7 @@ namespace Barotrauma
#if CLIENT
isControlled = character == Character.Controlled;
#endif
if (!ic.HasRequiredContainedItems(isControlled)) continue;
if (!ic.HasRequiredContainedItems(character, isControlled)) { continue; }
if (ic.SecondaryUse(deltaTime, character))
{
ic.WasUsed = true;
@@ -1675,7 +1690,7 @@ namespace Barotrauma
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character);
if (ic.DeleteOnUse) remove = true;
if (ic.DeleteOnUse) { remove = true; }
}
}
@@ -1702,7 +1717,7 @@ namespace Barotrauma
bool remove = false;
foreach (ItemComponent ic in components)
{
if (!ic.HasRequiredContainedItems(user == Character.Controlled)) continue;
if (!ic.HasRequiredContainedItems(user, addMessage: user == Character.Controlled)) continue;
bool success = Rand.Range(0.0f, 0.5f) < ic.DegreeOfSuccess(user);
ActionType actionType = success ? ActionType.OnUse : ActionType.OnFailure;
@@ -1713,7 +1728,7 @@ namespace Barotrauma
ic.WasUsed = true;
ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user);
if (GameMain.NetworkMember!=null && GameMain.NetworkMember.IsServer)
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[]
{
@@ -1729,11 +1744,15 @@ namespace Barotrauma
public bool Combine(Item item)
{
if (item == this) { return false; }
bool isCombined = false;
foreach (ItemComponent ic in components)
{
if (ic.Combine(item)) isCombined = true;
if (ic.Combine(item)) { isCombined = true; }
}
#if CLIENT
if (isCombined) { GameMain.Client?.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Combine, item.ID }); }
#endif
return isCombined;
}
@@ -412,8 +412,12 @@ namespace Barotrauma
}
XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc?.Root == null) { return; }
if (doc?.Root == null)
{
DebugConsole.ThrowError("File \"" + filePath + "\" could not be loaded.");
continue;
}
if (doc.Root.Name.ToString().ToLowerInvariant() == "items")
{
foreach (XElement element in doc.Root.Elements())
@@ -95,41 +95,25 @@ namespace Barotrauma
switch (type)
{
case RelationType.Contained:
if (parentItem == null) return false;
var containedItems = parentItem.ContainedItems;
if (containedItems == null) return false;
if (MatchOnEmpty && !containedItems.Any(ci => ci != null))
{
return true;
}
foreach (Item contained in containedItems)
{
if (contained.Condition > 0.0f && MatchesItem(contained)) return true;
}
break;
if (parentItem == null) { return false; }
return CheckContained(parentItem);
case RelationType.Container:
if (parentItem == null || parentItem.Container == null) return false;
if (parentItem == null || parentItem.Container == null) { return false; }
return parentItem.Container.Condition > 0.0f && MatchesItem(parentItem.Container);
case RelationType.Equipped:
if (character == null) return false;
if (character == null) { return false; }
foreach (Item equippedItem in character.SelectedItems)
{
if (equippedItem == null) continue;
if (equippedItem.Condition > 0.0f && MatchesItem(equippedItem)) return true;
if (equippedItem == null) { continue; }
if (equippedItem.Condition > 0.0f && MatchesItem(equippedItem)) { return true; }
}
break;
case RelationType.Picked:
if (character == null || character.Inventory == null) return false;
if (character == null || character.Inventory == null) { return false; }
foreach (Item pickedItem in character.Inventory.Items)
{
if (pickedItem == null) continue;
if (MatchesItem(pickedItem)) return true;
if (pickedItem == null) { continue; }
if (MatchesItem(pickedItem)) { return true; }
}
break;
default:
@@ -139,6 +123,25 @@ namespace Barotrauma
return false;
}
private bool CheckContained(Item parentItem)
{
var containedItems = parentItem.ContainedItems;
if (containedItems == null) { return false; }
if (MatchOnEmpty && !containedItems.Any(ci => ci != null))
{
return true;
}
foreach (Item contained in containedItems)
{
if (contained == null) { continue; }
if (contained.Condition > 0.0f && MatchesItem(contained)) { return true; }
if (CheckContained(contained)) { return true; }
}
return false;
}
public void Save(XElement element)
{
element.Add(
@@ -77,7 +77,7 @@ namespace Barotrauma
return prevExplosions.FindAll(e => e.Third >= Timing.TotalTime - maxSecondsAgo);
}
public void Explode(Vector2 worldPosition, Entity damageSource)
public void Explode(Vector2 worldPosition, Entity damageSource, Character attacker = null)
{
prevExplosions.Add(new Triplet<Explosion, Vector2, float>(this, worldPosition, (float)Timing.TotalTime));
if (prevExplosions.Count > 100)
@@ -98,7 +98,7 @@ namespace Barotrauma
if (attack.GetStructureDamage(1.0f) > 0.0f)
{
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f));
RangedStructureDamage(worldPosition, displayRange, attack.GetStructureDamage(1.0f), attacker);
}
if (empStrength > 0.0f)
@@ -130,7 +130,7 @@ namespace Barotrauma
if (force == 0.0f && attack.Stun == 0.0f && attack.GetTotalDamage(false) == 0.0f) return;
DamageCharacters(worldPosition, attack, force, damageSource);
DamageCharacters(worldPosition, attack, force, damageSource, attacker);
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
@@ -163,17 +163,9 @@ namespace Barotrauma
}
partial void ExplodeProjSpecific(Vector2 worldPosition, Hull hull);
private Vector2 ClampParticlePos(Vector2 particlePos, Hull hull)
{
if (hull == null) return particlePos;
return new Vector2(
MathHelper.Clamp(particlePos.X, hull.WorldRect.X, hull.WorldRect.Right),
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
}
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource)
public static void DamageCharacters(Vector2 worldPosition, Attack attack, float force, Entity damageSource, Character attacker)
{
if (attack.Range <= 0.0f) return;
@@ -222,11 +214,13 @@ namespace Barotrauma
modifiedAfflictions.Add(affliction.CreateMultiplied(distFactor / c.AnimController.Limbs.Length));
}
c.LastDamageSource = damageSource;
Character attacker = null;
if (damageSource is Item item)
if (attacker == null)
{
attacker = item.GetComponent<Projectile>()?.User;
if (attacker == null) attacker = item.GetComponent<MeleeWeapon>()?.User;
if (damageSource is Item item)
{
attacker = item.GetComponent<Projectile>()?.User;
if (attacker == null) attacker = item.GetComponent<MeleeWeapon>()?.User;
}
}
//use a position slightly from the limb's position towards the explosion
@@ -280,7 +274,7 @@ namespace Barotrauma
/// <summary>
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
/// </summary>
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage)
public static Dictionary<Structure, float> RangedStructureDamage(Vector2 worldPosition, float worldRange, float damage, Character attacker = null)
{
List<Structure> structureList = new List<Structure>();
float dist = 600.0f;
@@ -304,7 +298,7 @@ namespace Barotrauma
float distFactor = 1.0f - (Vector2.Distance(structure.SectionPosition(i, true), worldPosition) / worldRange);
if (distFactor <= 0.0f) continue;
structure.AddDamage(i, damage * distFactor);
structure.AddDamage(i, damage * distFactor, attacker);
if (damagedStructures.ContainsKey(structure))
{
@@ -853,7 +853,7 @@ namespace Barotrauma
return tooCloseCells;
}
private List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
public List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
{
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
@@ -57,7 +57,12 @@ namespace Barotrauma
get;
private set;
}
public override string ToString()
{
return $"LocationType (" + Identifier + ")";
}
private LocationType(XElement element)
{
Identifier = element.GetAttributeString("identifier", element.Name.ToString());
@@ -12,9 +12,13 @@ namespace Barotrauma
private set;
}
public Camera AssignedCamera;
private float duration;
private CoroutineHandle updateCoroutine;
public RoundEndCinematic(Submarine submarine, Camera cam, float duration)
public RoundEndCinematic(Submarine submarine, Camera cam, float duration = 10.0f)
: this(new List<Submarine>() { submarine }, cam, duration)
{
@@ -25,9 +29,19 @@ namespace Barotrauma
if (!submarines.Any(s => s != null)) return;
this.duration = duration;
AssignedCamera = cam;
Running = true;
CoroutineManager.StartCoroutine(Update(submarines, cam));
updateCoroutine = CoroutineManager.StartCoroutine(Update(submarines, cam));
}
public void Stop()
{
CoroutineManager.StopCoroutines(updateCoroutine);
Running = false;
#if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack;
#endif
}
private IEnumerable<object> Update(List<Submarine> subs, Camera cam)
@@ -72,6 +86,11 @@ namespace Barotrauma
(minPos.Y + maxPos.Y) / 2.0f);
cam.Translate(cameraPos - cam.Position);
foreach (Submarine sub in subs)
{
sub.PhysicsBody?.ResetDynamics();
}
#if CLIENT
cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration);
if (timer / duration > 0.9f)
@@ -816,8 +816,7 @@ namespace Barotrauma
}
partial void AdjustKarma(IDamageable attacker, float amount);
public AttackResult AddDamage(Character attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
{
@@ -972,23 +971,18 @@ namespace Barotrauma
bool hadHole = SectionBodyDisabled(sectionIndex);
Sections[sectionIndex].damage = MathHelper.Clamp(damage, 0.0f, Prefab.Health);
//otherwise it's possible to infinitely gain karma by welding fixed things
if (attacker != null && damageDiff != 0.0f)
{
AdjustKarma(attacker, damageDiff);
#if CLIENT
if (GameMain.Client == null)
OnHealthChangedProjSpecific(attacker, damageDiff);
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
{
#endif
if (damageDiff < 0.0f)
{
attacker.Info.IncreaseSkillLevel("mechanical",
-damageDiff * SkillIncreaseMultiplier / Math.Max(attacker.GetSkillLevel("mechanical"), 1.0f),
SectionPosition(sectionIndex, true));
}
#if CLIENT
}
#endif
}
bool hasHole = SectionBodyDisabled(sectionIndex);
@@ -998,6 +992,8 @@ namespace Barotrauma
UpdateSections();
}
partial void OnHealthChangedProjSpecific(Character attacker, float damageAmount);
public void SetCollisionCategory(Category collisionCategory)
{
if (Bodies == null) return;
@@ -745,6 +745,12 @@ namespace Barotrauma
private static readonly Dictionary<Body, float> bodyDist = new Dictionary<Body, float>();
private static readonly List<Body> bodies = new List<Body>();
public static float LastPickedBodyDist(Body body)
{
if (!bodyDist.ContainsKey(body)) { return 0.0f; }
return bodyDist[body];
}
/// <summary>
/// Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest body is at the beginning of the list).
/// </summary>
@@ -1067,16 +1073,14 @@ namespace Barotrauma
//Level.Loaded.Move(-amount);
}
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false)
public static Submarine FindClosest(Vector2 worldPosition, bool ignoreOutposts = false, bool ignoreOutsideLevel = true)
{
Submarine closest = null;
float closestDist = 0.0f;
foreach (Submarine sub in loaded)
{
if (ignoreOutposts && sub.IsOutpost)
{
continue;
}
if (ignoreOutposts && sub.IsOutpost) { continue; }
if (ignoreOutsideLevel && Level.Loaded != null && sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
float dist = Vector2.DistanceSquared(worldPosition, sub.WorldPosition);
if (closest == null || dist < closestDist)
{
@@ -1209,7 +1213,34 @@ namespace Barotrauma
foreach (string path in filePaths)
{
var sub = new Submarine(path);
if (!sub.IsFileCorrupted)
if (sub.IsFileCorrupted)
{
#if CLIENT
if (DebugConsole.IsOpen) { DebugConsole.Toggle(); }
var deleteSubPrompt = new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariable("SubLoadError", "[subname]", sub.name) +"\n"+
TextManager.GetWithVariable("DeleteFileVerification", "[filename]", sub.name),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
string filePath = path;
deleteSubPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
try
{
File.Delete(filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError($"Failed to delete file \"{filePath}\".", e);
}
deleteSubPrompt.Close();
return true;
};
deleteSubPrompt.Buttons[1].OnClicked += deleteSubPrompt.Close;
#endif
}
else
{
savedSubmarines.Add(sub);
}
@@ -1621,7 +1652,6 @@ namespace Barotrauma
if (wp.isObstructed) { continue; }
foreach (var connection in node.connections)
{
bool isObstructed = false;
var connectedWp = connection.Waypoint;
if (connectedWp.isObstructed) { continue; }
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition);
@@ -1652,7 +1682,6 @@ namespace Barotrauma
if (wp.isObstructed) { continue; }
foreach (var connection in node.connections)
{
bool isObstructed = false;
var connectedWp = connection.Waypoint;
if (connectedWp.isObstructed) { continue; }
Vector2 start = ConvertUnits.ToSimUnits(wp.WorldPosition) - otherSub.SimPosition;
@@ -1,4 +1,5 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -42,6 +43,27 @@ namespace Barotrauma.Networking
}
}
private Vector2 spectate_position;
public Vector2? SpectatePos
{
get
{
if (character == null || character.IsDead)
{
return spectate_position;
}
else
{
return null;
}
}
set
{
spectate_position = value.Value;
}
}
private bool muted;
public bool Muted
{
@@ -21,7 +21,8 @@ namespace Barotrauma.Networking
ServerLog = 0x100,
ManageSettings = 0x200,
ManagePermissions = 0x400,
All = 0x7ff
KarmaImmunity = 0x800,
All = 0xFFF
}
class PermissionPreset
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace Barotrauma
{
partial class KarmaManager : ISerializableEntity
{
public static readonly string ConfigFile = "Data" + Path.DirectorySeparatorChar + "karmasettings.xml";
public string Name => "KarmaManager";
public Dictionary<string, SerializableProperty> SerializableProperties { get; private set; }
[Serialize(0.1f, true)]
public float KarmaDecay { get; set; }
[Serialize(50.0f, true)]
public float KarmaDecayThreshold { get; set; }
[Serialize(0.15f, true)]
public float KarmaIncrease { get; set; }
[Serialize(50.0f, true)]
public float KarmaIncreaseThreshold { get; set; }
[Serialize(0.05f, true)]
public float StructureRepairKarmaIncrease { get; set; }
[Serialize(0.1f, true)]
public float StructureDamageKarmaDecrease { get; set; }
[Serialize(30.0f, true)]
public float MaxStructureDamageKarmaDecreasePerSecond { get; set; }
[Serialize(0.03f, true)]
public float ItemRepairKarmaIncrease { get; set; }
[Serialize(0.5f, true)]
public float ReactorOverheatKarmaDecrease { get; set; }
[Serialize(30.0f, true)]
public float ReactorMeltdownKarmaDecrease { get; set; }
[Serialize(0.1f, true)]
public float DamageEnemyKarmaIncrease { get; set; }
[Serialize(0.2f, true)]
public float HealFriendlyKarmaIncrease { get; set; }
[Serialize(0.25f, true)]
public float DamageFriendlyKarmaDecrease { get; set; }
[Serialize(1.0f, true)]
public float ExtinguishFireKarmaIncrease { get; set; }
private float allowedWireDisconnectionsPerMinute;
[Serialize(5.0f, true)]
public float AllowedWireDisconnectionsPerMinute
{
get { return allowedWireDisconnectionsPerMinute; }
set { allowedWireDisconnectionsPerMinute = Math.Max(0.0f, value); }
}
[Serialize(6.0f, true)]
public float WireDisconnectionKarmaDecrease { get; set; }
[Serialize(0.15f, true)]
public float SteerSubKarmaIncrease { get; set; }
[Serialize(15.0f, true)]
public float SpamFilterKarmaDecrease { get; set; }
[Serialize(40.0f, true)]
public float HerpesThreshold { get; set; }
[Serialize(1.0f, true)]
public float KickBanThreshold { get; set; }
[Serialize(10.0f, true)]
public float KarmaNotificationInterval { get; set; }
private readonly AfflictionPrefab herpesAffliction;
public Dictionary<string, XElement> Presets = new Dictionary<string, XElement>();
public KarmaManager()
{
XDocument doc = XMLExtensions.TryLoadXml(ConfigFile);
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc?.Root);
if (doc?.Root != null)
{
Presets["custom"] = doc.Root;
foreach (XElement subElement in doc.Root.Elements())
{
string presetName = subElement.GetAttributeString("name", "");
Presets[presetName.ToLowerInvariant()] = subElement;
}
SelectPreset("default");
}
herpesAffliction = AfflictionPrefab.List.Find(ap => ap.Identifier == "spaceherpes");
}
public void SelectPreset(string presetName)
{
if (string.IsNullOrEmpty(presetName)) { return; }
presetName = presetName.ToLowerInvariant();
if (Presets.ContainsKey(presetName))
{
SerializableProperty.DeserializeProperties(this, Presets[presetName]);
}
}
public void SaveCustomPreset()
{
if (Presets.ContainsKey("custom"))
{
SerializableProperty.SerializeProperties(this, Presets["custom"]);
}
}
public void Save()
{
XDocument doc = new XDocument(new XElement(Name));
foreach (KeyValuePair<string, XElement> preset in Presets)
{
doc.Root.Add(preset.Value);
}
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true
};
using (var writer = XmlWriter.Create(ConfigFile, settings))
{
doc.Save(writer);
}
}
}
}
@@ -15,7 +15,8 @@ namespace Barotrauma.Networking
ApplyStatusEffect,
ChangeProperty,
Control,
UpdateSkills
UpdateSkills,
Combine
}
public readonly Entity Entity;
@@ -35,7 +35,8 @@ namespace Barotrauma.Networking
CHAT_MESSAGE, //also self-explanatory
VOTE, //you get the idea
CHARACTER_INPUT,
ENTITY_STATE
ENTITY_STATE,
SPECTATING_POS
}
enum ClientNetError
@@ -168,7 +169,13 @@ namespace Barotrauma.Networking
updateInterval = new TimeSpan(0, 0, 0, 0, MathHelper.Clamp(1000 / serverSettings.TickRate, 1, 500));
}
}
public KarmaManager KarmaManager
{
get;
private set;
} = new KarmaManager();
public string Name
{
get { return name; }
@@ -214,7 +221,7 @@ namespace Barotrauma.Networking
var radioComponent = radio.GetComponent<WifiComponent>();
if (radioComponent == null) return false;
return radioComponent.HasRequiredContainedItems(false);
return radioComponent.HasRequiredContainedItems(sender, addMessage: false);
}
public void AddChatMessage(string message, ChatMessageType type, string senderName = "", Character senderCharacter = null)
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -18,10 +17,6 @@ namespace Barotrauma.Networking
}
private NetworkMember networkMember;
private State state;
private Submarine respawnShuttle;
private Steering shuttleSteering;
private List<Door> shuttleDoors;
@@ -31,46 +26,40 @@ namespace Barotrauma.Networking
public bool UsingShuttle
{
get { return respawnShuttle != null; }
get { return RespawnShuttle != null; }
}
/// <summary>
/// How long until the shuttle is dispatched with respawned characters
/// When will the shuttle be dispatched with respawned characters
/// </summary>
public float RespawnTimer
{
get { return respawnTimer; }
}
public DateTime RespawnTime { get; private set; }
/// <summary>
/// how long until the shuttle starts heading back out of the level
/// When will the sub start heading back out of the level
/// </summary>
public float TransportTimer
{
get { return shuttleTransportTimer; }
}
public DateTime ReturnTime { get; private set; }
public bool CountdownStarted
public bool RespawnCountdownStarted
{
get;
private set;
}
public State CurrentState
public bool ReturnCountdownStarted
{
get { return state; }
get;
private set;
}
private float respawnTimer, shuttleReturnTimer, shuttleTransportTimer;
public State CurrentState { get; private set; }
private DateTime despawnTime;
private float maxTransportTime;
private float updateReturnTimer;
public Submarine RespawnShuttle
{
get { return respawnShuttle; }
}
public Submarine RespawnShuttle { get; private set; }
public RespawnManager(NetworkMember networkMember, Submarine shuttle)
: base(shuttle)
@@ -79,8 +68,8 @@ namespace Barotrauma.Networking
if (shuttle != null)
{
respawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
respawnShuttle.Load(false);
RespawnShuttle = new Submarine(shuttle.FilePath, shuttle.MD5Hash.Hash, true);
RespawnShuttle.Load(false);
ResetShuttle();
@@ -89,7 +78,7 @@ namespace Barotrauma.Networking
shuttleDoors = new List<Door>();
foreach (Item item in Item.ItemList)
{
if (item.Submarine != respawnShuttle) continue;
if (item.Submarine != RespawnShuttle) continue;
var steering = item.GetComponent<Steering>();
if (steering != null) shuttleSteering = steering;
@@ -113,13 +102,12 @@ namespace Barotrauma.Networking
}
else
{
respawnShuttle = null;
RespawnShuttle = null;
}
#if SERVER
if (networkMember is GameServer server)
{
respawnTimer = server.ServerSettings.RespawnInterval;
maxTransportTime = server.ServerSettings.MaxTransportTime;
}
#endif
@@ -127,15 +115,15 @@ namespace Barotrauma.Networking
public void Update(float deltaTime)
{
if (respawnShuttle == null)
if (RespawnShuttle == null)
{
if (state != State.Waiting)
if (CurrentState != State.Waiting)
{
state = State.Waiting;
CurrentState = State.Waiting;
}
}
switch (state)
switch (CurrentState)
{
case State.Waiting:
UpdateWaiting(deltaTime);
@@ -155,32 +143,25 @@ namespace Barotrauma.Networking
{
//infinite transport time -> shuttle wont return
if (maxTransportTime <= 0.0f) return;
shuttleTransportTimer -= deltaTime;
UpdateTransportingProjSpecific(deltaTime);
}
partial void UpdateTransportingProjSpecific(float deltaTime);
public void ForceRespawn()
{
ResetShuttle();
RespawnTime = DateTime.Now;
CurrentState = State.Waiting;
}
private void UpdateReturning(float deltaTime)
{
//if (shuttleReturnTimer == maxTransportTime &&
// networkMember.Character != null &&
// networkMember.Character.Submarine == respawnShuttle)
//{
// networkMember.AddChatMessage("The shuttle will automatically return back to the outpost. Please leave the shuttle immediately.", ChatMessageType.Server);
//}
shuttleReturnTimer -= deltaTime;
updateReturnTimer += deltaTime;
if (updateReturnTimer > 1.0f)
{
updateReturnTimer = 0.0f;
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier);
RespawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier);
if (shuttleSteering != null)
{
@@ -196,41 +177,41 @@ namespace Barotrauma.Networking
private IEnumerable<object> ForceShuttleToPos(Vector2 position, float speed)
{
if (respawnShuttle == null)
if (RespawnShuttle == null)
{
yield return CoroutineStatus.Success;
}
respawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier);
RespawnShuttle.PhysicsBody.FarseerBody.IgnoreCollisionWith(Level.Loaded.TopBarrier);
while (Math.Abs(position.Y - respawnShuttle.WorldPosition.Y) > 100.0f)
while (Math.Abs(position.Y - RespawnShuttle.WorldPosition.Y) > 100.0f)
{
Vector2 diff = position - respawnShuttle.WorldPosition;
Vector2 diff = position - RespawnShuttle.WorldPosition;
if (diff.LengthSquared() > 0.01f)
{
Vector2 displayVel = Vector2.Normalize(diff) * speed;
respawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
RespawnShuttle.SubBody.Body.LinearVelocity = ConvertUnits.ToSimUnits(displayVel);
}
yield return CoroutineStatus.Running;
if (respawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
if (RespawnShuttle.SubBody == null) yield return CoroutineStatus.Success;
}
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier);
RespawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier);
yield return CoroutineStatus.Success;
}
private void ResetShuttle()
{
shuttleTransportTimer = maxTransportTime;
shuttleReturnTimer = maxTransportTime;
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
if (respawnShuttle == null) return;
if (RespawnShuttle == null) return;
foreach (Item item in Item.ItemList)
{
if (item.Submarine != respawnShuttle) continue;
if (item.Submarine != RespawnShuttle) continue;
//remove respawn items that have been left in the shuttle
if (respawnItems.Contains(item))
@@ -251,7 +232,7 @@ namespace Barotrauma.Networking
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != respawnShuttle) continue;
if (wall.Submarine != RespawnShuttle) continue;
for (int i = 0; i < wall.SectionCount; i++)
{
@@ -259,12 +240,12 @@ namespace Barotrauma.Networking
}
}
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == respawnShuttle && g.ConnectedWall != null);
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine != respawnShuttle) continue;
if (hull.Submarine != RespawnShuttle) continue;
hull.OxygenPercentage = 100.0f;
hull.WaterVolume = 0.0f;
@@ -272,7 +253,7 @@ namespace Barotrauma.Networking
foreach (Character c in Character.CharacterList)
{
if (c.Submarine != respawnShuttle) continue;
if (c.Submarine != RespawnShuttle) continue;
#if CLIENT
if (Character.Controlled == c) Character.Controlled = null;
@@ -288,15 +269,12 @@ namespace Barotrauma.Networking
if (item == null) continue;
Spawner.AddToRemoveQueue(item);
}
}
}
}
respawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + respawnShuttle.Borders.Height));
respawnShuttle.Velocity = Vector2.Zero;
respawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier);
RespawnShuttle.SetPosition(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height));
RespawnShuttle.Velocity = Vector2.Zero;
RespawnShuttle.PhysicsBody.FarseerBody.RestoreCollisionWith(Level.Loaded.TopBarrier);
}
partial void RespawnCharactersProjSpecific();
@@ -305,55 +283,92 @@ namespace Barotrauma.Networking
RespawnCharactersProjSpecific();
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public Vector2 FindSpawnPos()
{
msg.WriteRangedInteger(0, Enum.GetNames(typeof(State)).Length, (int)state);
if (Level.Loaded == null || Submarine.MainSub == null) { return Vector2.Zero; }
switch (state)
Rectangle dockedBorders = RespawnShuttle.GetDockedBorders();
Vector2 diffFromDockedBorders =
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)
- new Vector2(RespawnShuttle.Borders.Center.X, RespawnShuttle.Borders.Y - RespawnShuttle.Borders.Height / 2);
int minWidth = Math.Max(dockedBorders.Width, 1000);
int minHeight = Math.Max(dockedBorders.Height, 1000);
List<Level.InterestingPosition> potentialSpawnPositions = new List<Level.InterestingPosition>();
foreach (Level.InterestingPosition potentialSpawnPos in Level.Loaded.PositionsOfInterest.Where(p => p.PositionType == Level.PositionType.MainPath))
{
case State.Transporting:
msg.Write(TransportTimer);
break;
case State.Waiting:
msg.Write(CountdownStarted);
msg.Write(respawnTimer);
break;
case State.Returning:
break;
}
bool invalid = false;
//make sure the shuttle won't overlap with any ruins
foreach (var ruin in Level.Loaded.Ruins)
{
if (Math.Abs(ruin.Area.Center.X - potentialSpawnPos.Position.X) < (minWidth + ruin.Area.Width) / 2) { invalid = true; break; }
if (Math.Abs(ruin.Area.Center.Y - potentialSpawnPos.Position.Y) < (minHeight + ruin.Area.Height) / 2) { invalid = true; break; }
}
if (invalid) { continue; }
msg.WritePadBits();
}
//make sure there aren't any walls too close
var tooCloseCells = Level.Loaded.GetTooCloseCells(potentialSpawnPos.Position.ToVector2(), Math.Max(minWidth, minHeight));
if (tooCloseCells.Any()) { continue; }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
//make sure the spawnpoint is far enough from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == RespawnShuttle || RespawnShuttle.DockedTo.Contains(sub)) { continue; }
switch (newState)
{
case State.Transporting:
maxTransportTime = msg.ReadSingle();
shuttleTransportTimer = maxTransportTime;
CountdownStarted = false;
if (state != newState)
float minDist = Math.Max(Math.Max(minWidth, minHeight) + Math.Max(sub.Borders.Width, sub.Borders.Height), 10000.0f);
if (Vector2.DistanceSquared(sub.WorldPosition, potentialSpawnPos.Position.ToVector2()) < minDist * minDist)
{
CoroutineManager.StopCoroutines("forcepos");
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
invalid = true;
break;
}
break;
case State.Waiting:
CountdownStarted = msg.ReadBoolean();
ResetShuttle();
respawnTimer = msg.ReadSingle();
break;
case State.Returning:
CountdownStarted = false;
break;
}
state = newState;
}
if (invalid) { continue; }
msg.ReadPadBits();
foreach (Character character in Character.CharacterList)
{
if (character.IsDead)
{
//cannot spawn directly over dead bodies
if (Math.Abs(character.WorldPosition.X - potentialSpawnPos.Position.X) < minWidth) { invalid = true; break; }
if (Math.Abs(character.WorldPosition.Y - potentialSpawnPos.Position.Y) < minHeight) { invalid = true; break; }
}
else
{
//cannot spawn near alive characters (to prevent other players from seeing the shuttle
//appear out of nowhere, or monsters from immediatelly wrecking the shuttle)
if (Vector2.DistanceSquared(character.WorldPosition, potentialSpawnPos.Position.ToVector2()) < 5000.0f * 5000.0f)
{
invalid = true;
break;
}
}
}
if (invalid) { continue; }
potentialSpawnPositions.Add(potentialSpawnPos);
}
Vector2 bestSpawnPos = new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + RespawnShuttle.Borders.Height);
float bestSpawnPosValue = 0.0f;
foreach (var potentialSpawnPos in potentialSpawnPositions)
{
//the closer the spawnpos is to the main sub, the better
float spawnPosValue = 100000.0f / Math.Max(Vector2.Distance(potentialSpawnPos.Position.ToVector2(), Submarine.MainSub.WorldPosition), 1.0f);
//prefer spawnpoints that are at the left side of the sub (so the shuttle doesn't have to go backwards)
if (potentialSpawnPos.Position.X > Submarine.MainSub.WorldPosition.X)
{
spawnPosValue *= 0.1f;
}
if (spawnPosValue > bestSpawnPosValue)
{
bestSpawnPos = potentialSpawnPos.Position.ToVector2();
bestSpawnPosValue = spawnPosValue;
}
}
return bestSpawnPos;
}
}
}
@@ -94,7 +94,7 @@ namespace Barotrauma.Networking
private SerializableProperty property;
private string typeString;
private ServerSettings serverSettings;
private object parentObject;
public string Name
{
@@ -103,16 +103,42 @@ namespace Barotrauma.Networking
public object Value
{
get { return property.GetValue(serverSettings); }
get { return property.GetValue(parentObject); }
set { property.SetValue(parentObject, value); }
}
public NetPropertyData(ServerSettings serverSettings, SerializableProperty property, string typeString)
public NetPropertyData(object parentObject, SerializableProperty property, string typeString)
{
this.property = property;
this.typeString = typeString;
this.serverSettings = serverSettings;
this.parentObject = parentObject;
}
public bool PropEquals(object a, object b)
{
switch (typeString)
{
case "float":
if (!(a is float?)) return false;
if (!(b is float?)) return false;
return MathUtils.NearlyEqual((float)a, (float)b);
case "int":
if (!(a is int?)) return false;
if (!(b is int?)) return false;
return (int)a == (int)b;
case "bool":
if (!(a is bool?)) return false;
if (!(b is bool?)) return false;
return (bool)a == (bool)b;
case "Enum":
if (!(a is Enum)) return false;
if (!(b is Enum)) return false;
return ((Enum)a).Equals((Enum)b);
default:
return a.ToString().Equals(b.ToString(), StringComparison.InvariantCulture);
}
}
public void Read(NetBuffer msg)
{
long oldPos = msg.Position;
@@ -126,20 +152,24 @@ namespace Barotrauma.Networking
{
case "float":
if (size != 4) break;
property.SetValue(serverSettings, msg.ReadFloat());
property.SetValue(parentObject, msg.ReadFloat());
return;
case "int":
if (size != 4) break;
property.SetValue(parentObject, msg.ReadInt32());
return;
case "vector2":
if (size != 8) break;
x = msg.ReadFloat();
y = msg.ReadFloat();
property.SetValue(serverSettings, new Vector2(x, y));
property.SetValue(parentObject, new Vector2(x, y));
return;
case "vector3":
if (size != 12) break;
x = msg.ReadFloat();
y = msg.ReadFloat();
z = msg.ReadFloat();
property.SetValue(serverSettings, new Vector3(x, y, z));
property.SetValue(parentObject, new Vector3(x, y, z));
return;
case "vector4":
if (size != 16) break;
@@ -147,7 +177,7 @@ namespace Barotrauma.Networking
y = msg.ReadFloat();
z = msg.ReadFloat();
w = msg.ReadFloat();
property.SetValue(serverSettings, new Vector4(x, y, z, w));
property.SetValue(parentObject, new Vector4(x, y, z, w));
return;
case "color":
if (size != 4) break;
@@ -155,7 +185,7 @@ namespace Barotrauma.Networking
g = msg.ReadByte();
b = msg.ReadByte();
a = msg.ReadByte();
property.SetValue(serverSettings, new Color(r, g, b, a));
property.SetValue(parentObject, new Color(r, g, b, a));
return;
case "rectangle":
if (size != 16) break;
@@ -163,12 +193,12 @@ namespace Barotrauma.Networking
iy = msg.ReadInt32();
width = msg.ReadInt32();
height = msg.ReadInt32();
property.SetValue(serverSettings, new Rectangle(ix, iy, width, height));
property.SetValue(parentObject, new Rectangle(ix, iy, width, height));
return;
default:
msg.Position = oldPos; //reset position to properly read the string
string incVal = msg.ReadString();
property.TrySetValue(serverSettings, incVal);
property.TrySetValue(parentObject, incVal);
return;
}
@@ -178,13 +208,17 @@ namespace Barotrauma.Networking
public void Write(NetBuffer msg, object overrideValue = null)
{
if (overrideValue == null) overrideValue = property.GetValue(serverSettings);
if (overrideValue == null) overrideValue = property.GetValue(parentObject);
switch (typeString)
{
case "float":
msg.WriteVariableUInt32(4);
msg.Write((float)overrideValue);
break;
case "int":
msg.WriteVariableUInt32(4);
msg.Write((int)overrideValue);
break;
case "vector2":
msg.WriteVariableUInt32(8);
msg.Write(((Vector2)overrideValue).X);
@@ -232,14 +266,14 @@ namespace Barotrauma.Networking
private set;
}
Dictionary<UInt32,NetPropertyData> netProperties;
Dictionary<UInt32, NetPropertyData> netProperties;
partial void InitProjSpecific();
public ServerSettings(string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP)
{
public ServerSettings(NetworkMember networkMember, string serverName, int port, int queryPort, int maxPlayers, bool isPublic, bool enableUPnP)
{
ServerLog = new ServerLog(serverName);
Voting = new Voting();
Whitelist = new WhiteList();
@@ -265,17 +299,30 @@ namespace Barotrauma.Networking
foreach (var property in saveProperties)
{
object value = property.GetValue(this);
if (value == null) continue;
if (value == null) { continue; }
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(this, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
if (netProperties.ContainsKey(key)){ throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
netProperties.Add(key, netPropertyData);
}
}
if (netProperties.ContainsKey(key)) throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")");
var karmaProperties = SerializableProperty.GetProperties<Serialize>(networkMember.KarmaManager);
foreach (var property in karmaProperties)
{
object value = property.GetValue(networkMember.KarmaManager);
if (value == null) { continue; }
string typeName = SerializableProperty.GetSupportedTypeName(value.GetType());
if (typeName != null || property.PropertyType.IsEnum)
{
NetPropertyData netPropertyData = new NetPropertyData(networkMember.KarmaManager, property, typeName);
UInt32 key = ToolBox.StringToUInt32Hash(property.Name, md5);
if (netProperties.ContainsKey(key)) { throw new Exception("Hashing collision in ServerSettings.netProperties: " + netProperties[key] + " has same key as " + property.Name + " (" + key.ToString() + ")"); }
netProperties.Add(key, netPropertyData);
}
}
@@ -548,7 +595,14 @@ namespace Barotrauma.Networking
get;
set;
}
[Serialize(true, true)]
public bool AllowFriendlyFire
{
get;
set;
}
private YesNoMaybe traitorsEnabled;
public YesNoMaybe TraitorsEnabled
{
@@ -631,12 +685,26 @@ namespace Barotrauma.Networking
private set;
}
private bool karmaEnabled;
[Serialize(false, true)]
public bool KarmaEnabled
{
get { return karmaEnabled; }
set
{
karmaEnabled = value;
#if CLIENT
if (karmaSettingsBlocker != null) { karmaSettingsBlocker.Visible = !karmaEnabled || karmaPresetDD.SelectedData as string != "custom"; }
#endif
}
}
[Serialize("default", true)]
public string KarmaPreset
{
get;
set;
}
} = "default";
[Serialize("sandbox", true)]
public string GameModeIdentifier
@@ -664,14 +732,14 @@ namespace Barotrauma.Networking
set;
}
[Serialize(60f, true)]
[Serialize(60f * 60.0f, true)]
public float AutoBanTime
{
get;
private set;
}
[Serialize(360f, true)]
[Serialize(60.0f * 60.0f * 24.0f, true)]
public float MaxAutoBanTime
{
get;
@@ -512,11 +512,9 @@ namespace Barotrauma
#endif
}
public bool IsValidValue(float value, string valueName, float? minValue = null, float? maxValue = null)
public bool IsValidValue(float value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue)
{
if (!MathUtils.IsValid(value) ||
(minValue.HasValue && value < minValue.Value) ||
(maxValue.HasValue && value > maxValue.Value))
if (!MathUtils.IsValid(value) || value < minValue || value > maxValue)
{
string userData = UserData == null ? "null" : UserData.ToString();
string errorMsg =
@@ -539,11 +537,11 @@ namespace Barotrauma
return true;
}
private bool IsValidValue(Vector2 value, string valueName, float? minValue = null, float? maxValue = null)
private bool IsValidValue(Vector2 value, string valueName, float minValue = float.MinValue, float maxValue = float.MaxValue)
{
if (!MathUtils.IsValid(value) ||
(minValue.HasValue && (value.X < minValue.Value || value.Y < minValue.Value)) ||
(maxValue.HasValue && (value.X > maxValue.Value || value.Y > maxValue)))
(value.X < minValue || value.Y < minValue) ||
(value.X > maxValue || value.Y > maxValue))
{
string userData = UserData == null ? "null" : UserData.ToString();
string errorMsg =
@@ -7,12 +7,18 @@ namespace Barotrauma
{
partial class GameScreen : Screen
{
private Camera cam;
private readonly Camera cam;
public override Camera Cam
{
get { return cam; }
}
public double GameTime
{
get;
private set;
}
public GameScreen()
{
@@ -74,6 +80,9 @@ namespace Barotrauma
closestSub.ApplyForce(targetMovement * closestSub.SubBody.Body.Mass * 100.0f);
}
#endif
GameTime += deltaTime;
foreach (PhysicsBody body in PhysicsBody.List)
{
body.Update((float)deltaTime);
@@ -15,7 +15,9 @@ namespace Barotrauma
public static string ParseContentPathFromUri(this XObject element)
{
string[] splitted = element.BaseUri.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
IEnumerable<string> filtered = splitted.SkipWhile(part => part != "Content");
string currentFolder = Environment.CurrentDirectory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Last();
// Filter out the current folder -> result is "Content/blaahblaah" or "Mods/blaahblaah" etc.
IEnumerable<string> filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1);
return string.Join("/", filtered);
}
@@ -121,6 +121,8 @@ namespace Barotrauma
private List<ItemSpawnInfo> spawnItems;
private Character user;
public readonly float FireSize;
public HashSet<string> TargetIdentifiers
@@ -494,6 +496,7 @@ namespace Barotrauma
public void SetUser(Character user)
{
this.user = user;
foreach (Affliction affliction in Afflictions)
{
affliction.Source = user;
@@ -627,7 +630,7 @@ namespace Barotrauma
}
}
if (explosion != null && entity != null) explosion.Explode(entity.WorldPosition, entity);
if (explosion != null && entity != null) { explosion.Explode(entity.WorldPosition, damageSource: entity, attacker: user); }
foreach (ISerializableEntity target in targets)
{
@@ -655,14 +658,26 @@ namespace Barotrauma
foreach (Pair<string, float> reduceAffliction in ReduceAffliction)
{
float reduceAmount = disableDeltaTime ? reduceAffliction.Second : reduceAffliction.Second * deltaTime;
Limb targetLimb = null;
Character targetCharacter = null;
if (target is Character character)
{
character.CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAmount);
targetCharacter = character;
}
else if (target is Limb limb)
{
limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAmount);
targetLimb = limb;
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAmount);
#if SERVER
GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, user, prevVitality - targetCharacter.Vitality);
#endif
}
}
}
@@ -822,13 +837,24 @@ namespace Barotrauma
foreach (Pair<string, float> reduceAffliction in element.Parent.ReduceAffliction)
{
if (target is Character)
Limb targetLimb = null;
Character targetCharacter = null;
if (target is Character character)
{
((Character)target).CharacterHealth.ReduceAffliction(null, reduceAffliction.First, reduceAffliction.Second * deltaTime);
targetCharacter = character;
}
else if (target is Limb limb)
{
limb.character.CharacterHealth.ReduceAffliction(limb, reduceAffliction.First, reduceAffliction.Second * deltaTime);
targetLimb = limb;
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
float prevVitality = targetCharacter.Vitality;
targetCharacter.CharacterHealth.ReduceAffliction(targetLimb, reduceAffliction.First, reduceAffliction.Second * deltaTime);
#if SERVER
GameMain.Server.KarmaManager.OnCharacterHealthChanged(targetCharacter, element.Parent.user, prevVitality - targetCharacter.Vitality);
#endif
}
}
}
@@ -15,11 +15,17 @@ namespace Barotrauma
//key = language
private static Dictionary<string, List<TextPack>> textPacks = new Dictionary<string, List<TextPack>>();
private static string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
private static readonly string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
public static string Language;
private static HashSet<string> availableLanguages = new HashSet<string>();
public static bool Initialized
{
get;
private set;
}
private static readonly HashSet<string> availableLanguages = new HashSet<string>();
public static IEnumerable<string> AvailableLanguages
{
get { return availableLanguages; }
@@ -99,6 +105,7 @@ namespace Barotrauma
availableLanguages.Add(textPack.Language);
textPacks.Add(textPack.Language, new List<TextPack>() { textPack });
}
Initialized = true;
}
public static bool ContainsTag(string textTag)
@@ -260,19 +260,20 @@ namespace Barotrauma
public static string SecondsToReadableTime(float seconds)
{
int s = (int)(seconds % 60.0f);
if (seconds < 60.0f)
{
return (int)seconds + " s";
return s + " s";
}
else
{
int m = (int)(seconds / 60.0f);
int s = (int)(seconds % 60.0f);
return s == 0 ?
m + " m" :
m + " m " + s + " s";
}
int h = (int)(seconds / (60.0f * 60.0f));
int m = (int)((seconds / 60.0f) % 60);
string text = "";
if (h != 0) { text = h + " h"; }
if (m != 0) { text = string.IsNullOrEmpty(text) ? m + " m" : string.Join(" ", text, m, "m"); }
if (s != 0) { text = string.IsNullOrEmpty(text) ? s + " s" : string.Join(" ", text, s, "s"); }
return text;
}
private static Dictionary<string, List<string>> cachedLines = new Dictionary<string, List<string>>();