(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
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<KarmaManager>
<Preset
name="Default"
karmadecay="0.08"
karmadecaythreshold="50"
karmaincrease="0.1"
karmaincreasethreshold="50"
structurerepairkarmaincrease="0.05"
structuredamagekarmadecrease="0.1"
itemrepairkarmaincrease="0.03"
reactoroverheatkarmadecrease="0.5"
reactormeltdownkarmadecrease="30"
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.2"
extinguishfirekarmaincrease="1"
allowedwiredisconnectionsperminute="5"
wiredisconnectionkarmadecrease="6.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="15"
herpesthreshold="40"
kickbanthreshold="1"
karmanotificationinterval="10" />
<Preset
name="Strict"
karmadecay="0.08"
karmadecaythreshold="50"
karmaincrease="0.08"
karmaincreasethreshold="45"
structurerepairkarmaincrease="0.05"
structuredamagekarmadecrease="0.15"
itemrepairkarmaincrease="0.03"
reactoroverheatkarmadecrease="1.0"
reactormeltdownkarmadecrease="35"
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.3"
extinguishfirekarmaincrease="1"
allowedwiredisconnectionsperminute="4"
wiredisconnectionkarmadecrease="10.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="25"
herpesthreshold="40"
kickbanthreshold="1"
karmanotificationinterval="10" />
<Preset
name="Custom"
karmadecay="0.08"
karmadecaythreshold="50"
karmaincrease="0.1"
karmaincreasethreshold="50"
structurerepairkarmaincrease="0.05"
structuredamagekarmadecrease="0.1"
itemrepairkarmaincrease="0.03"
reactoroverheatkarmadecrease="0.5"
reactormeltdownkarmadecrease="30"
damageenemykarmaincrease="0.1"
damagefriendlykarmadecrease="0.2"
extinguishfirekarmaincrease="1"
allowedwiredisconnectionsperminute="5"
wiredisconnectionkarmadecrease="6.0"
steersubkarmaincrease="0.15"
spamfilterkarmadecrease="15"
herpesthreshold="40"
kickbanthreshold="1"
karmanotificationinterval="10" />
</KarmaManager>
@@ -25,6 +25,7 @@
<Command name="kickid"/>
<Command name="campaigninfo"/>
<Command name="campaigndestination"/>
<Command name="respawnnow"/>
</Preset>
<Preset
@@ -76,5 +77,6 @@
<Command name="power"/>
<Command name="oxygen"/>
<Command name="setclientcharacter"/>
<Command name="respawnnow"/>
</Preset>
</PermissionPresets>
@@ -66,6 +66,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\Afflictions\AfflictionHusk.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\Afflictions\AfflictionPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\Afflictions\AfflictionPsychosis.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\Afflictions\AfflictionSpaceHerpes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\Buffs\BuffDurationIncrease.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\DamageModifier.cs" />
@@ -226,6 +227,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\EntitySpawner.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\FileTransfer\FileTransfer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\INetSerializable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\KarmaManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetBufferExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetConfig.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetEntityEvent\NetEntityEvent.cs" />
@@ -481,6 +481,9 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LabelLetters.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\MapPieces.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\OpbgMaintenanceBay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -683,6 +686,9 @@
<Content Include="$(MSBuildThisFileDirectory)Content\UI\tutorialAtlas.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Data\karmasettings.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="$(MSBuildThisFileDirectory)Concentus_LICENSE">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -1640,54 +1646,6 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapLine.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_01.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_02.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_03.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_04.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_05.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_06.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_07.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_08.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_09.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_10.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_11.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_12.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_13.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_14.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_15.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapPieces\btMAP_Test_16.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\Reticles.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -2183,9 +2141,105 @@
<None Include="$(MSBuildThisFileDirectory)Content\Items\PowerOnLight3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Reactor\ReactorOverheatAlarm.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ElectricalDischarger.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\FlameThrowerLoop.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\FragGrenade40mmShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\FragGrenade40mmShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\GrenadeLauncherShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\GrenadeLauncherShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\GrenadeLauncherShot3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunLoad1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunLoad2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunLoad3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunShot3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ShotgunShot4.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot4.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot5.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\SMGsingleShot6.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\StunGrenade40mmShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\StunGrenade40mmShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\TaserHit1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\TaserHit2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\TaserShot1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\TaserShot2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit1.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit4.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit5.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Items\Weapons\ToyHammerHit6.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Content\Map\Outposts\Outpost2.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -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>>();
Binary file not shown.
Binary file not shown.
+109
View File
@@ -1,3 +1,112 @@
---------------------------------------------------------------------------------------------------------
v0.9.2.0
---------------------------------------------------------------------------------------------------------
Easier server hosting:
- Switched to Steam's networking API, which allows clients to connect to servers without the need for the
host to forward their ports.
Anti-griefing:
- Karma: a system that detects malicious actions and automatically creates a more challenging experience
for griefers, or potentially triggers a kickban. The feature is completely optional and the server hosts
can decide how aggressively it will react to malicious actions. In a nutshell, you can lose karma for doing
dumb things, karma is regained gradually over time and may be increased more rapidly by doing good things,
thus negating false positives and also giving you the chance to redeem yourself. Lose too much karma and
you'll start to experience increasing levels of inconvenience.
- Wire griefing is more difficult: wires have to be disconnected from the connection panels at both ends
before they can be removed. Disconnected wires can be seen visibly hanging from the device, with noticeable
particle effects and sound cues that make it easier to detect and fix wiring problems.
- Added a built-in overheat warning light and siren to reactors and increased the time it takes for the
reactor to catch fire and explode.
- Option to disable friendly fire altogether.
Expanded traitor mode:
- More varied objectives: destroy cargo, jam the sonar, sabotage devices, infect crew members, kill a specific
target, cause a reactor meltdown, flood the sub...
- The traitors must complete a set of tasks, generally starting with less destructive objectives that
give the other players a better chance to detect (and detain/kill) the traitor before they proceed to the
more lethal ones.
New items:
- Additional weapons: stun gun, SMG, shotgun, grenade launcher, flamer.
- New monster loot: husk stinger, mudraptor shell, crawler mask, moloch shell shield.
- Fun stuff: new musical instruments, captain's pipe, clown hammer.
Bugfixes:
- Fixed shuttles not being able to redock into some submarines with unconventionally positioned docking
ports. Specifically, if a port needed to be docked to from below but was positioned above the center of
the submarine (or vice versa), the docking interface would not activate on the navigation terminal.
- Fixed crashing when combining items inside an ItemContainer (e.g. cabinet, deconstructor).
- Fixed item editing panel not appearing in wiring mode when selecting a wire in the sub editor.
- Fixed ability to throw throwable items while unconscious.
- Fixed bots not using fire extinguishers client-side in multiplayer.
- Fixed clients not hearing the sound when someone crowbars a door open.
- Fixed progress bars not showing client-side when cutting/welding doors.
- Fixed inability to combine items in multiplayer.
- Fixed inability to scroll the crew list with the mouse wheel when the cursor is over certain parts
of the list.
- Fixed sonar pings stopping mid-way when active sonar is turned off, which could be exploited to stop
the pings before they reach a monster further away from the sub.
- Fixed clients not seeing other characters when entering spectator mode after their character has been
eaten by a creature.
- Fixed clients not seeing other characters in spectator mode after the distance between the submarine
and the client's corpse gets great enough.
- Fixed clients getting stuck to a non-functional game screen if they start a new round before the ending
cinematic has finished server-side.
- Fixed projectiles hitting a character you're standing next to when firing.
- Fixed characters automatically equipping handcuffs (i.e. handcuffing themselves) if they were picked up
when the only free inventory space was the hand slots.
- Fixed a rare race condition that occasionally caused the game to crash during the loading screen with
a "no text packs available in English!" error message.
- Fixed husk infection being healable with broad-spectrum antibiotics even when the infection has reached
the final stage.
- Fixed attachable items (detonators, electrical components) becoming deattachable without any tools
if the sub is saved after deattaching them.
- Fixed projectile raycasts not taking wall rotation into account, causing the projectiles to hit sloped
walls when standing next to them.
- Fixed crashing in the character editor when no textures were found for the selected character.
- Fixed crashing if a humanoid character has no knees or ankles.
- Fixed EventManager calculating flooding amount incorrectly, causing floods to only have a very small
effect on the intensity value.
- Fixed batteries and supercapacitors being able to provide power through their signal connections
(e.g. "set_charge_rate", "charge_rate_out").
- Lighting fix: obstruct background lights behind hulls. Previously the background lights would only get
obstructed by background structures, causing light to "bleed through" parts with no structures (e.g.
humpback's docking port) and windows in the background walls.
- Fixed item loading being interrupted if any item XML cannot be loaded, causing some items not to be
loaded if any of the selected content packages are missing files or contain corrupted item XMLs.
Misc additions and changes:
- Respawn shuttles now spawn inside the level somewhere near the submarine, instead of always spawning
at the beginning of the level.
- Respawn shuttles don't leave and despawn until a new respawn is pending.
- Added "respawnnow" console command which immediately dispatches the respawn shuttle.
- Search for default animation/ragdoll folders from the folder where the character file is located, not
from "Content/Characters". Fixes mods not being able to load animation/ragdoll files if the file paths
are not defined explicitly in the character configuration file.
- Improved performance by deleting dead monsters that are far away from the sub and by disabling physics
on dead bodies when they stay still long enough.
- In wiring mode, items are selected by pressing E instead of clicking. Selecting items with the left
mouse button made it very difficult to manipulate the wires because it was easy to accidentally select
some device instead of a wire node.
- Added limits to submarine name and description length.
- Delete incomplete file downloads when disconnecting from a server while a file transfer is active.
Prevents console errors about corrupted submarine/save files caused by the partially downloaded files.
- Increased Humpback's battery capacity.
- Added widgets for manipulating coilgun/railgun rotation limits in the sub editor.
- Show ballast tanks and airlocks in a different color on the status monitor to make it easier to
distinguish which rooms are actually flooding and which are supposed to have water in them.
- Option to define multiple inventory variants for a character (e.g. to add variation to monster loot).
- Start playing the main menu music during the loading screen.
- More reliable human walk sounds (played at specific points of the walk cycle instead of relying on
impacts between the feet and the floor).
- Option to show a 16x16 grid and snap the cursor to it in the sprite editor.
- Reactor can be controlled with movement keys.
- Option to add a probability for afflictions applied by an attack.
- Husk attacks have a 50% chance of causing a husk infection.
- Decreased minimum item scale from 0.1 to 0.01.
- The amount of materials received from deconstructing fuel rods depends on the condition of the fuel rod.
---------------------------------------------------------------------------------------------------------
v0.9.1.0
---------------------------------------------------------------------------------------------------------
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -33,8 +33,8 @@
karmaenabled="False"
gamemodeidentifier="sandbox"
missiontype="Random"
autobantime="60"
maxautobantime="360"
autobantime="600"
maxautobantime="86400"
name="Server"
public="false"
port="27015"
Binary file not shown.