Merge branch 'dev' of https://github.com/Regalis11/Barotrauma.git into unstable-tests

This commit is contained in:
Evil Factory
2022-04-08 12:52:28 -03:00
990 changed files with 44338 additions and 38589 deletions
@@ -5,9 +5,7 @@ namespace Barotrauma
{
partial class Character
{
public static Character Controlled = null;
partial void InitProjSpecific(XElement mainElement) { }
public static Character Controlled => null;
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun)
{
@@ -20,7 +18,7 @@ namespace Barotrauma
{
if (causeOfDeath == CauseOfDeathType.Affliction)
{
GameServer.Log(GameServer.CharacterLogName(this) + " has died (Cause of death: " + causeOfDeathAffliction.Prefab.Name + ")", ServerLog.MessageType.Attack);
GameServer.Log(GameServer.CharacterLogName(this) + " has died (Cause of death: " + causeOfDeathAffliction.Prefab.Name.Value + ")", ServerLog.MessageType.Attack);
}
else
{
@@ -62,7 +60,7 @@ namespace Barotrauma
partial void OnMoneyChanged(int prevAmount, int newAmount)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney });
GameMain.NetworkMember.CreateEntityEvent(this, new UpdateMoneyEventData());
}
partial void OnTalentGiven(TalentPrefab talentPrefab)
@@ -8,9 +8,9 @@ namespace Barotrauma
{
partial class CharacterInfo
{
private readonly Dictionary<string, float> prevSentSkill = new Dictionary<string, float>();
private readonly Dictionary<Identifier, float> prevSentSkill = new Dictionary<Identifier, float>();
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel)
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
{
if (Character == null || Character.Removed) { return; }
if (!prevSentSkill.ContainsKey(skillIdentifier))
@@ -19,7 +19,7 @@ namespace Barotrauma
}
if (Math.Abs(prevSentSkill[skillIdentifier] - newLevel) > 0.01f)
{
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateSkills });
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdateSkillsEventData());
prevSentSkill[skillIdentifier] = newLevel;
}
}
@@ -30,14 +30,14 @@ namespace Barotrauma
if (prevAmount != newAmount)
{
GameServer.Log($"{GameServer.CharacterLogName(Character)} has gained {newAmount - prevAmount} experience ({prevAmount} -> {newAmount})", ServerLog.MessageType.Talent);
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdateExperience });
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdateExperienceEventData());
}
}
partial void OnPermanentStatChanged(StatTypes statType)
{
if (Character == null || Character.Removed) { return; }
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.UpdatePermanentStats, statType });
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdatePermanentStatsEventData());
}
public void ServerWrite(IWriteMessage msg)
@@ -45,16 +45,18 @@ namespace Barotrauma
msg.Write(ID);
msg.Write(Name);
msg.Write(OriginalName);
msg.Write((byte)Gender);
msg.Write((byte)Race);
msg.Write((byte)HeadSpriteId);
msg.Write((byte)HairIndex);
msg.Write((byte)BeardIndex);
msg.Write((byte)MoustacheIndex);
msg.Write((byte)FaceAttachmentIndex);
msg.WriteColorR8G8B8(SkinColor);
msg.WriteColorR8G8B8(HairColor);
msg.WriteColorR8G8B8(FacialHairColor);
msg.Write((byte)Head.Preset.TagSet.Count);
foreach (Identifier tag in Head.Preset.TagSet)
{
msg.Write(tag);
}
msg.Write((byte)Head.HairIndex);
msg.Write((byte)Head.BeardIndex);
msg.Write((byte)Head.MoustacheIndex);
msg.Write((byte)Head.FaceAttachmentIndex);
msg.WriteColorR8G8B8(Head.SkinColor);
msg.WriteColorR8G8B8(Head.HairColor);
msg.WriteColorR8G8B8(Head.FacialHairColor);
msg.Write(ragdollFileName);
if (Job != null)
@@ -73,20 +75,9 @@ namespace Barotrauma
msg.Write("");
msg.Write((byte)0);
}
// TODO: animations
msg.Write((byte)SavedStatValues.SelectMany(s => s.Value).Count());
foreach (var savedStatValuePair in SavedStatValues)
{
foreach (var savedStatValue in savedStatValuePair.Value)
{
msg.Write((byte)savedStatValuePair.Key);
msg.Write(savedStatValue.StatIdentifier);
msg.Write(savedStatValue.StatValue);
msg.Write(savedStatValue.RemoveOnDeath);
}
}
msg.Write((ushort)ExperiencePoints);
msg.Write((ushort)AdditionalTalentPoints);
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
}
}
}
@@ -153,14 +153,94 @@ namespace Barotrauma
}
}
public virtual void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerReadInput(IReadMessage msg, Client c)
{
if (GameMain.Server == null) return;
switch (type)
if (c.Character != this)
{
case ClientNetObject.CHARACTER_INPUT:
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
UInt16 networkUpdateID = msg.ReadUInt16();
byte inputCount = msg.ReadByte();
if (AllowInput) { Enabled = true; }
for (int i = 0; i < inputCount; i++)
{
InputNetFlags newInput = (InputNetFlags)msg.ReadRangedInteger(0, (int)InputNetFlags.MaxVal);
UInt16 newAim = 0;
UInt16 newInteract = 0;
if (newInput != InputNetFlags.None && newInput != InputNetFlags.FacingLeft)
{
c.KickAFKTimer = 0.0f;
}
else if (AnimController.Dir < 0.0f != newInput.HasFlag(InputNetFlags.FacingLeft))
{
//character changed the direction they're facing
c.KickAFKTimer = 0.0f;
}
newAim = msg.ReadUInt16();
if (newInput.HasFlag(InputNetFlags.Select) ||
newInput.HasFlag(InputNetFlags.Deselect) ||
newInput.HasFlag(InputNetFlags.Use) ||
newInput.HasFlag(InputNetFlags.Health) ||
newInput.HasFlag(InputNetFlags.Grab))
{
newInteract = msg.ReadUInt16();
}
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
{
if ((i > 0 && memInput[i - 1].intAim != newAim))
{
c.KickAFKTimer = 0.0f;
}
NetInputMem newMem = new NetInputMem
{
states = newInput,
intAim = newAim,
interact = newInteract,
networkUpdateID = (ushort)(networkUpdateID - i)
};
memInput.Insert(i, newMem);
LastInputTime = Timing.TotalTime;
}
}
if (NetIdUtils.IdMoreRecent(networkUpdateID, LastNetworkUpdateID))
{
LastNetworkUpdateID = networkUpdateID;
}
else if (NetIdUtils.Difference(networkUpdateID, LastNetworkUpdateID) > 500)
{
#if DEBUG || UNSTABLE
DebugConsole.AddWarning($"Large disrepancy between a client character's network update ID server-side and client-side (client: {networkUpdateID}, server: {LastNetworkUpdateID}). Resetting the ID.");
#endif
LastNetworkUpdateID = networkUpdateID;
}
if (memInput.Count > 60)
{
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
//we'll make the server drop down to 30 inputs for good measure
memInput.RemoveRange(30, memInput.Count - 30);
}
}
public virtual void ServerEventRead(IReadMessage msg, Client c)
{
EventType eventType = (EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventType)
{
case EventType.InventoryState:
Inventory.ServerEventRead(msg, c);
break;
case EventType.Treatment:
bool doingCPR = msg.ReadBoolean();
if (c.Character != this)
{
#if DEBUG
@@ -169,416 +249,315 @@ namespace Barotrauma
return;
}
UInt16 networkUpdateID = msg.ReadUInt16();
byte inputCount = msg.ReadByte();
if (AllowInput) { Enabled = true; }
for (int i = 0; i < inputCount; i++)
AnimController.Anim = doingCPR ? AnimController.Animation.CPR : AnimController.Animation.None;
break;
case EventType.Status:
if (c.Character != this)
{
InputNetFlags newInput = (InputNetFlags)msg.ReadRangedInteger(0, (int)InputNetFlags.MaxVal);
UInt16 newAim = 0;
UInt16 newInteract = 0;
if (newInput != InputNetFlags.None && newInput != InputNetFlags.FacingLeft)
{
c.KickAFKTimer = 0.0f;
}
else if (AnimController.Dir < 0.0f != newInput.HasFlag(InputNetFlags.FacingLeft))
{
//character changed the direction they're facing
c.KickAFKTimer = 0.0f;
}
newAim = msg.ReadUInt16();
if (newInput.HasFlag(InputNetFlags.Select) ||
newInput.HasFlag(InputNetFlags.Deselect) ||
newInput.HasFlag(InputNetFlags.Use) ||
newInput.HasFlag(InputNetFlags.Health) ||
newInput.HasFlag(InputNetFlags.Grab))
{
newInteract = msg.ReadUInt16();
}
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
{
if ((i > 0 && memInput[i - 1].intAim != newAim))
{
c.KickAFKTimer = 0.0f;
}
NetInputMem newMem = new NetInputMem
{
states = newInput,
intAim = newAim,
interact = newInteract,
networkUpdateID = (ushort)(networkUpdateID - i)
};
memInput.Insert(i, newMem);
LastInputTime = Timing.TotalTime;
}
}
if (NetIdUtils.IdMoreRecent(networkUpdateID, LastNetworkUpdateID))
{
LastNetworkUpdateID = networkUpdateID;
}
else if (NetIdUtils.Difference(networkUpdateID, LastNetworkUpdateID) > 500)
{
#if DEBUG || UNSTABLE
DebugConsole.AddWarning($"Large disrepancy between a client character's network update ID server-side and client-side (client: {networkUpdateID}, server: {LastNetworkUpdateID}). Resetting the ID.");
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
LastNetworkUpdateID = networkUpdateID;
return;
}
if (memInput.Count > 60)
if (IsIncapacitated)
{
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
//we'll make the server drop down to 30 inputs for good measure
memInput.RemoveRange(30, memInput.Count - 30);
var causeOfDeath = CharacterHealth.GetCauseOfDeath();
Kill(causeOfDeath.type, causeOfDeath.affliction);
}
break;
case ClientNetObject.ENTITY_STATE:
int eventType = msg.ReadRangedInteger(0, 4);
switch (eventType)
case EventType.UpdateTalents:
if (c.Character != this)
{
case 0:
Inventory.ServerRead(type, msg, c);
break;
case 1:
bool doingCPR = msg.ReadBoolean();
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
return;
}
AnimController.Anim = doingCPR ? AnimController.Animation.CPR : AnimController.Animation.None;
break;
case 2:
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
// get the full list of talents from the player, only give the ones
// that are not already given (or otherwise not viable)
ushort talentCount = msg.ReadUInt16();
List<Identifier> talentSelection = new List<Identifier>();
for (int i = 0; i < talentCount; i++)
{
UInt32 talentIdentifier = msg.ReadUInt32();
var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UintIdentifier == talentIdentifier);
if (prefab == null) { continue; }
if (IsIncapacitated)
{
var causeOfDeath = CharacterHealth.GetCauseOfDeath();
Kill(causeOfDeath.type, causeOfDeath.affliction);
}
break;
case 3: // NetEntityEvent.Type.UpdateTalents
if (c.Character != this)
{
#if DEBUG
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
#endif
return;
}
// get the full list of talents from the player, only give the ones
// that are not already given (or otherwise not viable)
ushort talentCount = msg.ReadUInt16();
List<string> talentSelection = new List<string>();
for (int i = 0; i < talentCount; i++)
{
UInt32 talentIdentifier = msg.ReadUInt32();
var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UIntIdentifier == talentIdentifier);
if (prefab == null) { continue; }
if (TalentTree.IsViableTalentForCharacter(this, prefab.Identifier, talentSelection))
{
GiveTalent(prefab.Identifier);
talentSelection.Add(prefab.Identifier);
}
}
if (talentSelection.Count != talentCount)
{
DebugConsole.AddWarning($"Failed to unlock talents: the amount of unlocked talents doesn't match (client: {talentCount}, server: {talentSelection.Count})");
}
break;
if (TalentTree.IsViableTalentForCharacter(this, prefab.Identifier, talentSelection))
{
GiveTalent(prefab.Identifier);
talentSelection.Add(prefab.Identifier);
}
}
if (talentSelection.Count != talentCount)
{
DebugConsole.AddWarning($"Failed to unlock talents: the amount of unlocked talents doesn't match (client: {talentCount}, server: {talentSelection.Count})");
}
break;
}
msg.ReadPadBits();
}
public virtual void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerWritePosition(IWriteMessage msg, Client c)
{
if (GameMain.Server == null) return;
msg.Write(ID);
if (extraData != null)
IWriteMessage tempBuffer = new WriteOnlyMessage();
if (this == c.Character)
{
const int min = 0, max = 13;
switch ((NetEntityEvent.Type)extraData[0])
tempBuffer.Write(true);
if (LastNetworkUpdateID < memInput.Count + 1)
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, min, max);
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
Inventory.ServerWrite(msg, c);
break;
case NetEntityEvent.Type.Control:
msg.WriteRangedInteger(1, min, max);
Client owner = (Client)extraData[1];
msg.Write(owner == c && owner.Character == this);
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, min, max);
WriteStatus(msg);
break;
case NetEntityEvent.Type.UpdateSkills:
msg.WriteRangedInteger(3, min, max);
if (Info?.Job == null)
{
msg.Write((byte)0);
}
else
{
msg.Write((byte)Info.Job.Skills.Count);
foreach (Skill skill in Info.Job.Skills)
{
msg.Write(skill.Identifier);
msg.Write(skill.Level);
}
}
break;
case NetEntityEvent.Type.SetAttackTarget:
case NetEntityEvent.Type.ExecuteAttack:
Limb attackLimb = extraData[1] as Limb;
UInt16 targetEntityID = (UInt16)extraData[2];
int targetLimbIndex = extraData.Length > 3 ? (int)extraData[3] : 0;
msg.WriteRangedInteger(extraData[0] is NetEntityEvent.Type.SetAttackTarget ? 4 : 5, min, max);
msg.Write((byte)(Removed ? 255 : Array.IndexOf(AnimController.Limbs, attackLimb)));
msg.Write(targetEntityID);
msg.Write((byte)targetLimbIndex);
msg.Write(extraData.Length > 4 ? (float)extraData[4] : 0);
msg.Write(extraData.Length > 5 ? (float)extraData[5] : 0);
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
msg.WriteRangedInteger(6, min, max);
msg.Write((byte)CampaignInteractionType);
msg.Write(RequireConsciousnessForCustomInteract);
break;
case NetEntityEvent.Type.ObjectiveManagerState:
msg.WriteRangedInteger(7, min, max);
int type = (extraData[1] as string) switch
{
"order" => 1,
"objective" => 2,
_ => 0
};
msg.WriteRangedInteger(type, 0, 2);
if (!(AIController is HumanAIController controller))
{
msg.Write(false);
break;
}
if (type == 1)
{
var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo();
bool validOrder = currentOrderInfo.HasValue;
msg.Write(validOrder);
if (!validOrder) { break; }
var orderPrefab = currentOrderInfo.Value.Order.Prefab;
int orderIndex = Order.PrefabList.IndexOf(orderPrefab);
msg.WriteRangedInteger(orderIndex, 0, Order.PrefabList.Count);
if (!orderPrefab.HasOptions) { break; }
int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Value.OrderOption);
if (optionIndex == -1)
{
DebugConsole.AddWarning($"Error while writing order data. Order option \"{(currentOrderInfo.Value.OrderOption ?? null)}\" not found in the order prefab \"{orderPrefab.Name}\".");
}
msg.WriteRangedInteger(optionIndex, -1, orderPrefab.AllOptions.Length);
}
else if (type == 2)
{
var objective = controller.ObjectiveManager.CurrentObjective;
bool validObjective = !string.IsNullOrEmpty(objective?.Identifier);
msg.Write(validObjective);
if (!validObjective) { break; }
msg.Write(objective.Identifier);
msg.Write(objective.Option ?? "");
UInt16 targetEntityId = 0;
if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null)
{
targetEntityId = operateObjective.OperateTarget.ID;
}
msg.Write(targetEntityId);
}
break;
case NetEntityEvent.Type.TeamChange:
msg.WriteRangedInteger(8, min, max);
msg.Write((byte)TeamID);
break;
case NetEntityEvent.Type.AddToCrew:
msg.WriteRangedInteger(9, min, max);
msg.Write((byte)(CharacterTeamType)extraData[1]); // team id
ushort[] inventoryItemIDs = (ushort[])extraData[2];
msg.Write((ushort)inventoryItemIDs.Length);
for (int i = 0; i < inventoryItemIDs.Length; i++)
{
msg.Write(inventoryItemIDs[i]);
}
break;
case NetEntityEvent.Type.UpdateExperience:
msg.WriteRangedInteger(10, min, max);
msg.Write(Info.ExperiencePoints);
break;
case NetEntityEvent.Type.UpdateTalents:
msg.WriteRangedInteger(11, min, max);
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.AddedThisRound);
msg.Write(unlockedTalent.Prefab.UIntIdentifier);
}
break;
case NetEntityEvent.Type.UpdateMoney:
msg.WriteRangedInteger(12, min, max);
msg.Write(GameMain.GameSession.Campaign.Money);
break;
case NetEntityEvent.Type.UpdatePermanentStats:
msg.WriteRangedInteger(13, min, max);
if (Info == null || extraData.Length < 2 || !(extraData[1] is StatTypes statType))
{
msg.Write((byte)0);
msg.Write((byte)0);
}
else if (!Info.SavedStatValues.ContainsKey(statType))
{
msg.Write((byte)0);
msg.Write((byte)statType);
}
else
{
msg.Write((byte)Info.SavedStatValues[statType].Count);
msg.Write((byte)statType);
foreach (var savedStatValue in Info.SavedStatValues[statType])
{
msg.Write(savedStatValue.StatIdentifier);
msg.Write(savedStatValue.StatValue);
msg.Write(savedStatValue.RemoveOnDeath);
}
}
break;
default:
DebugConsole.ThrowError("Invalid NetworkEvent type for entity " + ToString() + " (" + (NetEntityEvent.Type)extraData[0] + ")");
break;
tempBuffer.Write((UInt16)0);
}
else
{
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
}
msg.WritePadBits();
}
else
{
msg.Write(ID);
tempBuffer.Write(false);
IWriteMessage tempBuffer = new WriteOnlyMessage();
bool aiming = false;
bool use = false;
bool attack = false;
bool shoot = false;
if (this == c.Character)
if (IsRemotePlayer)
{
tempBuffer.Write(true);
if (LastNetworkUpdateID < memInput.Count + 1)
aiming = dequeuedInput.HasFlag(InputNetFlags.Aim);
use = dequeuedInput.HasFlag(InputNetFlags.Use);
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot);
}
else if (keys != null)
{
aiming = keys[(int)InputType.Aim].GetHeldQueue;
use = keys[(int)InputType.Use].GetHeldQueue;
attack = keys[(int)InputType.Attack].GetHeldQueue;
shoot = keys[(int)InputType.Shoot].GetHeldQueue;
networkUpdateSent = true;
}
tempBuffer.Write(aiming);
tempBuffer.Write(shoot);
tempBuffer.Write(use);
if (AnimController is HumanoidAnimController)
{
tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching);
}
tempBuffer.Write(attack);
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated);
tempBuffer.Write(AnimController.Dir > 0.0f);
}
if (SelectedCharacter != null || SelectedConstruction != null)
{
tempBuffer.Write(true);
tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID);
tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID);
if (SelectedCharacter != null)
{
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
}
}
else
{
tempBuffer.Write(false);
}
tempBuffer.Write(SimPosition.X);
tempBuffer.Write(SimPosition.Y);
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
AnimController.Collider.LinearVelocity = new Vector2(
MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel),
MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel));
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12);
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
tempBuffer.Write(fixedRotation);
if (!fixedRotation)
{
tempBuffer.Write(AnimController.Collider.Rotation);
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8);
}
bool writeStatus = healthUpdateTimer <= 0.0f;
tempBuffer.Write(writeStatus);
if (writeStatus)
{
WriteStatus(tempBuffer);
AIController?.ServerWrite(tempBuffer);
HealthUpdatePending = false;
}
tempBuffer.WritePadBits();
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
}
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}, got {extraData?.GetType().Name ?? "[NULL]"}"); }
msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData)
{
case InventoryStateEventData _:
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
Inventory.ServerEventWrite(msg, c);
break;
case ControlEventData controlEventData:
Client owner = controlEventData.Owner;
msg.Write(owner == c && owner.Character == this);
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0);
break;
case CharacterStatusEventData _:
WriteStatus(msg);
break;
case UpdateSkillsEventData _:
if (Info?.Job == null)
{
tempBuffer.Write((UInt16)0);
msg.Write((byte)0);
}
else
{
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
msg.Write((byte)Info.Job.Skills.Count);
foreach (Skill skill in Info.Job.Skills)
{
msg.Write(skill.Identifier);
msg.Write(skill.Level);
}
}
}
else
{
tempBuffer.Write(false);
bool aiming = false;
bool use = false;
bool attack = false;
bool shoot = false;
if (IsRemotePlayer)
break;
case IAttackEventData attackEventData:
{
aiming = dequeuedInput.HasFlag(InputNetFlags.Aim);
use = dequeuedInput.HasFlag(InputNetFlags.Use);
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
shoot = dequeuedInput.HasFlag(InputNetFlags.Shoot);
int attackLimbIndex = Removed ? -1 : Array.IndexOf(AnimController.Limbs, attackEventData.AttackLimb);
ushort targetEntityId = 0;
int targetLimbIndex = -1;
if (attackEventData.TargetEntity is Entity { Removed: false } targetEntity)
{
targetEntityId = targetEntity.ID;
if (targetEntity is Character { AnimController: { Limbs: var targetLimbsArray } })
{
targetLimbIndex = targetLimbsArray.IndexOf(attackEventData.TargetLimb);
}
}
msg.Write((byte)(attackLimbIndex < 0 ? 255 : attackLimbIndex));
msg.Write((ushort)targetEntityId);
msg.Write((byte)(targetLimbIndex < 0 ? 255 : targetLimbIndex));
msg.Write(attackEventData.TargetSimPos.X);
msg.Write(attackEventData.TargetSimPos.Y);
}
else if (keys != null)
break;
case AssignCampaignInteractionEventData _:
msg.Write((byte)CampaignInteractionType);
msg.Write(RequireConsciousnessForCustomInteract);
break;
case ObjectiveManagerStateEventData objectiveManagerStateEventData:
AIObjectiveManager.ObjectiveType type = objectiveManagerStateEventData.ObjectiveType;
msg.WriteRangedInteger((int)type, (int)AIObjectiveManager.ObjectiveType.MinValue, (int)AIObjectiveManager.ObjectiveType.MaxValue);
if (!(AIController is HumanAIController controller))
{
aiming = keys[(int)InputType.Aim].GetHeldQueue;
use = keys[(int)InputType.Use].GetHeldQueue;
attack = keys[(int)InputType.Attack].GetHeldQueue;
shoot = keys[(int)InputType.Shoot].GetHeldQueue;
networkUpdateSent = true;
msg.Write(false);
break;
}
tempBuffer.Write(aiming);
tempBuffer.Write(shoot);
tempBuffer.Write(use);
if (AnimController is HumanoidAnimController)
if (type == AIObjectiveManager.ObjectiveType.Order)
{
tempBuffer.Write(((HumanoidAnimController)AnimController).Crouching);
var currentOrderInfo = controller.ObjectiveManager.GetCurrentOrderInfo();
bool validOrder = currentOrderInfo != null;
msg.Write(validOrder);
if (!validOrder) { break; }
var orderPrefab = currentOrderInfo.Prefab;
msg.Write(orderPrefab.UintIdentifier);
if (!orderPrefab.HasOptions) { break; }
int optionIndex = orderPrefab.AllOptions.IndexOf(currentOrderInfo.Option);
if (optionIndex == -1)
{
DebugConsole.AddWarning($"Error while writing order data. Order option \"{currentOrderInfo.Option}\" not found in the order prefab \"{orderPrefab.Name}\".");
}
msg.WriteRangedInteger(optionIndex, -1, orderPrefab.AllOptions.Length);
}
tempBuffer.Write(attack);
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
tempBuffer.Write(IsRagdolled || Stun > 0.0f || IsDead || IsIncapacitated);
tempBuffer.Write(AnimController.Dir > 0.0f);
}
if (SelectedCharacter != null || SelectedConstruction != null)
{
tempBuffer.Write(true);
tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID);
tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID);
if (SelectedCharacter != null)
else if (type == AIObjectiveManager.ObjectiveType.Objective)
{
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
var objective = controller.ObjectiveManager.CurrentObjective;
bool validObjective = objective?.Identifier is { IsEmpty: false };
msg.Write(validObjective);
if (!validObjective) { break; }
msg.Write(objective.Identifier);
msg.Write(objective.Option);
UInt16 targetEntityId = 0;
if (objective is AIObjectiveOperateItem operateObjective && operateObjective.OperateTarget != null)
{
targetEntityId = operateObjective.OperateTarget.ID;
}
msg.Write(targetEntityId);
}
}
else
{
tempBuffer.Write(false);
}
tempBuffer.Write(SimPosition.X);
tempBuffer.Write(SimPosition.Y);
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
AnimController.Collider.LinearVelocity = new Vector2(
MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel),
MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel));
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12);
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
tempBuffer.Write(fixedRotation);
if (!fixedRotation)
{
tempBuffer.Write(AnimController.Collider.Rotation);
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
AnimController.Collider.AngularVelocity = NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8);
}
bool writeStatus = healthUpdateTimer <= 0.0f;
tempBuffer.Write(writeStatus);
if (writeStatus)
{
WriteStatus(tempBuffer);
AIController?.ServerWrite(tempBuffer);
HealthUpdatePending = false;
}
tempBuffer.WritePadBits();
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
break;
case TeamChangeEventData _:
msg.Write((byte)TeamID);
break;
case AddToCrewEventData addToCrewEventData:
msg.Write((byte)addToCrewEventData.TeamType); // team id
ushort[] inventoryItemIDs = addToCrewEventData.InventoryItems.Select(item => item.ID).ToArray();
msg.Write((ushort)inventoryItemIDs.Length);
for (int i = 0; i < inventoryItemIDs.Length; i++)
{
msg.Write(inventoryItemIDs[i]);
}
break;
case UpdateExperienceEventData _:
msg.Write(Info.ExperiencePoints);
break;
case UpdateTalentsEventData _:
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.AddedThisRound);
msg.Write(unlockedTalent.Prefab.UintIdentifier);
}
break;
case UpdateMoneyEventData _:
msg.Write(GameMain.GameSession.Campaign.GetWallet(c).Balance);
break;
case UpdatePermanentStatsEventData updatePermanentStatsEventData:
StatTypes statType = updatePermanentStatsEventData.StatType;
if (Info == null)
{
msg.Write((byte)0);
msg.Write((byte)0);
}
else if (!Info.SavedStatValues.ContainsKey(statType))
{
msg.Write((byte)0);
msg.Write((byte)statType);
}
else
{
msg.Write((byte)Info.SavedStatValues[statType].Count);
msg.Write((byte)statType);
foreach (var savedStatValue in Info.SavedStatValues[statType])
{
msg.Write(savedStatValue.StatIdentifier);
msg.Write(savedStatValue.StatValue);
msg.Write(savedStatValue.RemoveOnDeath);
}
}
break;
default:
throw new Exception($"Malformed character event: did not expect {eventData.GetType().Name}");
}
}
@@ -623,9 +602,9 @@ namespace Barotrauma
public void WriteSpawnData(IWriteMessage msg, UInt16 entityId, bool restrictMessageSize)
{
if (GameMain.Server == null) return;
if (GameMain.Server == null) { return; }
int msgLength = msg.LengthBytes;
int initialMsgLength = msg.LengthBytes;
msg.Write(Info == null);
msg.Write(entityId);
@@ -657,6 +636,8 @@ namespace Barotrauma
{
msg.Write(true);
msg.Write(ownerClient.ID);
msg.Write(Wallet.Balance);
msg.WriteRangedInteger(Wallet.RewardDistribution, 0, 100);
}
else if (GameMain.Server.Character == this)
{
@@ -671,43 +652,64 @@ namespace Barotrauma
msg.Write((byte)TeamID);
msg.Write(this is AICharacter);
msg.Write(info.SpeciesName);
int msgLengthBeforeInfo = msg.LengthBytes;
info.ServerWrite(msg);
int infoLength = msg.LengthBytes - msgLengthBeforeInfo;
msg.Write((byte)CampaignInteractionType);
if (CampaignInteractionType == CampaignMode.InteractionType.Store)
{
msg.Write(MerchantIdentifier);
}
int msgLengthBeforeOrders = msg.LengthBytes;
// Current orders
msg.Write((byte)info.CurrentOrders.Count(o => o.Order != null));
msg.Write((byte)info.CurrentOrders.Count(o => o != null));
foreach (var orderInfo in info.CurrentOrders)
{
if (orderInfo.Order == null) { continue; }
msg.Write((byte)Order.PrefabList.IndexOf(orderInfo.Order.Prefab));
msg.Write(orderInfo.Order.TargetEntity == null ? (UInt16)0 : orderInfo.Order.TargetEntity.ID);
var hasOrderGiver = orderInfo.Order.OrderGiver != null;
if (orderInfo == null) { continue; }
msg.Write(orderInfo.Prefab.UintIdentifier);
msg.Write(orderInfo.TargetEntity == null ? (UInt16)0 : orderInfo.TargetEntity.ID);
var hasOrderGiver = orderInfo.OrderGiver != null;
msg.Write(hasOrderGiver);
if (hasOrderGiver) { msg.Write(orderInfo.Order.OrderGiver.ID); }
msg.Write((byte)(string.IsNullOrWhiteSpace(orderInfo.OrderOption) ? 0 : Array.IndexOf(orderInfo.Order.Prefab.Options, orderInfo.OrderOption)));
if (hasOrderGiver) { msg.Write(orderInfo.OrderGiver.ID); }
msg.Write((byte)(orderInfo.Option == Identifier.Empty ? 0 : orderInfo.Prefab.Options.IndexOf(orderInfo.Option)));
msg.Write((byte)orderInfo.ManualPriority);
var hasTargetPosition = orderInfo.Order.TargetPosition != null;
var hasTargetPosition = orderInfo.TargetPosition != null;
msg.Write(hasTargetPosition);
if (hasTargetPosition)
{
msg.Write(orderInfo.Order.TargetPosition.Position.X);
msg.Write(orderInfo.Order.TargetPosition.Position.Y);
msg.Write(orderInfo.Order.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.Order.TargetPosition.Hull.ID);
msg.Write(orderInfo.TargetPosition.Position.X);
msg.Write(orderInfo.TargetPosition.Position.Y);
msg.Write(orderInfo.TargetPosition.Hull == null ? (UInt16)0 : orderInfo.TargetPosition.Hull.ID);
}
}
int ordersLength = msg.LengthBytes - msgLengthBeforeOrders;
if (msg.LengthBytes - initialMsgLength >= 255 && restrictMessageSize)
{
string errorMsg = $"Error when writing character spawn data: data exceeded 255 bytes (info: {infoLength}, orders: {ordersLength}, total: {msg.LengthBytes - initialMsgLength})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Character.WriteSpawnData:TooMuchData", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
TryWriteStatus(msg);
void TryWriteStatus(IWriteMessage msg)
{
int msgLengthBeforeStatus = msg.LengthBytes - initialMsgLength;
var tempBuffer = new ReadWriteMessage();
WriteStatus(tempBuffer);
if (msg.LengthBytes + tempBuffer.LengthBytes >= 255 && restrictMessageSize && GameMain.Lua.networking.restrictMessageSize)
if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize)
{
msg.Write(false);
DebugConsole.ThrowError($"Error when writing character spawn data: status data caused the length of the message to exceed 255 bytes ({msg.LengthBytes} + {tempBuffer.LengthBytes})");
if (msgLengthBeforeStatus < 255)
{
string errorMsg = $"Error when writing character spawn data: status data caused the length of the message to exceed 255 bytes ({msgLengthBeforeStatus} + {tempBuffer.LengthBytes})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Character.WriteSpawnData:TooMuchDataForStatus", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
}
else
{
@@ -716,7 +718,7 @@ namespace Barotrauma
}
}
DebugConsole.Log("Character spawn message length: " + (msg.LengthBytes - msgLength));
DebugConsole.Log("Character spawn message length: " + (msg.LengthBytes - initialMsgLength));
}
}
}
@@ -1,16 +1,12 @@
using Barotrauma.Networking;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using FarseerPhysics;
using Barotrauma.Items.Components;
using System.Threading;
using Barotrauma.IO;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
namespace Barotrauma
{
@@ -98,7 +94,7 @@ namespace Barotrauma
{
ColoredText msg = queuedMessages.Dequeue();
Messages.Add(msg);
if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
{
unsavedMessages.Add(msg);
if (unsavedMessages.Count >= messagesPerFile)
@@ -281,7 +277,7 @@ namespace Barotrauma
{
var msg = queuedMessages.Dequeue();
Messages.Add(msg);
if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
{
unsavedMessages.Add(msg);
if (unsavedMessages.Count >= messagesPerFile)
@@ -392,7 +388,7 @@ namespace Barotrauma
if (float.TryParse(args[0], out seconds))
{
seconds = Math.Max(0, seconds);
GameMain.Server.SendConsoleMessage("Set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds), client);
GameMain.Server.SendConsoleMessage("Set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds).Value, client);
NewMessage(client.Name + " set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds), Color.White);
}
else
@@ -955,7 +951,7 @@ namespace Barotrauma
return;
}
client.Muted = true;
GameMain.Server.SendDirectChatMessage(TextManager.Get("MutedByServer"), client, ChatMessageType.MessageBox);
GameMain.Server.SendDirectChatMessage(TextManager.Get("MutedByServer").Value, client, ChatMessageType.MessageBox);
},
() =>
{
@@ -975,7 +971,7 @@ namespace Barotrauma
return;
}
client.Muted = false;
GameMain.Server.SendDirectChatMessage(TextManager.Get("UnmutedByServer"), client, ChatMessageType.MessageBox);
GameMain.Server.SendDirectChatMessage(TextManager.Get("UnmutedByServer").Value, client, ChatMessageType.MessageBox);
},
() =>
{
@@ -1102,7 +1098,7 @@ namespace Barotrauma
TraitorManager traitorManager = GameMain.Server.TraitorManager;
if (traitorManager == null || traitorManager.Traitors == null || !traitorManager.Traitors.Any())
{
GameMain.Server.SendTraitorMessage(client, "There are no traitors at the moment.", "", TraitorMessageType.Console);
GameMain.Server.SendTraitorMessage(client, "There are no traitors at the moment.", Identifier.Empty, TraitorMessageType.Console);
return;
}
foreach (Traitor t in traitorManager.Traitors)
@@ -1116,11 +1112,11 @@ namespace Barotrauma
$"[traitorgoals]={traitorGoals.Substring(traitorGoalsStart)}",
$"[traitorname]={t.Character.Name}",
"Traitor [traitorname]'s current goals are:\n[traitorgoals]"
}.Where(s => !string.IsNullOrEmpty(s))), t.Mission?.Identifier, TraitorMessageType.Console);
}.Where(s => !string.IsNullOrEmpty(s))), t.Mission.Identifier, TraitorMessageType.Console);
}
else
{
GameMain.Server.SendTraitorMessage(client, string.Format("- Traitor {0} has no current objective.", "", t.Character.Name), "", TraitorMessageType.Console);
GameMain.Server.SendTraitorMessage(client, string.Format("- Traitor {0} has no current objective.", t.Character.Name), Identifier.Empty, TraitorMessageType.Console);
}
}
//GameMain.Server.SendTraitorMessage(client, "The code words are: " + traitorManager.CodeWords + ", response: " + traitorManager.CodeResponse + ".", TraitorMessageType.Console);
@@ -1187,7 +1183,7 @@ namespace Barotrauma
NewMessage("*****************", Color.Lime);
GameServer.Log("Console command \"restart\" executed: closing the server...", ServerLog.MessageType.ServerMessage);
GameMain.Instance.CloseServer();
GameMain.Instance.TryStartChildServerRelay();
Program.TryStartChildServerRelay(GameMain.Instance.CommandLineArgs);
GameMain.Instance.StartServer();
}));
@@ -1340,7 +1336,7 @@ namespace Barotrauma
{
return new string[][]
{
GameModePreset.List.Select(gm => gm.Name).ToArray()
GameModePreset.List.Select(gm => gm.Name.Value).ToArray()
};
}));
@@ -1444,17 +1440,71 @@ namespace Barotrauma
commands.Add(new Command("eventdata", "", (string[] args) =>
{
if (args.Length == 0) { return; }
if (!UInt16.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out ushort eventId)) { return; }
ServerEntityEvent ev = GameMain.Server.EntityEventManager.Events.Find(ev => ev.ID == eventId);
if (ev != null)
string indentStr(string s)
=> string.Join('\n', s.Split('\n').Select(sub => $" {sub}"));
string eventDataRip(object data)
{
if (data is null) { return "[NULL]"; }
var type = data.GetType();
string retVal = $"{type.FullName} ";
if (type.IsPrimitive
|| type.IsEnum
|| type.IsClass)
{
retVal += data.ToString();
return retVal;
}
retVal += "{\n";
var fields = data.GetType().GetFields();
foreach (var field in fields)
{
retVal += indentStr($"{field.Name}: {eventDataRip(field.GetValue(data))}")+"\n";
}
retVal += "}";
retVal = retVal.Replace("{\n}", "{ }");
return retVal;
}
string eventDebugStr(ServerEntityEvent ev)
{
ushort eventId = ev.ID;
string entityData = "";
if (ev.Entity is { ID: var entityId, Removed: var removed, IdFreed: var idFreed })
{
entityData = $"Entity ID: {entityId}; Entity removed: {removed}; Entity ID freed: {idFreed}";
entityData = $"Entity ID: {entityId}\n" +
$"Entity type {ev.Entity.GetType().Name}\n" +
$"Entity removed: {removed}\n" +
$"Entity ID freed: {idFreed}\n" +
$"Event data: {eventDataRip(ev.Data)}\n";
}
NewMessage($"EventData {eventId}\n{entityData}", Color.Lime);
//NewMessage(ev.StackTrace.CleanupStackTrace(), Color.Lime);
return $"EventData {eventId}\n{indentStr(entityData)}";
}
IReadOnlyList<ServerEntityEvent> events = GameMain.Server.EntityEventManager.Events;
ushort? eventId = null;
if (args[0].Equals("latest", StringComparison.OrdinalIgnoreCase))
{
eventId = events.Max(e => e.ID);
}
else if (UInt16.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out ushort id))
{
eventId = id;
}
IEnumerable<ServerEntityEvent> matchedEvents = GameMain.Server.EntityEventManager.Events.Where(ev
=> eventId.HasValue
? ev.ID == eventId
: eventDebugStr(ev).Contains(args[0], StringComparison.OrdinalIgnoreCase));
foreach (var ev in matchedEvents)
{
NewMessage(eventDebugStr(ev), Color.Lime);
}
}));
@@ -1709,28 +1759,27 @@ namespace Barotrauma
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (args.Length < 2) return;
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
a.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase));
string affliction = args[0];
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => a.Identifier == affliction);
if (afflictionPrefab == null)
{
GameMain.Server.SendConsoleMessage("Affliction \"" + args[0] + "\" not found.", client, Color.Red);
afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => a.Name.Equals(affliction, StringComparison.OrdinalIgnoreCase));
}
if (afflictionPrefab == null)
{
GameMain.Server.SendConsoleMessage("Affliction \"" + affliction + "\" not found.", client, Color.Red);
return;
}
if (!float.TryParse(args[1], out float afflictionStrength))
{
GameMain.Server.SendConsoleMessage("\"" + args[1] + "\" is not a valid affliction strength.", client, Color.Red);
return;
}
bool relativeStrength = false;
if (args.Length > 4)
{
bool.TryParse(args[4], out relativeStrength);
}
Character targetCharacter = (args.Length <= 2) ? client.Character : FindMatchingCharacter(args.Skip(2).ToArray());
if (targetCharacter != null)
{
@@ -1800,7 +1849,7 @@ namespace Barotrauma
if (targetCharacter == null) { return; }
TalentPrefab talentPrefab = TalentPrefab.TalentPrefabs.Find(c =>
c.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
c.Identifier == args[0] ||
c.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (talentPrefab == null)
{
@@ -1833,7 +1882,7 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage($"Failed to find the job \"{args[0]}\".", client, Color.Red);
return;
}
if (!TalentTree.JobTalentTrees.TryGetValue(job.Identifier, out TalentTree talentTree))
if (!TalentTree.JobTalentTrees.TryGet(job.Identifier, out TalentTree talentTree))
{
GameMain.Server.SendConsoleMessage($"No talents configured for the job \"{args[0]}\".", client, Color.Red);
return;
@@ -2054,7 +2103,7 @@ namespace Barotrauma
client.SetPermissions(preset.Permissions, preset.PermittedCommands);
GameMain.Server.UpdateClientPermissions(client);
GameMain.Server.SendConsoleMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", senderClient);
GameMain.Server.SendConsoleMessage($"Assigned the rank \"{preset.Name}\" to {client.Name}.", senderClient);
NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White);
}
);
@@ -2202,7 +2251,7 @@ namespace Barotrauma
foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
{
if (permission == ClientPermissions.None || !client.HasPermission(permission)) { continue; }
GameMain.Server.SendConsoleMessage(" - " + TextManager.Get("ClientPermission." + permission), senderClient);
GameMain.Server.SendConsoleMessage($" - {TextManager.Get("ClientPermission." + permission)}", senderClient);
}
if (client.HasPermission(ClientPermissions.ConsoleCommands))
{
@@ -2229,11 +2278,6 @@ namespace Barotrauma
if (args.Length < 2)
{
GameMain.Server.SendConsoleMessage("Invalid parameters. The command should be formatted as \"setclientcharacter [client] [character]\". If the names consist of multiple words, you should surround them with quotation marks.", senderClient, Color.Red);
return;
}
if (args.Length < 2)
{
ThrowError("Invalid parameters. The command should be formatted as \"setclientcharacter [client] [character]\". If the names consist of multiple words, you should surround them with quotation marks.");
return;
}
@@ -2261,18 +2305,50 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage("No campaign active!", senderClient, Color.Red);
return;
}
Character targetCharacter = null;
if (args.Length >= 2)
{
targetCharacter = FindMatchingCharacter(args.Skip(1).ToArray());
}
if (int.TryParse(args[0], out int money))
{
campaign.Money += money;
Wallet wallet = targetCharacter is null ? campaign.Bank : targetCharacter.Wallet;
wallet.Give(money);
GameAnalyticsManager.AddMoneyGainedEvent(money, GameAnalyticsManager.MoneySource.Cheat, "console");
campaign.LastUpdateID++;
}
else
{
GameMain.Server.SendConsoleMessage($"\"{args[0]}\" is not a valid numeric value.", senderClient, Color.Red);
}
}
}
);
AssignOnClientRequestExecute(
"showmoney",
(Client senderClient, Vector2 cursorWorldPos, string[] args) =>
{
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign))
{
GameMain.Server.SendConsoleMessage("No campaign active!", senderClient, Color.Red);
return;
}
StringBuilder sb = new StringBuilder();
sb.Append($"Bank: {campaign.Bank.Balance}");
foreach (Client client in GameMain.Server.ConnectedClients)
{
if (client.Character is null) { continue; }
sb.Append(Environment.NewLine);
sb.Append($"{client.Name}: {client.Character.Wallet.Balance}");
}
GameMain.Server.SendConsoleMessage(sb.ToString(), senderClient);
}
);
AssignOnClientRequestExecute(
"campaigndestination|setcampaigndestination",
(Client senderClient, Vector2 cursorWorldPos, string[] args) =>
@@ -2301,7 +2377,7 @@ namespace Barotrauma
var tagList = MapEntityPrefab.List.SelectMany(p => p.Tags.Select(t => t)).Distinct();
foreach (var tag in tagList)
{
NewMessage(tag, Color.Yellow);
NewMessage(tag.Value, Color.Yellow);
}
}));
@@ -2342,7 +2418,7 @@ namespace Barotrauma
return;
}
string skillIdentifier = args[0];
Identifier skillIdentifier = args[0].ToIdentifier();
string levelString = args[1];
Character character = args.Length >= 3 ? FindMatchingCharacter(args.Skip(2).ToArray(), false) : senderClient.Character;
@@ -2357,7 +2433,7 @@ namespace Barotrauma
if (float.TryParse(levelString, NumberStyles.Number, CultureInfo.InvariantCulture, out float level) || isMax)
{
if (isMax) { level = 100; }
if (skillIdentifier.Equals("all", StringComparison.OrdinalIgnoreCase))
if (skillIdentifier == "all")
{
foreach (Skill skill in character.Info.Job.Skills)
{
@@ -2371,7 +2447,7 @@ namespace Barotrauma
GameMain.Server.SendConsoleMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", senderClient);
}
GameMain.NetworkMember.CreateEntityEvent(character, new object[] { NetEntityEvent.Type.UpdateSkills });
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdateSkillsEventData());
}
else
{
@@ -2441,7 +2517,7 @@ namespace Barotrauma
{
GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status });
}*/
foreach (Hull hull in Hull.hullList)
foreach (Hull hull in Hull.HullList)
{
GameMain.Server.CreateEntityEvent(hull);
}
@@ -80,7 +80,7 @@ namespace Barotrauma
partial void ShowDialog(Character speaker, Character targetCharacter)
{
targetClients.Clear();
if (!string.IsNullOrEmpty(TargetTag))
if (!TargetTag.IsEmpty)
{
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
foreach (Entity e in entities)
@@ -15,7 +15,7 @@ namespace Barotrauma
msg.Write((ushort)spawnedItems.Count);
foreach (Item item in spawnedItems)
{
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0, -1);
}
msg.Write((byte)characters.Count);
@@ -27,7 +27,7 @@ namespace Barotrauma
msg.Write((ushort)characterItems[character].Count());
foreach (Item item in characterItems[character])
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1);
}
}
}
@@ -13,7 +13,8 @@ namespace Barotrauma
item.WriteSpawnData(msg,
item.ID,
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID,
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0);
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0,
inventorySlotIndices.ContainsKey(item) ? inventorySlotIndices[item] : -1);
}
}
}
@@ -11,11 +11,11 @@ namespace Barotrauma
private bool initialized = false;
public override string Description
public override LocalizedString Description
{
get
{
if (descriptions == null) return "";
if (descriptions == null) { return ""; }
//non-team-specific description
return descriptions[0];
@@ -24,7 +24,7 @@ namespace Barotrauma
msg.Write((ushort)characterItems[character].Count());
foreach (Item item in characterItems[character])
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1);
}
}
}
@@ -16,11 +16,11 @@ namespace Barotrauma
foreach (var kvp in spawnedResources)
{
msg.Write((byte)kvp.Value.Count);
var rotation = resourceClusters[kvp.Key].rotation;
var rotation = resourceClusters[kvp.Key].Rotation;
msg.Write(rotation);
foreach (var r in kvp.Value)
{
r.WriteSpawnData(msg, r.ID, Entity.NullEntityID, 0);
r.WriteSpawnData(msg, r.ID, Entity.NullEntityID, 0, -1);
}
}
@@ -7,13 +7,13 @@ namespace Barotrauma
partial void ShowMessageProjSpecific(int missionState)
{
int messageIndex = missionState - 1;
if (messageIndex >= Headers.Count && messageIndex >= Messages.Count) { return; }
if (messageIndex >= Headers.Length && messageIndex >= Messages.Length) { return; }
if (messageIndex < 0) { return; }
string header = messageIndex < Headers.Count ? Headers[messageIndex] : "";
string message = messageIndex < Messages.Count ? Messages[messageIndex] : "";
LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : "";
LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : "";
GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage);
GameServer.Log($"{TextManager.Get("MissionInfo")}: {header} - {message}", ServerLog.MessageType.ServerMessage);
}
public virtual void ServerWriteInitial(IWriteMessage msg, Client c)
@@ -15,7 +15,7 @@ namespace Barotrauma
msg.Write((ushort)items.Count);
foreach (Item item in items)
{
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0, -1);
}
}
}
@@ -24,7 +24,7 @@ namespace Barotrauma
msg.Write((ushort)characterItems[character].Count());
foreach (Item item in characterItems[character])
{
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0);
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1);
}
}
}
@@ -10,6 +10,7 @@ namespace Barotrauma
private UInt16 originalInventoryID;
private byte originalItemContainerIndex;
private int originalSlotIndex;
private readonly List<Pair<int, int>> executedEffectIndices = new List<Pair<int, int>>();
@@ -24,7 +25,7 @@ namespace Barotrauma
}
else
{
item.WriteSpawnData(msg, item.ID, originalInventoryID, originalItemContainerIndex);
item.WriteSpawnData(msg, item.ID, originalInventoryID, originalItemContainerIndex, originalSlotIndex);
}
msg.Write((byte)executedEffectIndices.Count);
@@ -13,7 +13,8 @@ namespace Barotrauma
item.WriteSpawnData(msg,
item.ID,
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID,
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0);
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0,
inventorySlotIndices.ContainsKey(item) ? inventorySlotIndices[item] : -1);
}
ServerWriteScanTargetStatus(msg);
}
@@ -12,6 +12,7 @@ using System.Threading;
using System.Xml.Linq;
using MoonSharp.Interpreter;
using System.Net;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -22,7 +23,6 @@ namespace Barotrauma
public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null;
private static World world;
public static World World
{
@@ -33,7 +33,7 @@ namespace Barotrauma
}
set { world = value; }
}
public static GameSettings Config;
public static LuaSetup Lua;
public static GameServer Server;
@@ -61,28 +61,14 @@ namespace Barotrauma
//TODO: maybe clean up instead of having these constants
public static readonly Screen SubEditorScreen = UnimplementedScreen.Instance;
public static DecalManager DecalManager;
public static bool ShouldRun = true;
private static Stopwatch stopwatch;
private static Queue<int> prevUpdateRates = new Queue<int>();
private static readonly Queue<int> prevUpdateRates = new Queue<int>();
private static int updateCount = 0;
private static ContentPackage vanillaContent;
public static ContentPackage VanillaContent
{
get
{
if (vanillaContent == null)
{
// TODO: Dynamic method for defining and finding the vanilla content package.
vanillaContent = ContentPackage.CorePackages.SingleOrDefault(cp => Path.GetFileName(cp.Path).Equals("vanilla 0.9.xml", StringComparison.OrdinalIgnoreCase));
}
return vanillaContent;
}
}
public static ContentPackage VanillaContent => ContentPackageManager.VanillaCorePackage;
public readonly string[] CommandLineArgs;
@@ -99,13 +85,14 @@ namespace Barotrauma
FarseerPhysics.Settings.PositionIterations = 1;
Console.WriteLine("Loading game settings");
Config = new GameSettings();
GameSettings.Init();
Console.WriteLine("Loading MD5 hash cache");
Md5Hash.LoadCache();
Md5Hash.Cache.Load();
Console.WriteLine("Initializing SteamManager");
SteamManager.Initialize();
//TODO: figure out how consent is supposed to work for servers
//Console.WriteLine("Initializing GameAnalytics");
//GameAnalyticsManager.InitIfConsented();
@@ -118,36 +105,12 @@ namespace Barotrauma
public void Init()
{
NPCSet.LoadSets();
FactionPrefab.LoadFactions();
CharacterPrefab.LoadAll();
MissionPrefab.Init();
TraitorMissionPrefab.Init();
MapEntityPrefab.Init();
MapGenerationParams.Init();
LevelGenerationParams.LoadPresets();
CaveGenerationParams.LoadPresets();
OutpostGenerationParams.LoadPresets();
EventSet.LoadPrefabs();
Order.Init();
EventManagerSettings.Init();
ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item));
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure));
UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules));
JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs));
CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses));
NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations));
ItemAssemblyPrefab.LoadAll();
LevelObjectPrefab.LoadAll();
BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature));
TalentPrefab.LoadAll(GetFilesOfType(ContentType.Talents));
TalentTree.LoadAll(GetFilesOfType(ContentType.TalentTrees));
CoreEntityPrefab.InitCorePrefabs();
GameModePreset.Init();
DecalManager = new DecalManager();
LocationType.Init();
ContentPackageManager.Init().Consume();
ContentPackageManager.LogEnabledRegularPackageErrors();
SubmarineInfo.RefreshSavedSubs();
@@ -185,37 +148,6 @@ namespace Barotrauma
}*/
}
/// <summary>
/// Returns the file paths of all files of the given type in the content packages.
/// </summary>
/// <param name="type"></param>
/// <param name="searchAllContentPackages">If true, also returns files in content packages that are installed but not currently selected.</param>
public IEnumerable<ContentFile> GetFilesOfType(ContentType type, bool searchAllContentPackages = false)
{
if (searchAllContentPackages)
{
return ContentPackage.GetFilesOfType(ContentPackage.AllPackages, type);
}
else
{
return ContentPackage.GetFilesOfType(Config.AllEnabledPackages, type);
}
}
public bool TryStartChildServerRelay()
{
for (int i = 0; i < CommandLineArgs.Length; i++)
{
switch (CommandLineArgs[i].Trim())
{
case "-pipes":
ChildServerRelay.Start(CommandLineArgs[i + 2], CommandLineArgs[i + 1]);
return true;
}
}
return false;
}
public void StartServer()
{
string name = "Server";
@@ -368,7 +300,6 @@ namespace Barotrauma
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent));
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
TryStartChildServerRelay();
Init();
StartServer();
@@ -396,6 +327,9 @@ namespace Barotrauma
//otherwise it snowballs and becomes unplayable
Timing.Accumulator = Timing.Step;
}
CrossThread.ProcessTasks();
prevTicks = currTicks;
while (Timing.Accumulator >= Timing.Step)
{
@@ -476,7 +410,8 @@ namespace Barotrauma
SaveUtil.CleanUnnecessarySaveFiles();
if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); }
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
|| GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); }
MainThread = null;
@@ -1,56 +1,63 @@
using Barotrauma.Extensions;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class CargoManager
{
public void SellBackPurchasedItems(List<PurchasedItem> itemsToSell)
public void SellBackPurchasedItems(Identifier storeIdentifier, List<PurchasedItem> itemsToSell, Client client)
{
// Check all the prices before starting the transaction
// to make sure the modifiers stay the same for the whole transaction
Dictionary<ItemPrefab, int> buyValues = GetBuyValuesAtCurrentLocation(itemsToSell.Select(i => i.ItemPrefab));
foreach (PurchasedItem item in itemsToSell)
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab));
var store = Location.GetStore(storeIdentifier);
if (store == null) { return; }
var storeSpecificItems = GetPurchasedItems(storeIdentifier);
foreach (var item in itemsToSell)
{
var itemValue = item.Quantity * buyValues[item.ItemPrefab];
Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue;
PurchasedItems.Remove(item);
store.Balance -= itemValue;
campaign.GetWallet(client).Give(itemValue);
storeSpecificItems?.Remove(item);
}
}
public void BuyBackSoldItems(List<SoldItem> itemsToBuy)
public void BuyBackSoldItems(Identifier storeIdentifier, List<SoldItem> itemsToBuy)
{
// Check all the prices before starting the transaction
// to make sure the modifiers stay the same for the whole transaction
var sellValues = GetSellValuesAtCurrentLocation(itemsToBuy.Select(i => i.ItemPrefab));
var store = Location.GetStore(storeIdentifier);
if (store == null) { return; }
var storeSpecificItems = SoldItems.GetValueOrDefault(storeIdentifier);
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
var sellValues = GetSellValuesAtCurrentLocation(storeIdentifier, itemsToBuy.Select(i => i.ItemPrefab));
foreach (var item in itemsToBuy)
{
int itemValue = sellValues[item.ItemPrefab];
if (Location.StoreCurrentBalance < itemValue || item.Removed) { continue; }
Location.StoreCurrentBalance += itemValue;
campaign.Money -= itemValue;
SoldItems.Remove(item);
if (store.Balance < itemValue || item.Removed) { continue; }
store.Balance += itemValue;
campaign.Bank.TryDeduct(itemValue);
storeSpecificItems.Remove(item);
}
}
public void SellItems(List<SoldItem> itemsToSell)
public void SellItems(Identifier storeIdentifier, List<SoldItem> itemsToSell)
{
var store = Location.GetStore(storeIdentifier);
if (store == null) { return; }
bool canAddToRemoveQueue = (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) && Entity.Spawner != null;
IEnumerable<Item> sellableItemsInSub = Enumerable.Empty<Item>();
if (canAddToRemoveQueue && itemsToSell.Any(i => i.Origin == SoldItem.SellOrigin.Submarine && i.ID == Entity.NullEntityID && !i.Removed))
{
sellableItemsInSub = GetSellableItemsFromSub();
}
// Check all the prices before starting the transaction
// to make sure the modifiers stay the same for the whole transaction
var sellValues = GetSellValuesAtCurrentLocation(itemsToSell.Select(i => i.ItemPrefab));
var itemsSoldAtStore = SoldItems.GetValueOrDefault(storeIdentifier);
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
var sellValues = GetSellValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab));
foreach (var item in itemsToSell)
{
int itemValue = sellValues[item.ItemPrefab];
// check if the store can afford the item and if the item hasn't been removed already
if (Location.StoreCurrentBalance < itemValue || item.Removed) { continue; }
if (store.Balance < itemValue || item.Removed) { continue; }
// Server determines the items that are sold from the sub in multiplayer
if (item.Origin == SoldItem.SellOrigin.Submarine && item.ID == Entity.NullEntityID && !item.Removed)
{
@@ -63,12 +70,12 @@ namespace Barotrauma
if (!item.Removed && canAddToRemoveQueue && Entity.FindEntityByID(item.ID) is Item entity)
{
item.Removed = true;
Entity.Spawner.AddToRemoveQueue(entity);
Entity.Spawner.AddItemToRemoveQueue(entity);
}
SoldItems.Add(item);
Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue;
GameAnalyticsManager.AddMoneyGainedEvent(itemValue, GameAnalyticsManager.MoneySource.Store, item.ItemPrefab.Identifier);
itemsSoldAtStore?.Add(item);
store.Balance -= itemValue;
campaign.Bank.Give(itemValue);
GameAnalyticsManager.AddMoneyGainedEvent(itemValue, GameAnalyticsManager.MoneySource.Store, item.ItemPrefab.Identifier.Value);
}
OnSoldItemsChanged?.Invoke();
}
@@ -46,14 +46,14 @@ namespace Barotrauma
public void ServerWriteActiveOrders(IWriteMessage msg)
{
ushort count = (ushort)ActiveOrders.Count(o => o.First != null && !o.Second.HasValue);
ushort count = (ushort)ActiveOrders.Count(o => o.Order != null && !o.FadeOutTime.HasValue);
msg.Write(count);
if (count > 0)
{
foreach (var activeOrder in ActiveOrders)
{
if (!(activeOrder?.First is Order order) || activeOrder.Second.HasValue) { continue; }
OrderChatMessage.WriteOrder(msg, order, targetCharacter: null, order.TargetSpatialEntity, orderOption: null, orderPriority: 0, order.WallSectionIndex, isNewOrder: true);
if (!(activeOrder?.Order is Order order) || activeOrder.FadeOutTime.HasValue) { continue; }
OrderChatMessage.WriteOrder(msg, order, null, isNewOrder: true);
bool hasOrderGiver = order.OrderGiver != null;
msg.Write(hasOrderGiver);
if (hasOrderGiver)
@@ -0,0 +1,43 @@
using System.Collections.Generic;
namespace Barotrauma
{
internal partial class Wallet
{
private readonly Queue<WalletChangedData> transactions = new Queue<WalletChangedData>();
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{
transactions.Enqueue(new WalletChangedData
{
BalanceChanged = balanceChanged,
RewardDistributionChanged = rewardChanged
});
}
public bool HasTransactions() => transactions.Count > 0;
public NetWalletTransaction DequeueAndMergeTransactions(ushort id)
{
Option<ushort> targetCharacterID = id == Entity.NullEntityID ? Option<ushort>.None() : Option<ushort>.Some(id);
WalletChangedData changedData = new WalletChangedData
{
BalanceChanged = Option<int>.None(),
RewardDistributionChanged = Option<int>.None()
};
while (transactions.TryDequeue(out WalletChangedData transactionOut))
{
changedData = changedData.MergeInto(transactionOut);
}
return new NetWalletTransaction
{
CharacterID = targetCharacterID,
ChangedData = changedData,
Info = CreateWalletInfo()
};
}
}
}
@@ -14,8 +14,8 @@ namespace Barotrauma
{
foreach (Mission mission in Missions)
{
GameServer.Log(TextManager.Get("Mission") + ": " + mission.Name, ServerLog.MessageType.ServerMessage);
GameServer.Log(mission.Description, ServerLog.MessageType.ServerMessage);
GameServer.Log($"{TextManager.Get("Mission")}: {mission.Name}", ServerLog.MessageType.ServerMessage);
GameServer.Log(mission.Description.Value, ServerLog.MessageType.ServerMessage);
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma
@@ -32,8 +31,26 @@ namespace Barotrauma
{
CharacterInfo.SaveOrderData(client.CharacterInfo, OrderData);
}
if (client.Character?.Wallet.Save() is { } walletSave)
{
WalletData = walletSave;
}
}
public void Refresh(Character character)
{
healthData = new XElement("health");
character.CharacterHealth.Save(healthData);
if (character.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(character.Inventory, itemData);
}
OrderData = new XElement("orders");
CharacterInfo.SaveOrderData(character.Info, OrderData);
WalletData = character.Wallet.Save();
}
public CharacterCampaignData(XElement element)
{
@@ -63,6 +80,9 @@ namespace Barotrauma
case "orders":
OrderData = subElement;
break;
case Wallet.LowerCaseSaveElementName:
WalletData = subElement;
break;
}
}
}
@@ -94,11 +114,11 @@ namespace Barotrauma
{
throw new System.InvalidOperationException($"Failed to spawn inventory items for the character \"{character.Name}\". No saved inventory data.");
}
character.SpawnInventoryItems(inventory, itemData);
character.SpawnInventoryItems(inventory, itemData.FromPackage(null));
}
public void ApplyHealthData(Character character)
{
{
CharacterInfo.ApplyHealthData(character, healthData);
}
@@ -106,5 +126,26 @@ namespace Barotrauma
{
CharacterInfo.ApplyOrderData(character, OrderData);
}
public void ApplyWalletData(Character character)
{
character.Wallet = new Wallet(Option<Character>.Some(character), WalletData);
}
public XElement Save()
{
XElement element = new XElement("CharacterCampaignData",
new XAttribute("name", Name),
new XAttribute("endpoint", ClientEndPoint),
new XAttribute("steamid", SteamID));
CharacterInfo?.Save(element);
if (itemData != null) { element.Add(itemData); }
if (healthData != null) { element.Add(healthData); }
if (OrderData != null) { element.Add(OrderData); }
if (WalletData != null) { element.Add(WalletData); }
return element;
}
}
}
@@ -6,8 +6,8 @@
{
foreach (Mission mission in missions)
{
Networking.GameServer.Log(TextManager.Get("Mission") + ": " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log($"{TextManager.Get("Mission")}: {mission.Name}", Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log(mission.Description.Value, Networking.ServerLog.MessageType.ServerMessage);
}
}
}
@@ -12,6 +12,22 @@ namespace Barotrauma
partial class MultiPlayerCampaign : CampaignMode
{
private readonly List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
private readonly Dictionary<ushort, Wallet> walletsToCheck = new Dictionary<ushort, Wallet>();
private readonly HashSet<NetWalletTransaction> transactions = new HashSet<NetWalletTransaction>();
private const float clientCheckInterval = 10;
private float clientCheckTimer = clientCheckInterval;
public override Wallet GetWallet(Client client = null)
{
if (client is null) { throw new ArgumentNullException(nameof(client), "Client should not be null in multiplayer"); }
if (client.Character is { } character)
{
return character.Wallet;
}
return Wallet.Invalid;
}
private bool forceMapUI;
public bool ForceMapUI
@@ -119,7 +135,7 @@ namespace Barotrauma
DebugConsole.NewMessage("Saved campaigns:", Color.White);
for (int i = 0; i < saveFiles.Length; i++)
{
DebugConsole.NewMessage(" " + i + ". " + saveFiles[i], Color.White);
DebugConsole.NewMessage(" " + i + ". " + saveFiles[i].FilePath, Color.White);
}
DebugConsole.ShowQuestionPrompt("Select a save file to load (0 - " + (saveFiles.Length - 1) + "):", (string selectedSave) =>
{
@@ -132,7 +148,7 @@ namespace Barotrauma
}
else
{
LoadCampaign(saveFiles[saveIndex]);
LoadCampaign(saveFiles[saveIndex].FilePath);
}
});
}
@@ -150,28 +166,13 @@ namespace Barotrauma
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToEndRound(Client client)
{
//allow ending the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has permissions
return
client.HasPermission(ClientPermissions.ManageRound) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c =>
c.InGame && (IsOwner(c) || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
}
/// <summary>
/// There is a client-side implementation of the method in <see cref="CampaignMode"/>
/// </summary>
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions = ClientPermissions.ManageCampaign)
public bool AllowedToManageCampaign(Client client, ClientPermissions permissions)
{
//allow managing the campaign if the client has permissions, is the owner, or the only client in the server,
//or if no-one has management permissions
return
client.HasPermission(permissions) ||
client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Server.ConnectedClients.Count == 1 ||
IsOwner(client) ||
GameMain.Server.ConnectedClients.None(c => c.InGame && (IsOwner(c) || c.HasPermission(permissions)));
@@ -229,8 +230,7 @@ namespace Barotrauma
characterInfo.CauseOfDeath = null;
}
c.CharacterInfo = characterInfo;
characterData.RemoveAll(cd => cd.MatchesClient(c));
characterData.Add(new CharacterCampaignData(c));
SetClientCharacterData(c);
}
//refresh the character data of clients who aren't in the server anymore
@@ -412,8 +412,8 @@ namespace Barotrauma
LastSaveID++;
}
public bool CanPurchaseSub(SubmarineInfo info)
=> info.Price <= Money && GetCampaignSubs().Contains(info);
public bool CanPurchaseSub(SubmarineInfo info, Client client)
=> GetWallet(client).CanAfford(info.Price) && GetCampaignSubs().Contains(info);
public void DiscardClientCharacterData(Client client)
{
@@ -487,6 +487,54 @@ namespace Barotrauma
KeepCharactersCloseToOutpost(deltaTime);
}
}
UpdateClientsToCheck(deltaTime);
UpdateWallets();
}
private void UpdateClientsToCheck(float deltaTime)
{
if (clientCheckTimer < clientCheckInterval)
{
clientCheckTimer += deltaTime;
return;
}
clientCheckTimer = 0;
walletsToCheck.Clear();
walletsToCheck.Add(0, Bank);
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Player))
{
walletsToCheck.Add(character.ID, character.Wallet);
}
}
private void UpdateWallets()
{
foreach (var (id, wallet) in walletsToCheck)
{
if (wallet.HasTransactions())
{
transactions.Add(wallet.DequeueAndMergeTransactions(id));
}
}
if (transactions.Count == 0) { return; }
NetWalletUpdate walletUpdate = new NetWalletUpdate
{
Transactions = transactions.ToArray()
};
transactions.Clear();
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ServerPacketHeader.MONEY);
((INetSerializableStruct)walletUpdate).Write(msg);
GameMain.Server?.ServerPeer?.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
}
public override void End(TransitionType transitionType = TransitionType.None)
@@ -516,6 +564,21 @@ namespace Barotrauma
msg.Write((byte)selectedMissionIndex);
}
var subList = GameMain.NetLobbyScreen.GetSubList();
List<int> ownedSubmarineIndices = new List<int>();
for (int i = 0; i < subList.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines.Any(s => s.Name == subList[i].Name))
{
ownedSubmarineIndices.Add(i);
}
}
msg.Write((ushort)ownedSubmarineIndices.Count);
foreach (int index in ownedSubmarineIndices)
{
msg.Write((ushort)index);
}
msg.Write(map.AllowDebugTeleport);
msg.Write(reputation != null);
if (reputation != null) { msg.Write(reputation.Value); }
@@ -530,7 +593,6 @@ namespace Barotrauma
msg.Write(ForceMapUI);
msg.Write(Money);
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
@@ -554,8 +616,17 @@ namespace Barotrauma
}
// Store balance
msg.Write(true);
msg.Write((UInt16)map.CurrentLocation.StoreCurrentBalance);
bool hasStores = map.CurrentLocation.Stores != null && map.CurrentLocation.Stores.Any();
msg.Write(hasStores);
if (hasStores)
{
msg.Write((byte)map.CurrentLocation.Stores.Count);
foreach (var store in map.CurrentLocation.Stores.Values)
{
msg.Write(store.Identifier);
msg.Write((UInt16)store.Balance);
}
}
}
else
{
@@ -564,36 +635,10 @@ namespace Barotrauma
msg.Write(false);
}
msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count);
foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, CargoManager.MaxQuantity);
}
msg.Write((UInt16)CargoManager.ItemsInSellFromSubCrate.Count);
foreach (PurchasedItem pi in CargoManager.ItemsInSellFromSubCrate)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, CargoManager.MaxQuantity);
}
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, CargoManager.MaxQuantity);
}
msg.Write((UInt16)CargoManager.SoldItems.Count);
foreach (SoldItem si in CargoManager.SoldItems)
{
msg.Write(si.ItemPrefab.Identifier);
msg.Write((UInt16)si.ID);
msg.Write(si.Removed);
msg.Write(si.SellerID);
msg.Write((byte)si.Origin);
}
WriteItems(msg, CargoManager.ItemsInBuyCrate);
WriteItems(msg, CargoManager.ItemsInSellFromSubCrate);
WriteItems(msg, CargoManager.PurchasedItems);
WriteItems(msg, CargoManager.SoldItems);
msg.Write((ushort)UpgradeManager.PendingUpgrades.Count);
foreach (var (prefab, category, level) in UpgradeManager.PendingUpgrades)
@@ -607,7 +652,7 @@ namespace Barotrauma
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{
msg.Write(itemSwap.ItemToRemove.ID);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? string.Empty);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty);
}
var characterData = GetClientCharacterData(c);
@@ -638,53 +683,19 @@ namespace Barotrauma
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
UInt16 buyCrateItemCount = msg.ReadUInt16();
List<PurchasedItem> buyCrateItems = new List<PurchasedItem>();
for (int i = 0; i < buyCrateItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 subSellCrateItemCount = msg.ReadUInt16();
List<PurchasedItem> subSellCrateItems = new List<PurchasedItem>();
for (int i = 0; i < subSellCrateItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
subSellCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 purchasedItemCount = msg.ReadUInt16();
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
for (int i = 0; i < purchasedItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 soldItemCount = msg.ReadUInt16();
List<SoldItem> soldItems = new List<SoldItem>();
for (int i = 0; i < soldItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
UInt16 id = msg.ReadUInt16();
bool removed = msg.ReadBoolean();
byte sellerId = msg.ReadByte();
byte origin = msg.ReadByte();
soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId, (SoldItem.SellOrigin)origin));
}
var buyCrateItems = ReadPurchasedItems(msg, sender);
var subSellCrateItems = ReadPurchasedItems(msg, sender);
var purchasedItems = ReadPurchasedItems(msg, sender);
var soldItems = ReadSoldItems(msg);
ushort purchasedUpgradeCount = msg.ReadUInt16();
List<PurchasedUpgrade> purchasedUpgrades = new List<PurchasedUpgrade>();
for (int i = 0; i < purchasedUpgradeCount; i++)
{
string upgradeIdentifier = msg.ReadString();
Identifier upgradeIdentifier = msg.ReadIdentifier();
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
string categoryIdentifier = msg.ReadString();
Identifier categoryIdentifier = msg.ReadIdentifier();
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
int upgradeLevel = msg.ReadByte();
@@ -698,148 +709,264 @@ namespace Barotrauma
for (int i = 0; i < purchasedItemSwapCount; i++)
{
UInt16 itemToRemoveID = msg.ReadUInt16();
string itemToInstallIdentifier = msg.ReadString();
ItemPrefab itemToInstall = string.IsNullOrEmpty(itemToInstallIdentifier) ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
Identifier itemToInstallIdentifier = msg.ReadIdentifier();
ItemPrefab itemToInstall = itemToInstallIdentifier.IsEmpty ? null : ItemPrefab.Find(string.Empty, itemToInstallIdentifier);
if (!(Entity.FindEntityByID(itemToRemoveID) is Item itemToRemove)) { continue; }
purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall));
}
bool allowedToManageCampaign = AllowedToManageCampaign(sender);
if (AllowedToManageCampaign(sender))
Location location = Map.CurrentLocation;
int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost;
int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost;
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost;
Wallet personalWallet = GetWallet(sender);
if (purchasedHullRepairs != PurchasedHullRepairs)
{
Location location = Map.CurrentLocation;
int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost;
int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost;
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost;
if (purchasedHullRepairs != this.PurchasedHullRepairs)
switch (purchasedHullRepairs)
{
if (purchasedHullRepairs && Money >= hullRepairCost)
{
this.PurchasedHullRepairs = true;
Money -= hullRepairCost;
case true when personalWallet.CanAfford(hullRepairCost):
personalWallet.Deduct(hullRepairCost);
PurchasedHullRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
}
else if (!purchasedHullRepairs)
{
this.PurchasedHullRepairs = false;
Money += hullRepairCost;
}
break;
case false:
PurchasedHullRepairs = false;
personalWallet.Refund(hullRepairCost);
break;
}
if (purchasedItemRepairs != this.PurchasedItemRepairs)
}
if (purchasedItemRepairs != PurchasedItemRepairs)
{
switch (purchasedItemRepairs)
{
if (purchasedItemRepairs && Money >= itemRepairCost)
{
this.PurchasedItemRepairs = true;
Money -= itemRepairCost;
case true when personalWallet.CanAfford(itemRepairCost):
personalWallet.Deduct(itemRepairCost);
PurchasedItemRepairs = true;
GameAnalyticsManager.AddMoneySpentEvent(itemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
}
else if (!purchasedItemRepairs)
{
this.PurchasedItemRepairs = false;
Money += itemRepairCost;
}
break;
case false:
PurchasedItemRepairs = false;
personalWallet.Refund(itemRepairCost);
break;
}
if (purchasedLostShuttles != this.PurchasedLostShuttles)
}
if (purchasedLostShuttles != PurchasedLostShuttles)
{
if (GameMain.GameSession?.SubmarineInfo != null && GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied)
{
if (GameMain.GameSession?.SubmarineInfo != null &&
GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied)
{
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox);
}
else if (purchasedLostShuttles && Money >= shuttleRetrieveCost)
{
this.PurchasedLostShuttles = true;
Money -= shuttleRetrieveCost;
GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
}
else if (!purchasedItemRepairs)
{
this.PurchasedLostShuttles = false;
Money += shuttleRetrieveCost;
}
GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("ReplaceShuttleDockingPortOccupied"), sender, ChatMessageType.MessageBox);
}
if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport)
else if (purchasedLostShuttles && personalWallet.TryDeduct(shuttleRetrieveCost))
{
Map.SetLocation(currentLocIndex);
PurchasedLostShuttles = true;
GameAnalyticsManager.AddMoneySpentEvent(shuttleRetrieveCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
}
else if (!purchasedItemRepairs)
{
PurchasedLostShuttles = false;
personalWallet.Refund(shuttleRetrieveCost);
}
}
if (currentLocIndex < Map.Locations.Count && Map.AllowDebugTeleport)
{
Map.SetLocation(currentLocIndex);
}
if (AllowedToManageCampaign(sender, ClientPermissions.ManageMap))
{
Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
if (Map.SelectedLocation == null) { Map.SelectRandomLocation(preferUndiscovered: true); }
if (Map.SelectedConnection != null) { Map.SelectMission(selectedMissionIndices); }
CheckTooManyMissions(Map.CurrentLocation, sender);
}
bool allowedToUseStore = AllowedToManageCampaign(sender, ClientPermissions.CampaignStore);
if (allowedToManageCampaign || allowedToUseStore || AllowedToManageCampaign(sender, ClientPermissions.BuyItems))
var prevBuyCrateItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.ItemsInBuyCrate)
{
var currentBuyCrateItems = new List<PurchasedItem>(CargoManager.ItemsInBuyCrate);
currentBuyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, -i.Quantity));
buyCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInBuyCrate(i.ItemPrefab, i.Quantity));
CargoManager.SellBackPurchasedItems(new List<PurchasedItem>(CargoManager.PurchasedItems));
CargoManager.PurchaseItems(purchasedItems, false);
prevBuyCrateItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var store in prevBuyCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
foreach (var store in buyCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems);
if (allowedToManageCampaign || allowedToUseStore || allowedToSellSubItems)
var prevPurchasedItems = new Dictionary<Identifier, List<PurchasedItem>>();
foreach (var kvp in CargoManager.PurchasedItems)
{
var currentSubSellCrateItems = new List<PurchasedItem>(CargoManager.ItemsInSellFromSubCrate);
currentSubSellCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInSubSellCrate(i.ItemPrefab, -i.Quantity));
subSellCrateItems.ForEach(i => CargoManager.ModifyItemQuantityInSubSellCrate(i.ItemPrefab, i.Quantity));
prevPurchasedItems.Add(kvp.Key, new List<PurchasedItem>(kvp.Value));
}
foreach (var store in prevPurchasedItems)
{
CargoManager.SellBackPurchasedItems(store.Key, store.Value, sender);
}
foreach (var store in purchasedItems)
{
CargoManager.PurchaseItems(store.Key, store.Value, false, sender);
}
bool allowedToSellSubItems = AllowedToManageCampaign(sender, ClientPermissions.SellSubItems);
if (allowedToSellSubItems)
{
var prevSubSellCrateItems = new Dictionary<Identifier, List<PurchasedItem>>(CargoManager.ItemsInSellFromSubCrate);
foreach (var store in prevSubSellCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, -item.Quantity, sender);
}
}
foreach (var store in subSellCrateItems)
{
foreach (var item in store.Value)
{
CargoManager.ModifyItemQuantityInSubSellCrate(store.Key, item.ItemPrefab, item.Quantity, sender);
}
}
}
bool allowedToSellInventoryItems = AllowedToManageCampaign(sender, ClientPermissions.SellInventoryItems);
if (allowedToManageCampaign || allowedToUseStore || (allowedToSellInventoryItems && allowedToSellSubItems))
if (allowedToSellInventoryItems && allowedToSellSubItems)
{
// for some reason CargoManager.SoldItem is never cleared by the server, I've added a check to SellItems that ignores all
// sold items that are removed so they should be discarded on the next message
CargoManager.BuyBackSoldItems(new List<SoldItem>(CargoManager.SoldItems));
CargoManager.SellItems(soldItems);
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
{
CargoManager.BuyBackSoldItems(store.Key, store.Value);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value);
}
}
else if (allowedToSellInventoryItems || allowedToSellSubItems)
{
if (allowedToSellInventoryItems)
var prevSoldItems = new Dictionary<Identifier, List<SoldItem>>(CargoManager.SoldItems);
foreach (var store in prevSoldItems)
{
CargoManager.BuyBackSoldItems(new List<SoldItem>(CargoManager.SoldItems.Where(i => i.Origin == SoldItem.SellOrigin.Character)));
soldItems.RemoveAll(i => i.Origin != SoldItem.SellOrigin.Character);
store.Value.RemoveAll(predicate);
CargoManager.BuyBackSoldItems(store.Key, store.Value);
}
foreach (var store in soldItems)
{
store.Value.RemoveAll(predicate);
}
foreach (var store in soldItems)
{
CargoManager.SellItems(store.Key, store.Value);
}
bool predicate(SoldItem i) => allowedToSellInventoryItems != (i.Origin == SoldItem.SellOrigin.Character);
}
foreach (var (prefab, category, _) in purchasedUpgrades)
{
UpgradeManager.PurchaseUpgrade(prefab, category, client: sender);
// unstable logging
int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
}
else
{
CargoManager.BuyBackSoldItems(new List<SoldItem>(CargoManager.SoldItems.Where(i => i.Origin == SoldItem.SellOrigin.Submarine)));
soldItems.RemoveAll(i => i.Origin != SoldItem.SellOrigin.Submarine);
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, client: sender);
}
CargoManager.SellItems(soldItems);
}
if (allowedToManageCampaign)
foreach (Item item in Item.ItemList)
{
foreach (var (prefab, category, _) in purchasedUpgrades)
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.PurchaseUpgrade(prefab, category);
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
}
}
// unstable logging
int price = prefab.Price.GetBuyprice(UpgradeManager.GetUpgradeLevel(prefab, category), Map?.CurrentLocation);
int level = UpgradeManager.GetUpgradeLevel(prefab, category);
GameServer.Log($"SERVER: Purchased level {level} {category.Identifier}.{prefab.Identifier} for {price}", ServerLog.MessageType.ServerMessage);
}
foreach (var purchasedItemSwap in purchasedItemSwaps)
public void ServerReadMoney(IReadMessage msg, Client sender)
{
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
switch (transfer.Sender)
{
case Some<ushort> { Value: var id }:
if (id != sender.CharacterID && !AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; }
Wallet wallet = GetWalletByID(id);
if (wallet is InvalidWallet) { return; }
TransferMoney(wallet);
break;
case None<ushort> _:
if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney))
{
if (transfer.Receiver is Some<ushort> { Value: var receiverId } && receiverId == sender.CharacterID)
{
GameMain.Server?.Voting.StartTransferVote(sender, null, transfer.Amount, sender);
GameServer.Log($"{sender.Name} started a vote to transfer {transfer.Amount} mk from the bank.", ServerLog.MessageType.Money);
}
return;
}
TransferMoney(Bank);
break;
}
void TransferMoney(Wallet from)
{
if (!from.TryDeduct(transfer.Amount)) { return; }
switch (transfer.Receiver)
{
if (purchasedItemSwap.ItemToInstall == null)
{
UpgradeManager.CancelItemSwap(purchasedItemSwap.ItemToRemove);
}
else
{
UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall);
}
}
foreach (Item item in Item.ItemList)
{
if (item.PendingItemSwap != null && !purchasedItemSwaps.Any(it => it.ItemToRemove == item))
{
UpgradeManager.CancelItemSwap(item);
item.PendingItemSwap = null;
}
case Some<ushort> { Value: var id }:
Wallet wallet = GetWalletByID(id);
if (wallet is InvalidWallet) { return; }
wallet.Give(transfer.Amount);
GameServer.Log($"{sender.Name} transferred {transfer.Amount} mk to {wallet.GetOwnerLogName()} from {from.GetOwnerLogName()}.", ServerLog.MessageType.Money);
break;
case None<ushort> _:
Bank.Give(transfer.Amount);
GameServer.Log($"{sender.Name} transferred {transfer.Amount} mk to {Bank.GetOwnerLogName()} from {from.GetOwnerLogName()}.", ServerLog.MessageType.Money);
break;
}
}
Wallet GetWalletByID(ushort id)
{
Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == id);
return targetCharacter is null ? Wallet.Invalid : targetCharacter.Wallet;
}
}
public void ServerReadRewardDistribution(IReadMessage msg, Client sender)
{
NetWalletSetSalaryUpdate update = INetSerializableStruct.Read<NetWalletSetSalaryUpdate>(msg);
if (!AllowedToManageCampaign(sender, ClientPermissions.ManageMoney)) { return; }
Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == update.Target);
targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution);
GameServer.Log($"{sender.Name} changed the salary of {targetCharacter?.Name ?? "the bank"} to {update.NewRewardDistribution}%.", ServerLog.MessageType.Money);
}
public void ServerReadCrew(IReadMessage msg, Client sender)
@@ -878,7 +1005,7 @@ namespace Barotrauma
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
CharacterInfo firedCharacter = null;
if (location != null && AllowedToManageCampaign(sender))
if (location != null && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
{
if (fireCharacter)
{
@@ -928,7 +1055,7 @@ namespace Barotrauma
{
foreach (CharacterInfo hireInfo in location.HireManager.PendingHires)
{
if (TryHireCharacter(location, hireInfo))
if (TryHireCharacter(location, hireInfo, sender))
{
hiredCharacters.Add(hireInfo);
};
@@ -1045,7 +1172,6 @@ namespace Barotrauma
{
element.Add(new XAttribute("campaignid", CampaignID));
XElement modeElement = new XElement("MultiPlayerCampaign",
new XAttribute("money", Money),
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
@@ -1053,6 +1179,7 @@ namespace Barotrauma
modeElement.Add(Settings.Save());
modeElement.Add(SaveStats());
modeElement.Add(Bank.Save());
CampaignMetadata?.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);
@@ -18,7 +18,7 @@ namespace Barotrauma
private struct RateLimitInfo
{
public int Requests;
public const int MaxRequests = 5;
public const int MaxRequests = 10;
public DateTimeOffset Expiry;
}
@@ -85,7 +85,7 @@ namespace Barotrauma
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
HealRequestResult result = HealAllPending();
HealRequestResult result = HealAllPending(client: client);
ServerSend(new NetHealRequest { Result = result }, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
@@ -5,7 +5,7 @@ namespace Barotrauma.Items.Components
{
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(docked);
@@ -6,6 +6,16 @@ namespace Barotrauma.Items.Components
{
partial class Door
{
private readonly struct EventData : IEventData
{
public readonly bool ForcedOpen;
public EventData(bool forcedOpen)
{
ForcedOpen = forcedOpen;
}
}
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
{
if (IsStuck || isOpen == open)
@@ -19,17 +29,18 @@ namespace Barotrauma.Items.Components
if (sendNetworkMessage)
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), forcedOpen });
item.CreateServerEvent(this, new EventData(forcedOpen));
}
}
public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
base.ServerWrite(msg, c, extraData);
bool forcedOpen = TryExtractEventData<EventData>(extraData, out var eventData) && eventData.ForcedOpen;
base.ServerEventWrite(msg, c, extraData);
msg.Write(isOpen);
msg.Write(isBroken);
msg.Write(extraData.Length == 3 ? (bool)extraData[2] : false); //forced open
msg.Write(forcedOpen); //forced open
msg.Write(isStuck);
msg.Write(isJammed);
msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8);
@@ -4,16 +4,16 @@ namespace Barotrauma.Items.Components
{
partial class GeneticMaterial : ItemComponent
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(tainted);
if (tainted)
{
msg.Write(selectedTaintedEffect?.UIntIdentifier ?? 0);
msg.Write(selectedTaintedEffect?.UintIdentifier ?? 0);
}
else
{
msg.Write(selectedEffect?.UIntIdentifier ?? 0);
msg.Write(selectedEffect?.UintIdentifier ?? 0);
}
}
}
@@ -6,12 +6,22 @@ namespace Barotrauma.Items.Components
{
internal partial class Growable
{
private readonly struct EventData : IEventData
{
public readonly int Offset;
public EventData(int offset)
{
Offset = offset;
}
}
private const int serverHealthUpdateDelay = 10;
private int serverHealthUpdateTimer;
partial void LoadVines(XElement element)
partial void LoadVines(ContentXElement element)
{
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -25,11 +35,12 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedSingle(Health, 0f, (float) MaxHealth, 8);
if (extraData != null && extraData.Length >= 3 && extraData[2] is int offset)
if (TryExtractEventData(extraData, out EventData eventData))
{
int offset = eventData.Offset;
int amountToSend = Math.Min(Vines.Count - offset, VineChunkSize);
msg.WriteRangedInteger(offset, -1, MaximumVines);
msg.WriteRangedInteger(amountToSend, 0, VineChunkSize);
@@ -5,9 +5,9 @@ namespace Barotrauma.Items.Components
{
partial class Holdable : Pickable, IServerSerializable, IClientSerializable
{
public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
base.ServerWrite(msg, c, extraData);
base.ServerEventWrite(msg, c, extraData);
bool writeAttachData = attachable && body != null;
msg.Write(writeAttachData);
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
msg.Write(item.Submarine?.ID ?? Entity.NullEntityID);
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
@@ -6,7 +6,7 @@ namespace Barotrauma.Items.Components
{
private float lastSentDeattachTimer;
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(deattachTimer);
}
@@ -1,10 +1,12 @@
using System.Xml.Linq;
using System;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class ItemComponent : ISerializableEntity
{
private bool LoadElemProjSpecific(XElement subElement)
private bool LoadElemProjSpecific(ContentXElement subElement)
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -18,7 +20,7 @@ namespace Barotrauma.Items.Components
return true; //element processed
}
public virtual void ServerAppendExtraData(ref object[] extraData) { }
public virtual IEventData ServerGetEventData() => null;
}
}
@@ -11,28 +11,28 @@ namespace Barotrauma.Items.Components
private string lastSentText;
private float sendStateTimer;
[Serialize("", true, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(100)]
[Serialize("", IsPropertySaveable.Yes, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(100)]
public string Text
{
get;
set;
}
[Editable, Serialize("0,0,0,255", true, description: "The color of the text displayed on the label.", alwaysUseInstanceValues: true)]
[Editable, Serialize("0,0,0,255", IsPropertySaveable.Yes, description: "The color of the text displayed on the label.", alwaysUseInstanceValues: true)]
public Color TextColor
{
get;
set;
}
[Editable, Serialize(1.0f, true, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)]
[Editable, Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)]
public float TextScale
{
get;
set;
}
[Serialize("0,0,0,0", true, description: "The amount of padding around the text in pixels (left,top,right,bottom).")]
[Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "The amount of padding around the text in pixels (left,top,right,bottom).")]
public Vector4 Padding
{
get;
@@ -44,7 +44,7 @@ namespace Barotrauma.Items.Components
//do nothing
}
public ItemLabel(Item item, XElement element)
public ItemLabel(Item item, ContentXElement element)
: base(item, element)
{
}
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(Text);
lastSentText = Text;
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(IsActive);
lastSentState = IsActive;
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class Controller : ItemComponent, IServerSerializable
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(State);
msg.Write(user == null ? (ushort)0 : user.ID);
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
bool active = msg.ReadBoolean();
@@ -16,7 +16,7 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(user?.ID ?? 0);
msg.Write(IsActive);
@@ -5,14 +5,14 @@ namespace Barotrauma.Items.Components
{
partial class Engine : Powered, IServerSerializable, IClientSerializable
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger((int)(targetForce / 10.0f), -10, 10);
msg.Write(User == null ? Entity.NullEntityID : User.ID);
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
@@ -1,57 +1,73 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
uint recipeHash = msg.ReadUInt32();
item.CreateServerEvent(this);
if (!item.CanClientAccess(c)) return;
if (!item.CanClientAccess(c)) { return; }
if (itemIndex == -1)
if (recipeHash == 0)
{
CancelFabricating(c.Character);
}
else
{
//if already fabricating the selected item, return
if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) return;
if (itemIndex < 0 || itemIndex >= fabricationRecipes.Count) return;
if (fabricatedItem != null && fabricatedItem.RecipeHash == recipeHash) { return; }
if (recipeHash == 0) { return; }
StartFabricating(fabricationRecipes[itemIndex], c.Character);
StartFabricating(fabricationRecipes[recipeHash], c.Character);
}
}
private ulong serverEventId = 0;
public override void ServerAppendExtraData(ref object[] extraData)
{
//ensuring the uniqueness of this event is
//required for the fabricator to sync correctly;
//otherwise, the event manager would incorrectly
//assume that the client actually has the latest state
Array.Resize(ref extraData, 4);
extraData[2] = serverEventId;
extraData[3] = State;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
private readonly struct EventData : IEventData
{
FabricatorState stateAtEvent = (FabricatorState)extraData[3];
msg.Write((byte)stateAtEvent);
public readonly ulong ServerEventId;
public readonly FabricatorState State;
public EventData(ulong serverEventId, FabricatorState state)
{
//ensuring the uniqueness of this event is
//required for the fabricator to sync correctly;
//otherwise, the event manager would incorrectly
//assume that the client actually has the latest state
ServerEventId = serverEventId;
State = state;
}
}
public override IEventData ServerGetEventData()
=> new EventData(serverEventId, State);
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<EventData>(data, out _);
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
var componentData = ExtractEventData<EventData>(extraData);
msg.Write((byte)componentData.State);
msg.Write(timeUntilReady);
int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem);
msg.WriteRangedInteger(itemIndex, -1, fabricationRecipes.Count - 1);
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
uint recipeHash = fabricatedItem?.RecipeHash ?? 0;
msg.Write(recipeHash);
UInt16 userID = fabricatedItem is null || user is null ? (UInt16)0 : user.ID;
msg.Write(userID);
var reachedLimits = fabricationLimits.Where(kvp => kvp.Value <= 0);
msg.Write((ushort)reachedLimits.Count());
foreach (var kvp in reachedLimits)
{
msg.Write(kvp.Key);
}
}
}
}
@@ -4,12 +4,12 @@ namespace Barotrauma.Items.Components
{
partial class OutpostTerminal : ItemComponent, IClientSerializable, IServerSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
}
@@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components
{
partial class Pump : Powered, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
bool newIsActive = msg.ReadBoolean();
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
@@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components
private float? nextServerLogWriteTime;
private float lastServerLogWriteTime;
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
bool autoTemp = msg.ReadBoolean();
bool powerOn = msg.ReadBoolean();
@@ -43,7 +43,7 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(autoTemp);
msg.Write(_powerOn);
@@ -5,6 +5,16 @@ namespace Barotrauma.Items.Components
{
partial class Steering : Powered, IServerSerializable, IClientSerializable
{
private readonly struct EventData : IEventData
{
public readonly bool DockingButtonClicked;
public EventData(bool dockingButtonClicked)
{
DockingButtonClicked = dockingButtonClicked;
}
}
// TODO: an enumeration would be much cleaner
public bool MaintainPos;
public bool LevelStartSelected;
@@ -23,7 +33,7 @@ namespace Barotrauma.Items.Components
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Barotrauma.Networking.Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
bool autoPilot = msg.ReadBoolean();
bool dockingButtonClicked = msg.ReadBoolean();
@@ -58,7 +68,7 @@ namespace Barotrauma.Items.Components
if (dockingButtonClicked)
{
item.SendSignal(new Signal("1", sender: c.Character), "toggle_docking");
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), true });
item.CreateServerEvent(this, new EventData(dockingButtonClicked: true));
}
if (!AutoPilot)
@@ -88,10 +98,10 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
public void ServerWrite(IWriteMessage msg, Barotrauma.Networking.Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Barotrauma.Networking.Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(autoPilot);
msg.Write(extraData.Length > 2 && extraData[2] is bool && (bool)extraData[2]);
msg.Write(TryExtractEventData<EventData>(extraData, out var eventData) && eventData.DockingButtonClicked);
if (!autoPilot)
{
@@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components
{
partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
float newRechargeSpeed = msg.ReadRangedInteger(0, 10) / 10.0f * maxRechargeSpeed;
@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
@@ -5,11 +5,26 @@ namespace Barotrauma.Items.Components
{
partial class Projectile : ItemComponent
{
private readonly struct EventData : IEventData
{
public readonly bool Launch;
public EventData(bool launch)
{
Launch = launch;
}
}
private float launchRot;
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<EventData>(data, out _);
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
bool launch = extraData.Length > 2 && (bool)extraData[2];
var eventData = ExtractEventData<EventData>(extraData);
bool launch = eventData.Launch;
msg.Write(launch);
if (launch)
{
@@ -27,8 +42,8 @@ namespace Barotrauma.Items.Components
msg.Write(item.CurrentHull?.ID ?? Entity.NullEntityID);
msg.Write(item.SimPosition.X);
msg.Write(item.SimPosition.Y);
msg.Write(stickJoint.Axis.X);
msg.Write(stickJoint.Axis.Y);
msg.Write(jointAxis.X);
msg.Write(jointAxis.Y);
if (StickTarget.UserData is Structure structure)
{
msg.Write(structure.ID);
@@ -4,13 +4,16 @@ namespace Barotrauma.Items.Components
{
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{
void InitProjSpecific()
private Character prevLoggedFixer;
private FixActions prevLoggedFixAction;
public override void OnMapLoaded()
{
//let the clients know the initial deterioration delay
item.CreateServerEvent(this);
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
if (c.Character == null) { return; }
var requestedFixAction = (FixActions)msg.ReadRangedInteger(0, 2);
@@ -19,7 +22,7 @@ namespace Barotrauma.Items.Components
{
if (!c.Character.IsTraitor && requestedFixAction == FixActions.Sabotage)
{
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log($"Non traitor \"{c.Character.Name}\" attempted to sabotage item.");
}
@@ -39,7 +42,7 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(deteriorationTimer);
msg.Write(deteriorateAlwaysResetTimer);
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class Rope : ItemComponent
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(Snapped);
@@ -6,7 +6,7 @@ namespace Barotrauma.Items.Components
{
private float LastSentScanTimer { get; set; }
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(scanTimer);
}
@@ -4,16 +4,16 @@ namespace Barotrauma.Items.Components
{
partial class ButtonTerminal : ItemComponent, IClientSerializable, IServerSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
int signalIndex = msg.ReadRangedInteger(0, Signals.Length - 1);
if (!item.CanClientAccess(c)) { return; }
if (!SendSignal(signalIndex, c.Character)) { return; }
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} sent a signal \"{Signals[signalIndex]}\" from {item.Name}", ServerLog.MessageType.ItemInteraction);
item.CreateServerEvent(this, new object[] { signalIndex });
item.CreateServerEvent(this, new EventData(signalIndex));
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
Write(msg, extraData);
}
@@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components
{
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
List<Wire>[] wires = new List<Wire>[Connections.Count];
@@ -84,7 +84,7 @@ namespace Barotrauma.Items.Components
if (!selectedWire.Item.Removed) { selectedWire.CreateNetworkEvent(); }
}, 1.0f);
}
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, c.Character.ID });
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, c.Character));
return;
}
@@ -210,10 +210,10 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(user == null ? (ushort)0 : user.ID);
ClientWrite(msg, extraData);
ClientEventWrite(msg, extraData);
}
}
}
@@ -5,7 +5,7 @@ namespace Barotrauma.Items.Components
{
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
bool[] elementStates = new bool[customInterfaceElementList.Count];
string[] elementValues = new string[customInterfaceElementList.Count];
@@ -51,17 +51,12 @@ namespace Barotrauma.Items.Components
}
//notify all clients of the new state
GameMain.Server.CreateEntityEvent(item, new object[]
{
NetEntityEvent.Type.ComponentState,
item.GetComponentIndex(this),
clickedButton
});
item.CreateServerEvent(this, new EventData(clickedButton));
item.CreateServerEvent(this);
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
//extradata contains an array of buttons clicked by a client (or nothing if nothing was clicked)
for (int i = 0; i < customInterfaceElementList.Count; i++)
@@ -76,7 +71,7 @@ namespace Barotrauma.Items.Components
}
else
{
msg.Write(extraData != null && extraData.Any(d => d as CustomInterfaceElement == customInterfaceElementList[i]));
msg.Write(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
}
}
}
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(Value);
lastSentValue = Value;
@@ -7,7 +7,19 @@ namespace Barotrauma.Items.Components
{
partial class Terminal : ItemComponent, IClientSerializable, IServerSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
private readonly struct ServerEventData : IEventData
{
public readonly int MsgIndex;
public readonly string MsgToSend;
public ServerEventData(int msgIndex, string msgToSend)
{
MsgIndex = msgIndex;
MsgToSend = msgToSend;
}
}
public void ServerEventRead(IReadMessage msg, Client c)
{
string newOutputValue = msg.ReadString();
@@ -47,7 +59,7 @@ namespace Barotrauma.Items.Components
string msgToSend = str;
if (string.IsNullOrEmpty(msgToSend))
{
item.CreateServerEvent(this, new object[] { msgIndex, msgToSend });
item.CreateServerEvent(this, new ServerEventData(msgIndex, msgToSend));
msgIndex++;
continue;
}
@@ -73,23 +85,23 @@ namespace Barotrauma.Items.Components
if (!splitMessage.Any()) { break; }
tempMsg += " ";
} while (tempMsg.Length + splitMessage[0].Length < MaxMessageLength);
item.CreateServerEvent(this, new object[] { msgIndex, tempMsg });
item.CreateServerEvent(this, new ServerEventData(msgIndex, tempMsg));
msgToSend = msgToSend.Remove(0, tempMsg.Length);
}
}
if (!string.IsNullOrEmpty(msgToSend))
{
item.CreateServerEvent(this, new object[] { msgIndex, msgToSend });
item.CreateServerEvent(this, new ServerEventData(msgIndex, msgToSend));
}
msgIndex++;
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
if (extraData.Length > 3 && extraData[3] is string str)
if (TryExtractEventData(extraData, out ServerEventData eventData))
{
msg.Write(str);
msg.Write(eventData.MsgToSend);
}
else
{
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class WifiComponent
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedInteger(Channel, MinChannel, MaxChannel);
}
@@ -6,6 +6,16 @@ namespace Barotrauma.Items.Components
{
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable
{
private readonly struct ServerEventData : IEventData
{
public readonly int EventIndex;
public ServerEventData(int eventIndex)
{
EventIndex = eventIndex;
}
}
public void CreateNetworkEvent()
{
if (GameMain.Server == null) return;
@@ -13,13 +23,17 @@ namespace Barotrauma.Items.Components
int eventCount = Math.Max((int)Math.Ceiling(nodes.Count / (float)MaxNodesPerNetworkEvent), 1);
for (int i = 0; i < eventCount; i++)
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), i });
item.CreateServerEvent(this, new ServerEventData(i));
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<ServerEventData>(data, out _);
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
int eventIndex = (int)extraData[2];
var eventData = ExtractEventData<ServerEventData>(extraData);
int eventIndex = eventData.EventIndex;
int nodeStartIndex = eventIndex * MaxNodesPerNetworkEvent;
int nodeCount = MathHelper.Clamp(nodes.Count - nodeStartIndex, 0, MaxNodesPerNetworkEvent);
@@ -32,7 +46,7 @@ namespace Barotrauma.Items.Components
}
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
int nodeCount = msg.ReadByte();
Vector2 lastNodePos = Vector2.Zero;
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class TriggerComponent : ItemComponent, IServerSerializable
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedSingle(CurrentForceFluctuation, 0.0f, 1.0f, 8);
}
@@ -8,21 +8,11 @@ namespace Barotrauma
{
partial class Inventory : IServerSerializable, IClientSerializable
{
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
List<Item> prevItems = new List<Item>(AllItems.Distinct());
byte slotCount = msg.ReadByte();
List<ushort>[] newItemIDs = new List<ushort>[slotCount];
for (int i = 0; i < slotCount; i++)
{
newItemIDs[i] = new List<ushort>();
int itemCount = msg.ReadRangedInteger(0, MaxStackSize);
for (int j = 0; j < itemCount; j++)
{
newItemIDs[i].Add(msg.ReadUInt16());
}
}
SharedRead(msg, out var newItemIDs);
if (c == null || c.Character == null) { return; }
@@ -33,7 +23,7 @@ namespace Barotrauma
{
accessible = false;
}
else if (!characterInventory.AccessibleWhenAlive && !ownerCharacter.IsDead)
else if (!characterInventory.AccessibleWhenAlive && !ownerCharacter.IsDead && !characterInventory.AccessibleByOwner)
{
accessible = false;
}
@@ -175,7 +165,7 @@ namespace Barotrauma
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
SharedWrite(msg, extraData);
}
@@ -2,6 +2,7 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -14,108 +15,77 @@ namespace Barotrauma
public override Sprite Sprite
{
get { return prefab?.sprite; }
get { return base.Prefab?.Sprite; }
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
GameMain.NetworkMember.CreateEntityEvent(this, new AssignCampaignInteractionEventData());
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
string errorMsg = "";
if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type))
Exception error(string reason)
{
if (extraData == null)
{
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was null.";
}
else if (extraData.Length == 0)
{
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event data was empty.";
}
else
{
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - event type not set.";
}
msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:InvalidData" + Name, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
string errorMsg = $"Failed to write a network event for the item \"{Name}\" - {reason}";
GameAnalyticsManager.AddErrorEventOnce($"Item.ServerWrite:{Name}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return new Exception(errorMsg);
}
if (extraData is null) { throw error("event data was null"); }
if (!(extraData is IEventData itemEventData)) { throw error($"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
int initialWritePos = msg.LengthBits;
NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0];
msg.WriteRangedInteger((int)eventType, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
switch (eventType)
msg.WriteRangedInteger((int)itemEventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (itemEventData)
{
case NetEntityEvent.Type.ComponentState:
if (extraData.Length < 2 || !(extraData[1] is int))
case ComponentStateEventData componentStateEventData:
int componentIndex = components.IndexOf(componentStateEventData.Component);
if (componentIndex < 0)
{
errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index not given.";
break;
throw error($"component index out of range ({componentIndex})");
}
int componentIndex = (int)extraData[1];
if (componentIndex < 0 || componentIndex >= components.Count)
if (!(components[componentIndex] is IServerSerializable serializableComponent))
{
errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component index out of range (" + componentIndex + ").";
break;
}
else if (!(components[componentIndex] is IServerSerializable))
{
errorMsg = "Failed to write a component state event for the item \"" + Name + "\" - component \"" + components[componentIndex] + "\" is not server serializable.";
break;
throw error($"component \"{components[componentIndex]}\" is not server serializable");
}
msg.WriteRangedInteger(componentIndex, 0, components.Count - 1);
(components[componentIndex] as IServerSerializable).ServerWrite(msg, c, extraData);
serializableComponent.ServerEventWrite(msg, c, extraData);
break;
case NetEntityEvent.Type.InventoryState:
if (extraData.Length < 2 || !(extraData[1] is int))
case InventoryStateEventData inventoryStateEventData:
int containerIndex = components.IndexOf(inventoryStateEventData.Component);
if (containerIndex < 0)
{
errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component index not given.";
break;
throw error($"container index out of range ({containerIndex})");
}
int containerIndex = (int)extraData[1];
if (containerIndex < 0 || containerIndex >= components.Count)
if (!(components[containerIndex] is ItemContainer itemContainer))
{
errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - container index out of range (" + containerIndex + ").";
break;
}
else if (!(components[containerIndex] is ItemContainer))
{
errorMsg = "Failed to write an inventory state event for the item \"" + Name + "\" - component \"" + components[containerIndex] + "\" is not server serializable.";
break;
throw error("component \"" + components[containerIndex] + "\" is not server serializable");
}
msg.WriteRangedInteger(containerIndex, 0, components.Count - 1);
msg.Write(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
(components[containerIndex] as ItemContainer).Inventory.ServerWrite(msg, c);
itemContainer.Inventory.ServerEventWrite(msg, c);
break;
case NetEntityEvent.Type.Status:
case ItemStatusEventData _:
msg.Write(condition);
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
case AssignCampaignInteractionEventData _:
msg.Write((byte)CampaignInteractionType);
break;
case NetEntityEvent.Type.ApplyStatusEffect:
case ApplyStatusEffectEventData applyStatusEffectEventData:
{
ActionType actionType = (ActionType)extraData[1];
ItemComponent targetComponent = extraData.Length > 2 ? (ItemComponent)extraData[2] : null;
ushort characterID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0;
Limb targetLimb = extraData.Length > 4 ? (Limb)extraData[4] : null;
ushort useTargetID = extraData.Length > 5 ? (ushort)extraData[5] : (ushort)0;
Vector2? worldPosition = null;
if (extraData.Length > 6) { worldPosition = (Vector2)extraData[6]; }
ActionType actionType = applyStatusEffectEventData.ActionType;
ItemComponent targetComponent = applyStatusEffectEventData.TargetItemComponent;
Limb targetLimb = applyStatusEffectEventData.TargetLimb;
Vector2? worldPosition = applyStatusEffectEventData.WorldPosition;
Character targetCharacter = FindEntityByID(characterID) as Character;
Character targetCharacter = applyStatusEffectEventData.TargetCharacter;
byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255;
msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1);
msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent)));
msg.Write(characterID);
msg.Write(applyStatusEffectEventData.TargetCharacter?.ID ?? (ushort)0);
msg.Write(targetLimbIndex);
msg.Write(useTargetID);
msg.Write(applyStatusEffectEventData.UseTarget?.ID ?? (ushort)0);
msg.Write(worldPosition.HasValue);
if (worldPosition.HasValue)
{
@@ -124,74 +94,56 @@ namespace Barotrauma
}
}
break;
case NetEntityEvent.Type.ChangeProperty:
case ChangePropertyEventData changePropertyEventData:
try
{
WritePropertyChange(msg, extraData, inGameEditableOnly: !GameMain.NetworkMember.IsServer);
WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: !GameMain.NetworkMember.IsServer);
}
catch (Exception e)
{
errorMsg = "Failed to write a ChangeProperty network event for the item \"" + Name + "\" (" + e.Message + ")";
throw new Exception(
$"Failed to write a ChangeProperty network event for the item \"{Name}\" ({e.Message})");
}
break;
case NetEntityEvent.Type.Upgrade:
if (extraData.Length > 0 && extraData[1] is Upgrade upgrade)
case UpgradeEventData upgradeEventData:
var upgrade = upgradeEventData.Upgrade;
var upgradeTargets = upgrade.TargetComponents;
msg.Write(upgrade.Identifier);
msg.Write((byte)upgrade.Level);
msg.Write((byte)upgradeTargets.Count);
foreach (var (_, value) in upgrade.TargetComponents)
{
var upgradeTargets = upgrade.TargetComponents;
msg.Write(upgrade.Identifier);
msg.Write((byte)upgrade.Level);
msg.Write((byte)upgradeTargets.Count);
foreach (var (_, value) in upgrade.TargetComponents)
msg.Write((byte)value.Length);
foreach (var propertyReference in value)
{
msg.Write((byte)value.Length);
foreach (var propertyReference in value)
{
object originalValue = propertyReference.OriginalValue;
msg.Write((float)(originalValue ?? -1));
}
object originalValue = propertyReference.OriginalValue;
msg.Write((float)(originalValue ?? -1));
}
}
else
{
errorMsg = extraData.Length > 0
? $"Failed to write a network event for the item \"{Name}\" - \"{extraData[1].GetType()}\" is not a valid upgrade."
: $"Failed to write a network event for the item \"{Name}\". No upgrade specified.";
}
break;
default:
errorMsg = "Failed to write a network event for the item \"" + Name + "\" - \"" + eventType + "\" is not a valid entity event type for items.";
break;
}
if (!string.IsNullOrEmpty(errorMsg))
{
//something went wrong - rewind the write position and write invalid event type to prevent creating an unreadable event
msg.BitPosition = initialWritePos;
msg.LengthBits = initialWritePos;
msg.WriteRangedInteger((int)NetEntityEvent.Type.Invalid, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
DebugConsole.Log(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.ServerWrite:" + errorMsg, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw error($"Unsupported event type {itemEventData.GetType().Name}");
}
}
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
NetEntityEvent.Type eventType =
(NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
EventType eventType =
(EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
c.KickAFKTimer = 0.0f;
switch (eventType)
{
case NetEntityEvent.Type.ComponentState:
case EventType.ComponentState:
int componentIndex = msg.ReadRangedInteger(0, components.Count - 1);
(components[componentIndex] as IClientSerializable).ServerRead(type, msg, c);
(components[componentIndex] as IClientSerializable).ServerEventRead(msg, c);
break;
case NetEntityEvent.Type.InventoryState:
case EventType.InventoryState:
int containerIndex = msg.ReadRangedInteger(0, components.Count - 1);
(components[containerIndex] as ItemContainer).Inventory.ServerRead(type, msg, c);
(components[containerIndex] as ItemContainer).Inventory.ServerEventRead(msg, c);
break;
case NetEntityEvent.Type.Treatment:
case EventType.Treatment:
if (c.Character == null || !c.Character.CanInteractWith(this)) return;
UInt16 characterID = msg.ReadUInt16();
@@ -217,10 +169,10 @@ namespace Barotrauma
ApplyTreatment(c.Character, targetCharacter, targetLimb);
break;
case NetEntityEvent.Type.ChangeProperty:
case EventType.ChangeProperty:
ReadPropertyChange(msg, inGameEditableOnly: GameMain.NetworkMember.IsServer, sender: c);
break;
case NetEntityEvent.Type.Combine:
case EventType.Combine:
UInt16 combineTargetID = msg.ReadUInt16();
Item combineTarget = FindEntityByID(combineTargetID) as Item;
if (combineTarget == null || !c.Character.CanInteractWith(this) || !c.Character.CanInteractWith(combineTarget))
@@ -232,14 +184,14 @@ namespace Barotrauma
}
}
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID, byte originalItemContainerIndex)
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID, byte originalItemContainerIndex, int originalSlotIndex)
{
if (GameMain.Server == null) { return; }
msg.Write(Prefab.OriginalName);
msg.Write(Prefab.Identifier);
msg.Write(Description != prefab.Description);
if (Description != prefab.Description)
msg.Write(Description != base.Prefab.Description);
if (Description != base.Prefab.Description)
{
msg.Write(Description);
}
@@ -259,9 +211,7 @@ namespace Barotrauma
{
msg.Write(originalInventoryID);
msg.Write(originalItemContainerIndex);
int slotIndex = ParentInventory.FindIndex(this);
msg.Write(slotIndex < 0 ? (byte)255 : (byte)slotIndex);
msg.Write(originalSlotIndex < 0 ? (byte)255 : (byte)originalSlotIndex);
}
msg.Write(body == null ? (byte)0 : (byte)body.BodyType);
@@ -270,6 +220,7 @@ namespace Barotrauma
msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality);
byte teamID = 0;
IdCard idCardComponent = null;
foreach (WifiComponent wifiComponent in GetComponents<WifiComponent>())
{
teamID = (byte)wifiComponent.TeamID;
@@ -280,18 +231,38 @@ namespace Barotrauma
foreach (IdCard idCard in GetComponents<IdCard>())
{
teamID = (byte)idCard.TeamID;
idCardComponent = idCard;
break;
}
}
msg.Write(teamID);
bool tagsChanged = tags.Count != prefab.Tags.Count || !tags.All(t => prefab.Tags.Contains(t));
bool hasIdCard = idCardComponent != null;
msg.Write(hasIdCard);
if (hasIdCard)
{
msg.Write(idCardComponent.OwnerName);
msg.Write(idCardComponent.OwnerTags);
msg.Write((byte)Math.Max(0, idCardComponent.OwnerBeardIndex+1));
msg.Write((byte)Math.Max(0, idCardComponent.OwnerHairIndex+1));
msg.Write((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex+1));
msg.Write((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex+1));
msg.WriteColorR8G8B8(idCardComponent.OwnerHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerFacialHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerSkinColor);
msg.Write(idCardComponent.OwnerJobId);
msg.Write((byte)idCardComponent.OwnerSheetIndex.X);
msg.Write((byte)idCardComponent.OwnerSheetIndex.Y);
}
bool tagsChanged = tags.Count != base.Prefab.Tags.Count || !tags.All(t => base.Prefab.Tags.Contains(t));
msg.Write(tagsChanged);
if (tagsChanged)
{
string[] splitTags = Tags.Split(',');
msg.Write(string.Join(',', splitTags.Where(t => !prefab.Tags.Contains(t))));
msg.Write(string.Join(',', prefab.Tags.Where(t => !splitTags.Contains(t))));
IEnumerable<Identifier> splitTags = Tags.Split(',').ToIdentifiers();
msg.Write(string.Join(',', splitTags.Where(t => !base.Prefab.Tags.Contains(t))));
msg.Write(string.Join(',', base.Prefab.Tags.Where(t => !splitTags.Contains(t))));
}
var nameTag = GetComponent<NameTag>();
msg.Write(nameTag != null);
@@ -368,18 +339,21 @@ namespace Barotrauma
}
}
public void ServerWritePosition(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerWritePosition(IWriteMessage msg, Client c)
{
msg.Write(ID);
IWriteMessage tempBuffer = new WriteOnlyMessage();
body.ServerWrite(tempBuffer, c, extraData);
body.ServerWrite(tempBuffer);
msg.WriteVariableUInt32((uint)tempBuffer.LengthBytes);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WritePadBits();
}
public void CreateServerEvent<T>(T ic) where T : ItemComponent, IServerSerializable
=> CreateServerEvent(ic, ic.ServerGetEventData());
public void CreateServerEvent<T>(T ic, ItemComponent.IEventData extraData) where T : ItemComponent, IServerSerializable
{
if (GameMain.Server == null) { return; }
@@ -391,32 +365,12 @@ namespace Barotrauma
return;
}
int index = components.IndexOf(ic);
if (index == -1) { return; }
#warning TODO: this should throw an exception
if (!components.Contains(ic)) { return; }
object[] extraData = new object[] { NetEntityEvent.Type.ComponentState, index };
ic.ServerAppendExtraData(ref extraData);
GameMain.Server.CreateEntityEvent(this, extraData);
}
public void CreateServerEvent<T>(T ic, object[] extraData) where T : ItemComponent, IServerSerializable
{
if (GameMain.Server == null) { return; }
if (!ItemList.Contains(this))
{
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
int index = components.IndexOf(ic);
if (index == -1) { return; }
object[] data = new object[] { NetEntityEvent.Type.ComponentState, index }.Concat(extraData).ToArray();
GameMain.Server.CreateEntityEvent(this, data);
var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); }
GameMain.Server.CreateEntityEvent(this, eventData);
}
}
}
@@ -0,0 +1,58 @@
using System;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class Level : Entity, IServerSerializable
{
public interface IEventData : NetEntityEvent.IData
{
public EventType EventType { get; }
}
public readonly struct SingleLevelWallEventData : IEventData
{
public EventType EventType => EventType.SingleDestructibleWall;
public readonly DestructibleLevelWall Wall;
public SingleLevelWallEventData(DestructibleLevelWall wall)
{
Wall = wall;
}
}
public readonly struct GlobalLevelWallEventData : IEventData
{
public EventType EventType => EventType.GlobalDestructibleWall;
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed level event: expected {nameof(Level)}.{nameof(IEventData)}"); }
msg.Write((byte)eventData.EventType);
switch (eventData)
{
case SingleLevelWallEventData { Wall: var destructibleWall }:
int index = ExtraWalls.IndexOf(destructibleWall);
msg.Write((ushort)(index == -1 ? ushort.MaxValue : index));
//write health using one byte
msg.Write((byte)MathHelper.Clamp((int)(MathUtils.InverseLerp(0.0f, destructibleWall.MaxHealth, destructibleWall.Damage) * 255.0f), 0, 255));
break;
case GlobalLevelWallEventData _:
foreach (LevelWall levelWall in ExtraWalls)
{
if (levelWall.Body.BodyType == BodyType.Static) { continue; }
msg.Write(levelWall.Body.Position.X);
msg.Write(levelWall.Body.Position.Y);
msg.WriteRangedSingle(levelWall.MoveState, 0.0f, MathHelper.TwoPi, 16);
}
break;
default:
throw new Exception($"Malformed level event: did not expect {eventData.GetType().Name}");
}
}
}
}
@@ -1,15 +1,18 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System;
using System.Xml.Linq;
namespace Barotrauma.MapCreatures.Behavior
{
partial class BallastFloraBehavior
{
partial void LoadPrefab(XElement element)
const float DamageUpdateInterval = 1.0f;
private float damageUpdateTimer;
partial void LoadPrefab(ContentXElement element)
{
foreach (XElement subElement in element.Elements())
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
@@ -29,19 +32,70 @@ namespace Barotrauma.MapCreatures.Behavior
}
}
public void ServerWriteSpawn(IWriteMessage msg)
partial void UpdateDamage(float deltaTime)
{
damageUpdateTimer -= deltaTime;
if (damageUpdateTimer > 0.0f) { return; }
const int maxMessagesPerSecond = 10;
int messages = 0;
foreach (BallastFloraBranch branch in Branches)
{
//don't notify about minuscule amounts of damage (<= 1.0f)
if (branch.AccumulatedDamage > 1.0f)
{
CreateNetworkMessage(new BranchDamageEventData(branch));
branch.AccumulatedDamage = 0.0f;
messages++;
//throttle a bit: if a large ballast flora is withering, it can lead to a very large number of events otherwise
if (messages > maxMessagesPerSecond) { break; }
}
}
damageUpdateTimer = DamageUpdateInterval;
}
public void ServerWrite(IWriteMessage msg, IEventData eventData)
{
msg.Write((byte)eventData.NetworkHeader);
switch (eventData)
{
case SpawnEventData _:
ServerWriteSpawn(msg);
break;
case KillEventData _:
//do nothing
break;
case BranchCreateEventData branchCreateEventData:
ServerWriteBranchGrowth(msg, branchCreateEventData.NewBranch, branchCreateEventData.Parent.ID);
break;
case BranchDamageEventData branchDamageEventData:
ServerWriteBranchDamage(msg, branchDamageEventData.Branch);
break;
case InfectEventData infectEventData:
ServerWriteInfect(msg, infectEventData.Item.ID, infectEventData.Infect, infectEventData.Infector);
break;
case BranchRemoveEventData branchRemoveEventData:
ServerWriteBranchRemove(msg, branchRemoveEventData.Branch);
break;
}
msg.Write(PowerConsumptionTimer);
}
private void ServerWriteSpawn(IWriteMessage msg)
{
msg.Write(Prefab.Identifier);
msg.Write(Offset.X);
msg.Write(Offset.Y);
}
public void ServerWriteBranchGrowth(IWriteMessage msg, BallastFloraBranch branch, int parentId = -1)
private void ServerWriteBranchGrowth(IWriteMessage msg, BallastFloraBranch branch, int parentId = -1)
{
var (x, y) = branch.Position;
msg.Write(parentId);
msg.Write((int)branch.ID);
msg.Write(branch.IsRootGrowth);
msg.WriteRangedInteger((byte)branch.Type, 0b0000, 0b1111);
msg.WriteRangedInteger((byte)branch.Sides, 0b0000, 0b1111);
msg.WriteRangedInteger(branch.FlowerConfig.Serialize(), 0, 0xFFF);
@@ -49,33 +103,33 @@ namespace Barotrauma.MapCreatures.Behavior
msg.Write((ushort)branch.MaxHealth);
msg.Write((int)(x / VineTile.Size));
msg.Write((int)(y / VineTile.Size));
msg.Write(branch.ParentBranch == null ? -1 : Branches.IndexOf(branch.ParentBranch));
}
public void ServerWriteBranchDamage(IWriteMessage msg, BallastFloraBranch branch, float damage)
private void ServerWriteBranchDamage(IWriteMessage msg, BallastFloraBranch branch)
{
msg.Write((int)branch.ID);
msg.Write(damage);
msg.Write(branch.Health);
}
public void ServerWriteInfect(IWriteMessage msg, UInt16 itemID, bool infect, BallastFloraBranch infector = null)
private void ServerWriteInfect(IWriteMessage msg, UInt16 itemID, InfectEventData.InfectState infect, BallastFloraBranch infector = null)
{
msg.Write(itemID);
msg.Write(infect);
if (infect)
msg.Write(infect == InfectEventData.InfectState.Yes);
if (infect == InfectEventData.InfectState.Yes)
{
msg.Write(infector?.ID ?? -1);
}
}
public void ServerWriteBranchRemove(IWriteMessage msg, BallastFloraBranch branch)
private void ServerWriteBranchRemove(IWriteMessage msg, BallastFloraBranch branch)
{
msg.Write(branch.ID);
}
public void SendNetworkMessage(params object[] extraData)
public void CreateNetworkMessage(IEventData extraData)
{
GameMain.Server.CreateEntityEvent(Parent, extraData);
GameMain.Server.CreateEntityEvent(Parent, new Hull.BallastFloraEventData(this, extraData));
}
}
}
@@ -4,14 +4,20 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
namespace Barotrauma
{
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable
{
private float lastSentVolume, lastSentOxygen, lastSentFireCount;
private float sendUpdateTimer;
private float lastSentVolume;
private float lastSentOxygen;
private int lastSentFireCount;
private float statusUpdateTimer;
private float decalUpdateTimer;
private float backgroundSectionUpdateTimer;
private bool decalUpdatePending;
@@ -33,224 +39,163 @@ namespace Barotrauma
return;
}
sendUpdateTimer -= deltaTime;
statusUpdateTimer -= deltaTime;
decalUpdateTimer -= deltaTime;
backgroundSectionUpdateTimer -= deltaTime;
//update client hulls if the amount of water has changed by >10%
//or if oxygen percentage has changed by 5%
if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || Math.Abs(lastSentOxygen - OxygenPercentage) > 5f ||
lastSentFireCount != FireSources.Count || FireSources.Count > 0 ||
pendingSectionUpdates.Count > 0 ||
sendUpdateTimer < -NetConfig.SparseHullUpdateInterval ||
decalUpdatePending)
{
if (sendUpdateTimer < 0.0f)
{
if (decalUpdatePending)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { false });
}
if (pendingSectionUpdates.Count > 0)
{
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { true, pendingSectionUpdate } );
}
pendingSectionUpdates.Clear();
}
else
{
GameMain.NetworkMember.CreateEntityEvent(this);
}
bool shouldSendStatusUpdate =
(Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f
|| Math.Abs(lastSentOxygen - OxygenPercentage) > 5f
|| lastSentFireCount != FireSources.Count)
&& statusUpdateTimer <= 0.0f;
lastSentVolume = waterVolume;
lastSentOxygen = OxygenPercentage;
lastSentFireCount = FireSources.Count;
sendUpdateTimer = NetConfig.HullUpdateInterval;
if (shouldSendStatusUpdate)
{
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
lastSentVolume = waterVolume;
lastSentOxygen = OxygenPercentage;
lastSentFireCount = FireSources.Count;
statusUpdateTimer = NetConfig.SparseHullUpdateInterval;
}
if (decalUpdatePending && decalUpdateTimer <= 0.0f)
{
GameMain.NetworkMember.CreateEntityEvent(this, new DecalEventData());
decalUpdateTimer = NetConfig.HullUpdateInterval;
decalUpdatePending = false;
}
if (pendingSectionUpdates.Count > 0 && backgroundSectionUpdateTimer <= 0.0f)
{
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate));
}
backgroundSectionUpdateTimer = NetConfig.HullUpdateInterval;
pendingSectionUpdates.Clear();
}
}
public void ServerWrite(IWriteMessage message, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
if (extraData != null && extraData.Length >= 2 && extraData[0] is BallastFloraBehavior behavior && extraData[1] is BallastFloraBehavior.NetworkHeader header)
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed hull event: expected {nameof(Hull)}.{nameof(IEventData)}"); }
msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData)
{
message.Write(true);
message.Write((byte)header);
switch (header)
{
case BallastFloraBehavior.NetworkHeader.Spawn:
behavior.ServerWriteSpawn(message);
break;
case BallastFloraBehavior.NetworkHeader.Kill:
break;
case BallastFloraBehavior.NetworkHeader.BranchCreate when extraData.Length >= 4 && extraData[2] is BallastFloraBranch branch && extraData[3] is int parentId:
behavior.ServerWriteBranchGrowth(message, branch, parentId);
break;
case BallastFloraBehavior.NetworkHeader.BranchDamage when extraData.Length >= 4 && extraData[2] is BallastFloraBranch branch && extraData[3] is float damage:
behavior.ServerWriteBranchDamage(message, branch, damage);
break;
case BallastFloraBehavior.NetworkHeader.BranchRemove when extraData.Length >= 3 && extraData[2] is BallastFloraBranch branch:
behavior.ServerWriteBranchRemove(message, branch);
break;
case BallastFloraBehavior.NetworkHeader.Infect when extraData.Length >= 4 && extraData[2] is UInt16 itemID && extraData[3] is bool infect:
BallastFloraBranch infector = null;
if (extraData.Length >= 5 && extraData[4] is BallastFloraBranch b) { infector = b; }
behavior.ServerWriteInfect(message, itemID, infect, infector);
break;
}
message.Write(behavior.PowerConsumptionTimer);
return;
}
message.Write(false); //not a ballast flora update
message.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
message.WriteRangedSingle(MathHelper.Clamp(OxygenPercentage, 0.0f, 100.0f), 0.0f, 100.0f, 8);
message.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{
message.WriteRangedInteger(Math.Min(FireSources.Count, 16), 0, 16);
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = FireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
message.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
message.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
}
}
message.Write(extraData != null);
if (extraData != null)
{
message.Write((bool)extraData[0]);
// Section update
if ((bool)extraData[0])
{
int sectorToUpdate = (int)extraData[1];
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
message.WriteRangedInteger(sectorToUpdate, 0, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
message.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
message.Write(BackgroundSections[i].Color.PackedValue);
}
}
else // Decal update
{
message.WriteRangedInteger(decals.Count, 0, MaxDecalsPerHull);
case StatusEventData statusEventData:
msg.WriteRangedSingle(MathHelper.Clamp(OxygenPercentage, 0.0f, 100.0f), 0.0f, 100.0f, 8);
SharedStatusWrite(msg);
break;
case BackgroundSectionsEventData backgroundSectionsEventData:
SharedBackgroundSectionsWrite(msg, backgroundSectionsEventData);
break;
case DecalEventData decalEventData:
msg.WriteRangedInteger(decals.Count, 0, MaxDecalsPerHull);
foreach (Decal decal in decals)
{
message.Write(decal.Prefab.UIntIdentifier);
message.Write((byte)decal.SpriteIndex);
msg.Write(decal.Prefab.UintIdentifier);
msg.Write((byte)decal.SpriteIndex);
float normalizedXPos = MathHelper.Clamp(MathUtils.InverseLerp(0.0f, rect.Width, decal.CenterPosition.X), 0.0f, 1.0f);
float normalizedYPos = MathHelper.Clamp(MathUtils.InverseLerp(-rect.Height, 0.0f, decal.CenterPosition.Y), 0.0f, 1.0f);
message.WriteRangedSingle(normalizedXPos, 0.0f, 1.0f, 8);
message.WriteRangedSingle(normalizedYPos, 0.0f, 1.0f, 8);
message.WriteRangedSingle(decal.Scale, 0f, 2f, 12);
msg.WriteRangedSingle(normalizedXPos, 0.0f, 1.0f, 8);
msg.WriteRangedSingle(normalizedYPos, 0.0f, 1.0f, 8);
msg.WriteRangedSingle(decal.Scale, 0f, 2f, 12);
}
}
break;
case BallastFloraEventData ballastFloraEventData:
ballastFloraEventData.Behavior.ServerWrite(msg, ballastFloraEventData.SubEventData);
break;
default:
throw new Exception($"Malformed hull event: did not expect {eventData.GetType().Name}");
}
}
//used when clients use the water/fire console commands or section / decal updates are received
public void ServerRead(ClientNetObject type, IReadMessage msg, Client c)
public void ServerEventRead(IReadMessage msg, Client c)
{
int messageType = msg.ReadRangedInteger(0, 2);
if (messageType == 0)
EventType eventType = (EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventType)
{
float newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
case EventType.Status:
SharedStatusRead(
msg,
out float newWaterVolume,
out NetworkFireSource[] newFireSources);
bool hasFireSources = msg.ReadBoolean();
int fireSourceCount = 0;
List<Vector3> newFireSources = new List<Vector3>();
if (hasFireSources)
{
fireSourceCount = msg.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
if (!c.HasPermission(ClientPermissions.ConsoleCommands) ||
!c.PermittedConsoleCommands.Any(command => command.names.Contains("fire") || command.names.Contains("editfire")))
{
newFireSources.Add(new Vector3(
MathHelper.Clamp(msg.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
MathHelper.Clamp(msg.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
msg.ReadRangedSingle(0.0f, 1.0f, 8)));
return;
}
}
if (!c.HasPermission(ClientPermissions.ConsoleCommands) ||
!c.PermittedConsoleCommands.Any(command => command.names.Contains("fire") || command.names.Contains("editfire")))
{
return;
}
WaterVolume = newWaterVolume;
WaterVolume = newWaterVolume;
for (int i = 0; i < fireSourceCount; i++)
{
Vector2 pos = new Vector2(
rect.X + rect.Width * newFireSources[i].X,
rect.Y - rect.Height + (rect.Height * newFireSources[i].Y));
float size = newFireSources[i].Z * rect.Width;
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
//ignore if the fire wasn't added to this room (invalid position)?
if (!FireSources.Contains(newFire))
for (int i = 0; i < newFireSources.Length; i++)
{
newFire.Remove();
continue;
}
}
Vector2 pos = newFireSources[i].Position;
float size = newFireSources[i].Size;
for (int i = FireSources.Count - 1; i >= fireSourceCount; i--)
{
FireSources[i].Remove();
if (i < FireSources.Count)
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
//ignore if the fire wasn't added to this room (invalid position)?
if (!FireSources.Contains(newFire))
{
newFire.Remove();
continue;
}
}
for (int i = FireSources.Count - 1; i >= newFireSources.Length; i--)
{
FireSources.RemoveAt(i);
FireSources[i].Remove();
if (i < FireSources.Count)
{
FireSources.RemoveAt(i);
}
}
}
}
else if (messageType == 1)
{
byte decalIndex = msg.ReadByte();
float decalAlpha = msg.ReadRangedSingle(0.0f, 1.0f, 255);
if (decalIndex < 0 || decalIndex >= decals.Count) { return; }
if (c.Character != null && c.Character.AllowInput && c.Character.HeldItems.Any(it => it.GetComponent<Sprayer>() != null))
{
decals[decalIndex].BaseAlpha = decalAlpha;
}
decalUpdatePending = true;
}
else
{
int sectorToUpdate = msg.ReadRangedInteger(0, BackgroundSections.Count - 1);
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
float colorStrength = msg.ReadRangedSingle(0.0f, 1.0f, 8);
Color color = new Color(msg.ReadUInt32());
break;
case EventType.BackgroundSections:
SharedBackgroundSectionRead(
msg,
bsnu =>
{
int i = bsnu.SectionIndex;
Color color = bsnu.Color;
float colorStrength = bsnu.ColorStrength;
//TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches
#warning TODO: verify the client is close enough to this hull to paint it, that the sprayer is functional and that the color matches
if (!(c.Character is { AllowInput: true })) { return; }
if (c.Character.HeldItems.All(it => it.GetComponent<Sprayer>() == null)) { return; }
BackgroundSections[i].SetColorStrength(colorStrength);
BackgroundSections[i].SetColor(color);
},
out int sectorToUpdate);
//add to pending updates to notify other clients as well
pendingSectionUpdates.Add(sectorToUpdate);
break;
case EventType.Decal:
byte decalIndex = msg.ReadByte();
float decalAlpha = msg.ReadRangedSingle(0.0f, 1.0f, 255);
if (decalIndex < 0 || decalIndex >= decals.Count) { return; }
if (c.Character != null && c.Character.AllowInput && c.Character.HeldItems.Any(it => it.GetComponent<Sprayer>() != null))
{
BackgroundSections[i].SetColorStrength(colorStrength);
BackgroundSections[i].SetColor(color);
decals[decalIndex].BaseAlpha = decalAlpha;
}
}
//add to pending updates to notify other clients as well
pendingSectionUpdates.Add(sectorToUpdate);
}
decalUpdatePending = true;
break;
default:
throw new Exception($"Malformed incoming hull event: {eventType} is not a supported event type");
}
}
}
}
@@ -9,7 +9,7 @@ namespace Barotrauma
GameMain.Server.KarmaManager.OnStructureHealthChanged(this, attacker, damageAmount);
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)Sections.Length);
for (int i = 0; i < Sections.Length; i++)
@@ -1,17 +1,23 @@
using Barotrauma.Networking;
using System;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class Submarine
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerWritePosition(IWriteMessage msg, Client c)
{
msg.Write(ID);
IWriteMessage tempBuffer = new WriteOnlyMessage();
subBody.Body.ServerWrite(tempBuffer, c, extraData);
subBody.Body.ServerWrite(tempBuffer);
msg.Write((byte)tempBuffer.LengthBytes);
msg.Write(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
msg.WritePadBits();
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
throw new Exception($"Error while writing a network event for the submarine \"{Info.Name} ({ID})\". Submarines are not even supposed to send events!");
}
}
}
@@ -38,7 +38,7 @@ namespace Barotrauma.Networking
public bool CompareTo(string endpointCompare)
{
if (string.IsNullOrEmpty(EndPoint) || string.IsNullOrEmpty(EndPoint)) { return false; }
if (string.IsNullOrEmpty(EndPoint) || string.IsNullOrEmpty(endpointCompare)) { return false; }
if (!IsRangeBan)
{
return endpointCompare == EndPoint;
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System;
using System;
using System.Text;
using MoonSharp.Interpreter;
@@ -18,31 +17,48 @@ namespace Barotrauma.Networking
Character orderTargetCharacter = null;
Entity orderTargetEntity = null;
OrderChatMessage orderMsg = null;
OrderTarget orderTargetPosition = null;
Order.OrderTargetType orderTargetType = Order.OrderTargetType.Entity;
int? wallSectionIndex = null;
Order order = null;
bool isNewOrder = false;
if (type == ChatMessageType.Order)
{
var orderMessageInfo = OrderChatMessage.ReadOrder(msg);
if (orderMessageInfo.OrderIndex < 0 || orderMessageInfo.OrderIndex >= Order.PrefabList.Count)
if (orderMessageInfo.OrderIdentifier == Identifier.Empty)
{
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order index out of bounds ({orderMessageInfo.OrderIndex}).");
DebugConsole.ThrowError($"Invalid order message from client \"{c.Name}\" - order identifier is empty.");
if (NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { c.LastSentChatMsgID = ID; }
return;
}
isNewOrder = orderMessageInfo.IsNewOrder;
orderTargetCharacter = orderMessageInfo.TargetCharacter;
orderTargetEntity = orderMessageInfo.TargetEntity;
orderTargetPosition = orderMessageInfo.TargetPosition;
OrderTarget orderTargetPosition = orderMessageInfo.TargetPosition;
orderTargetType = orderMessageInfo.TargetType;
wallSectionIndex = orderMessageInfo.WallSectionIndex;
var orderPrefab = orderMessageInfo.OrderPrefab ?? Order.PrefabList[orderMessageInfo.OrderIndex];
string orderOption = orderMessageInfo.OrderOption ??
(orderMessageInfo.OrderOptionIndex == null || orderMessageInfo.OrderOptionIndex < 0 || orderMessageInfo.OrderOptionIndex >= orderPrefab.Options.Length ?
"" : orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value]);
orderMsg = new OrderChatMessage(orderPrefab, orderOption, orderMessageInfo.Priority, orderTargetPosition ?? orderTargetEntity as ISpatialEntity, orderTargetCharacter, c.Character, isNewOrder: orderMessageInfo.IsNewOrder)
var orderPrefab = orderMessageInfo.OrderPrefab ?? OrderPrefab.Prefabs[orderMessageInfo.OrderIdentifier];
Identifier orderOption = orderMessageInfo.OrderOption;
if (orderOption.IsEmpty)
{
WallSectionIndex = wallSectionIndex
};
orderOption = orderMessageInfo.OrderOptionIndex == null || orderMessageInfo.OrderOptionIndex < 0 || orderMessageInfo.OrderOptionIndex >= orderPrefab.Options.Length ?
Identifier.Empty : orderPrefab.Options[orderMessageInfo.OrderOptionIndex.Value];
}
if (orderTargetType == Order.OrderTargetType.Position)
{
order = new Order(orderPrefab, orderOption, orderTargetPosition, orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
else if (orderTargetType == Order.OrderTargetType.WallSection)
{
order = new Order(orderPrefab, orderOption, orderTargetEntity as Structure, wallSectionIndex, orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
else
{
order = new Order(orderPrefab, orderOption, orderTargetEntity, orderPrefab.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: c.Character)
.WithManualPriority(orderMessageInfo.Priority);
}
orderMsg = new OrderChatMessage(order, orderTargetCharacter, c.Character);
txt = orderMsg.Text;
}
else
@@ -96,11 +112,11 @@ namespace Barotrauma.Networking
if (c.ChatSpamCount > 3)
{
//kick for spamming too much
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked"));
GameMain.Server.KickClient(c, TextManager.Get("SpamFilterKicked").Value);
}
else
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
}
@@ -111,7 +127,7 @@ namespace Barotrauma.Networking
if (c.ChatSpamTimer > 0.0f && !isOwner && !GameMain.Lua.game.disableSpamFilter)
{
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked"), ChatMessageType.Server, null);
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
c.ChatSpamTimer = 10.0f;
GameMain.Server.SendDirectChatMessage(denyMsg, c);
return;
@@ -132,16 +148,6 @@ namespace Barotrauma.Networking
{
HumanAIController.ReportProblem(orderMsg.Sender, orderMsg.Order);
}
Order order = orderTargetType switch
{
Order.OrderTargetType.Entity =>
new Order(orderMsg.Order, orderTargetEntity, orderMsg.Order?.GetTargetItemComponent(orderTargetEntity as Item), orderGiver: orderMsg.Sender),
Order.OrderTargetType.Position =>
new Order(orderMsg.Order, orderTargetPosition, orderGiver: orderMsg.Sender),
Order.OrderTargetType.WallSection when orderTargetEntity is Structure s && wallSectionIndex.HasValue =>
new Order(orderMsg.Order, s, wallSectionIndex, orderGiver: orderMsg.Sender),
_ => throw new NotImplementedException()
};
if (order != null)
{
if (order.TargetAllCharacters)
@@ -168,7 +174,7 @@ namespace Barotrauma.Networking
}
else if (orderTargetCharacter != null)
{
orderTargetCharacter.SetOrder(order, orderMsg.OrderOption, orderMsg.OrderPriority, orderMsg.Sender);
orderTargetCharacter.SetOrder(order, isNewOrder);
}
}
GameMain.Server.SendOrderChatMessage(orderMsg);
@@ -1,4 +1,7 @@
using System.IO.Pipes;
using System;
using System.IO.Pipes;
using System.Text;
using System.Threading;
namespace Barotrauma.Networking
{
@@ -14,6 +17,12 @@ namespace Barotrauma.Networking
PrivateStart();
}
public static void NotifyCrash(string msg)
{
errorsToWrite.Enqueue(msg);
Thread.Sleep(1000);
}
public static void ShutDown()
{
PrivateShutDown();
@@ -57,8 +57,8 @@ namespace Barotrauma.Networking
public bool ReadyToStart;
public List<Pair<JobPrefab, int>> JobPreferences;
public Pair<JobPrefab, int> AssignedJob;
public List<JobVariant> JobPreferences;
public JobVariant AssignedJob;
public float DeleteDisconnectedTimer;
@@ -104,7 +104,7 @@ namespace Barotrauma.Networking
partial void InitProjSpecific()
{
JobPreferences = new List<Pair<JobPrefab, int>>();
JobPreferences = new List<JobVariant>();
VoipQueue = new VoipQueue(ID, true, true);
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
@@ -149,7 +149,7 @@ namespace Barotrauma.Networking
foreach (char character in name)
{
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.First && (int)character <= charRange.Second)) { return false; }
if (!serverSettings.AllowedClientNameChars.Any(charRange => (int)character >= charRange.Start && (int)character <= charRange.End)) { return false; }
}
return true;
@@ -1,47 +1,55 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public void CreateNetworkEvent(Entity entity, bool remove)
public void CreateNetworkEvent(SpawnOrRemove spawnOrRemove)
{
CreateNetworkEventProjSpecific(entity, remove);
CreateNetworkEventProjSpecific(spawnOrRemove);
}
partial void CreateNetworkEventProjSpecific(Entity entity, bool remove)
partial void CreateNetworkEventProjSpecific(SpawnOrRemove spawnOrRemove)
{
if (GameMain.Server != null && entity != null)
if (GameMain.Server == null || spawnOrRemove?.Entity == null) { return; }
GameMain.Server.CreateEntityEvent(this, spawnOrRemove);
if (spawnOrRemove.Entity is Character { Info: { } } character)
{
GameMain.Server.CreateEntityEvent(this, new object[] { new SpawnOrRemove(entity, remove) });
foreach (var statKey in character.Info.SavedStatValues.Keys)
{
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdatePermanentStatsEventData(statKey));
}
}
}
public void ServerWrite(IWriteMessage message, Client client, object[] extraData = null)
public void ServerEventWrite(IWriteMessage message, Client client, NetEntityEvent.IData extraData = null)
{
if (GameMain.Server == null) return;
if (GameMain.Server is null) { return; }
if (!(extraData is SpawnOrRemove entities)) { throw new Exception($"Malformed {nameof(EntitySpawner)} event: expected {nameof(SpawnOrRemove)}"); }
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
message.Write(entities.Remove);
if (entities.Remove)
message.Write(entities is RemoveEntity);
if (entities is RemoveEntity)
{
message.Write(entities.OriginalID);
message.Write(entities.ID);
}
else
{
if (entities.Entity is Item)
switch (entities.Entity)
{
message.Write((byte)SpawnableType.Item);
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID, entities.OriginalInventoryID, entities.OriginalItemContainerIndex);
}
else if (entities.Entity is Character)
{
message.Write((byte)SpawnableType.Character);
DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Character)entities.Entity).WriteSpawnData(message, entities.OriginalID, restrictMessageSize: true);
case Item item:
message.Write((byte)SpawnableType.Item);
DebugConsole.Log(
$"Writing item spawn data {item} (ID: {entities.ID})");
item.WriteSpawnData(message, entities.ID, entities.InventoryID, entities.ItemContainerIndex, entities.SlotIndex);
break;
case Character character:
message.Write((byte)SpawnableType.Character);
DebugConsole.Log(
$"Writing character spawn data: {character} (ID: {entities.ID})");
character.WriteSpawnData(message, entities.ID, restrictMessageSize: true);
break;
}
}
}
@@ -36,12 +36,25 @@ namespace Barotrauma.Networking
get { return KnownReceivedOffset / (float)Data.Length; }
}
private float waitTimer;
public float WaitTimer
{
get;
set;
get => waitTimer;
set
{
if (value > 0.0f)
{
//setting a wait timer means that network conditions
//aren't ideal, slow down the packet rate
PacketsPerUpdate = Math.Max(PacketsPerUpdate / 4.0f, 1.0f);
}
waitTimer = value;
}
}
public const int MaxPacketsPerUpdate = 4;
public float PacketsPerUpdate { get; set; } = 1.0f;
public byte[] Data { get; }
public bool Acknowledged;
@@ -112,15 +125,12 @@ namespace Barotrauma.Networking
public float StallPacketsTime { get; set; }
#endif
public List<FileTransferOut> ActiveTransfers
{
get { return activeTransfers; }
}
public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
public FileSender(ServerPeer serverPeer, int mtu)
{
peer = serverPeer;
chunkLen = mtu - 100;
chunkLen = mtu - 200;
activeTransfers = new List<FileTransferOut>();
}
@@ -163,13 +173,14 @@ namespace Barotrauma.Networking
}
OnStarted(transfer);
GameMain.Server.LastClientListUpdateID++;
return transfer;
}
public void Update(float deltaTime)
{
activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
int numRemoved = activeTransfers.RemoveAll(t => t.Connection.Status != NetworkConnectionStatus.Connected);
var endedTransfers = activeTransfers.FindAll(t =>
t.Connection.Status != NetworkConnectionStatus.Connected ||
@@ -186,20 +197,19 @@ namespace Barotrauma.Networking
foreach (FileTransferOut transfer in activeTransfers)
{
transfer.WaitTimer -= deltaTime;
for (int i = 0; i < 10; i++)
{
if (transfer.WaitTimer > 0.0f) { break; }
Send(transfer);
}
if (transfer.WaitTimer > 0.0f) { continue; }
Send(transfer);
}
if (numRemoved > 0 || endedTransfers.Count > 0)
{
GameMain.Server.LastClientListUpdateID++;
}
}
private void Send(FileTransferOut transfer)
{
// send another part of the file
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
IWriteMessage message;
try
@@ -234,7 +244,7 @@ namespace Barotrauma.Networking
transfer.Status = FileTransferStatus.Sending;
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log("Sending file transfer initiation message: ");
DebugConsole.Log(" File: " + transfer.FileName);
@@ -246,28 +256,44 @@ namespace Barotrauma.Networking
return;
}
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
message.Write((byte)transfer.ID);
message.Write(transfer.SentOffset);
byte[] sendBytes = new byte[sendByteCount];
Array.Copy(transfer.Data, transfer.SentOffset, sendBytes, 0, sendByteCount);
message.Write((ushort)sendByteCount);
message.Write(sendBytes, 0, sendByteCount);
transfer.SentOffset += sendByteCount;
if (transfer.SentOffset > transfer.KnownReceivedOffset + chunkLen * 10 ||
transfer.SentOffset >= transfer.Data.Length)
for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
{
transfer.SentOffset = transfer.KnownReceivedOffset;
transfer.WaitTimer = 0.5f;
}
long remaining = transfer.Data.Length - transfer.SentOffset;
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
message = new WriteOnlyMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Data);
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable);
message.Write((byte)transfer.ID);
message.Write(transfer.SentOffset);
message.Write((ushort)sendByteCount);
int chunkDestPos = message.BytePosition;
message.BitPosition += sendByteCount * 8;
message.LengthBits = Math.Max(message.LengthBits, message.BitPosition);
Array.Copy(transfer.Data, transfer.SentOffset, message.Buffer, chunkDestPos, sendByteCount);
transfer.SentOffset += sendByteCount;
if (transfer.SentOffset >= transfer.Data.Length)
{
transfer.SentOffset = transfer.KnownReceivedOffset;
transfer.WaitTimer = 1.0f;
}
peer.Send(message, transfer.Connection, DeliveryMethod.Unreliable, compressPastThreshold: false);
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
}
//try to increase the packet rate so large files get sent faster,
//this gets reset when packet loss or disorder sets in
transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
transfer.PacketsPerUpdate + 0.05f);
}
#if DEBUG
transfer.WaitTimer = Math.Max(transfer.WaitTimer, StallPacketsTime);
#endif
@@ -283,11 +309,6 @@ namespace Barotrauma.Networking
transfer.Status = FileTransferStatus.Error;
return;
}
if (GameSettings.VerboseLogging)
{
DebugConsole.Log($"Sending {sendByteCount} bytes of the file {transfer.FileName} ({transfer.SentOffset / 1000}/{transfer.Data.Length / 1000} kB sent)");
}
}
public void CancelTransfer(FileTransferOut transfer)
@@ -302,9 +323,9 @@ namespace Barotrauma.Networking
public void ReadFileRequest(IReadMessage inc, Client client)
{
byte messageType = inc.ReadByte();
FileTransferMessageType messageType = (FileTransferMessageType)inc.ReadByte();
if (messageType == (byte)FileTransferMessageType.Cancel)
if (messageType == FileTransferMessageType.Cancel)
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
@@ -312,42 +333,51 @@ namespace Barotrauma.Networking
return;
}
else if (messageType == (byte)FileTransferMessageType.Data)
else if (messageType == FileTransferMessageType.Data)
{
byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null)
{
matchingTransfer.Acknowledged = true;
int offset = inc.ReadInt32();
matchingTransfer.KnownReceivedOffset = offset > matchingTransfer.KnownReceivedOffset ? offset : matchingTransfer.KnownReceivedOffset;
int expecting = inc.ReadInt32(); //the offset the client is waiting for
int lastSeen = Math.Min(matchingTransfer.SentOffset, inc.ReadInt32()); //the last offset the client got from us
matchingTransfer.KnownReceivedOffset = Math.Max(expecting, matchingTransfer.KnownReceivedOffset);
if (matchingTransfer.SentOffset < matchingTransfer.KnownReceivedOffset)
{
matchingTransfer.WaitTimer = 0.0f;
matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
}
if (lastSeen - matchingTransfer.KnownReceivedOffset >= chunkLen * 10 ||
matchingTransfer.SentOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.SentOffset = matchingTransfer.KnownReceivedOffset;
matchingTransfer.WaitTimer = 1.0f;
}
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
{
matchingTransfer.Status = FileTransferStatus.Finished;
}
}
return;
}
byte fileType = inc.ReadByte();
FileTransferType fileType = (FileTransferType)inc.ReadByte();
switch (fileType)
{
case (byte)FileTransferType.Submarine:
case FileTransferType.Submarine:
string fileName = inc.ReadString();
string fileHash = inc.ReadString();
var requestedSubmarine = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.Hash == fileHash);
var requestedSubmarine = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == fileName && s.MD5Hash.StringRepresentation == fileHash);
if (requestedSubmarine != null)
{
StartTransfer(inc.Sender, FileTransferType.Submarine, requestedSubmarine.FilePath);
}
break;
case (byte)FileTransferType.CampaignSave:
case FileTransferType.CampaignSave:
if (GameMain.GameSession != null &&
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
{
@@ -357,6 +387,23 @@ namespace Barotrauma.Networking
client.LastCampaignSaveSendTime = new Pair<ushort, float>(campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
}
}
break;
case FileTransferType.Mod:
string modName = inc.ReadString();
Md5Hash modHash = Md5Hash.StringAsHash(inc.ReadString());
if (!GameMain.Server.ServerSettings.AllowModDownloads) { return; }
if (!(GameMain.Server.ModSender is { Ready: true })) { return; }
ContentPackage mod = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Hash.Equals(modHash));
if (mod is null) { return; }
string modCompressedPath = ModSender.GetCompressedModPath(mod);
if (!File.Exists(modCompressedPath)) { return; }
StartTransfer(inc.Sender, FileTransferType.Mod, modCompressedPath);
break;
}
}
@@ -0,0 +1,59 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma.Networking
{
class ModSender : IDisposable
{
public const string UploadFolder = "TempMods_Upload";
public const string Extension = ".barodir.gz";
public bool Ready { get; private set; } = false;
public ModSender()
{
DeleteDir();
Directory.CreateDirectory(UploadFolder);
TaskPool.Add(
"ModSender",
Task.WhenAll(
ContentPackageManager.EnabledPackages.All
.Where(p => p != ContentPackageManager.VanillaCorePackage && p.HasMultiplayerSyncedContent)
.Select(CompressMod)),
(t) => Ready = true);
}
public static string GetCompressedModPath(ContentPackage mod)
{
string dir = mod.Dir;
string resultFileName
= dir.StartsWith(ContentPackage.LocalModsDir)
? $"Local_{mod.Name}"
: $"Workshop_{mod.Name}_{mod.SteamWorkshopId}";
resultFileName = ToolBox.RemoveInvalidFileNameChars(resultFileName.Replace('\\', '_').Replace('/', '_'));
resultFileName = $"{resultFileName}{Extension}";
return Path.Combine(UploadFolder, resultFileName);
}
public async Task CompressMod(ContentPackage mod)
{
await Task.Yield();
string dir = mod.Dir;
SaveUtil.CompressDirectory(dir, GetCompressedModPath(mod), fileName => { });
}
private void DeleteDir()
{
if (Directory.Exists(UploadFolder)) { Directory.Delete(UploadFolder, recursive: true); }
}
public bool IsDisposed { get; private set; } = false;
public void Dispose()
{
IsDisposed = true;
DeleteDir();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -128,7 +128,7 @@ namespace Barotrauma
clientMemory.PreviousNotifiedKarma >= KickBanThreshold + KarmaNotificationInterval &&
client.Karma < KickBanThreshold + KarmaNotificationInterval)
{
GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning"), client);
GameMain.Server.SendDirectChatMessage(TextManager.Get("KarmaBanWarning").Value, client);
GameServer.Log(GameServer.ClientLogName(client) + " has been warned for having dangerously low karma.", ServerLog.MessageType.Karma);
clientMemory.PreviousNotifiedKarma = client.Karma;
clientMemory.PreviousKarmaNotificationTime = Timing.TotalTime;
@@ -170,7 +170,7 @@ namespace Barotrauma
existingAffliction.Strength = herpesStrength;
if (herpesStrength <= 0.0f)
{
client.Character.CharacterHealth.ReduceAffliction(null, "invertcontrols", 100.0f);
client.Character.CharacterHealth.ReduceAfflictionOnAllLimbs("invertcontrols".ToIdentifier(), 100.0f);
}
}
@@ -283,7 +283,7 @@ namespace Barotrauma
if (foundItem == null) { return; }
bool isIdCard = foundItem.prefab.Identifier == "idcard";
bool isIdCard = ((MapEntity)foundItem).Prefab.Identifier == "idcard";
bool isWeapon = foundItem.GetComponent<RangedWeapon>() != null || foundItem.GetComponent<MeleeWeapon>() != null;
if (isIdCard)
@@ -394,8 +394,8 @@ namespace Barotrauma
}
//attacking/healing clowns has a smaller effect on karma
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
if (target.HasEquippedItem("clownmask".ToIdentifier()) &&
target.HasEquippedItem("clowncostume".ToIdentifier()))
{
damage *= 0.5f;
stun *= 0.5f;
@@ -604,8 +604,8 @@ namespace Barotrauma
if (client == null) { return; }
//all penalties/rewards are halved when wearing a clown costume
if (target.HasEquippedItem("clownmask") &&
target.HasEquippedItem("clowncostume"))
if (target.HasEquippedItem("clownmask".ToIdentifier()) &&
target.HasEquippedItem("clowncostume".ToIdentifier()))
{
amount *= 0.5f;
}
@@ -38,7 +38,7 @@ namespace Barotrauma.Networking
public void Write(IWriteMessage msg, Client recipient)
{
serializable.ServerWrite(msg, recipient, Data);
serializable.ServerEventWrite(msg, recipient, Data);
}
}
@@ -111,7 +111,7 @@ namespace Barotrauma.Networking
lastWarningTime = -10.0;
}
public void CreateEvent(IServerSerializable entity, object[] extraData = null)
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
{
if (!ValidateEntity(entity)) { return; }
@@ -179,7 +179,7 @@ namespace Barotrauma.Networking
catch (Exception e)
{
string entityName = bufferedEvent.TargetEntity == null ? "null" : bufferedEvent.TargetEntity.ToString();
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
string errorMsg = "Failed to read server event for entity \"" + entityName + "\"!";
GameServer.Log(errorMsg + "\n" + e.StackTrace.CleanupStackTrace(), ServerLog.MessageType.Error);
@@ -291,12 +291,6 @@ namespace Barotrauma.Networking
bufferedEvents.Add(bufferedEvent);
}
public void RefreshEntityIDs()
{
events.ForEach(e => e.RefreshEntityID());
uniqueEvents.ForEach(e => e.RefreshEntityID());
}
/// <summary>
/// Writes all the events that the client hasn't received yet into the outgoing message
/// </summary>
@@ -310,15 +304,7 @@ namespace Barotrauma.Networking
/// </summary>
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
List<NetEntityEvent> eventsToSync = null;
if (client.NeedsMidRoundSync)
{
eventsToSync = GetEventsToSync(client);
}
else
{
eventsToSync = GetEventsToSync(client);
}
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
if (eventsToSync.Count == 0)
{
@@ -347,7 +333,7 @@ namespace Barotrauma.Networking
count++;
if (count > 3) { break; }
}
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
GameServer.Log(warningMsg, ServerLog.MessageType.Error);
}
@@ -460,6 +446,7 @@ namespace Barotrauma.Networking
/// </summary>
public void Read(IReadMessage msg, Client sender = null)
{
msg.ReadPadBits();
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
@@ -470,7 +457,6 @@ namespace Barotrauma.Networking
if (entityID == Entity.NullEntityID)
{
msg.ReadPadBits();
if (thisEventID == (UInt16)(sender.LastSentEntityEventID + 1)) sender.LastSentEntityEventID++;
continue;
}
@@ -482,7 +468,7 @@ namespace Barotrauma.Networking
//skip the event if we've already received it
if (thisEventID != (UInt16)(sender.LastSentEntityEventID + 1))
{
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received msg " + thisEventID + ", expecting " + sender.LastSentEntityEventID, Color.Red);
}
@@ -490,10 +476,10 @@ namespace Barotrauma.Networking
}
else if (entity == null)
{
//entity not found -> consider the even read and skip over it
//entity not found -> consider the event read and skip over it
//(can happen, for example, when a client uses a medical item repeatedly
//and creates an event for it before receiving the event about it being removed)
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage(
"Received msg " + thisEventID + ", entity " + entityID + " not found",
@@ -504,7 +490,7 @@ namespace Barotrauma.Networking
}
else
{
if (GameSettings.VerboseLogging)
if (GameSettings.CurrentConfig.VerboseLogging)
{
DebugConsole.NewMessage("Received msg " + thisEventID, Microsoft.Xna.Framework.Color.Green);
}
@@ -519,7 +505,6 @@ namespace Barotrauma.Networking
sender.LastSentEntityEventID++;
}
msg.ReadPadBits();
}
}
@@ -536,7 +521,7 @@ namespace Barotrauma.Networking
var clientEntity = entity as IClientSerializable;
if (clientEntity == null) return;
clientEntity.ServerRead(ClientNetObject.ENTITY_STATE, buffer, sender);
clientEntity.ServerEventRead(buffer, sender);
}
public void Clear()
@@ -133,7 +133,7 @@ namespace Barotrauma.Networking
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
@@ -339,7 +339,7 @@ namespace Barotrauma.Networking
}
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (netServer == null) { return; }
@@ -366,7 +366,7 @@ namespace Barotrauma.Networking
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
@@ -435,7 +435,7 @@ namespace Barotrauma.Networking
{
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
bool requireSteamAuth = GameSettings.CurrentConfig.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
return;
}
string language = inc.ReadString();
LanguageIdentifier language = inc.ReadIdentifier().ToLanguageIdentifier();
pendingClient.Connection.Language = language;
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower()));
@@ -253,12 +253,14 @@ namespace Barotrauma.Networking
case ConnectionInitialization.ContentPackageOrder:
outMsg.Write(GameMain.Server.ServerName);
var mpContentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
var mpContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent).ToList();
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count);
for (int i = 0; i < mpContentPackages.Count; i++)
{
outMsg.Write(mpContentPackages[i].Name);
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
byte[] hashBytes = mpContentPackages[i].Hash.ByteRepresentation;
outMsg.WriteVariableUInt32((UInt32)hashBytes.Length);
outMsg.Write(hashBytes, 0, hashBytes.Length);
outMsg.Write(mpContentPackages[i].SteamWorkshopId);
UInt32 installTimeDiffSeconds = (UInt32)((mpContentPackages[i].InstallTime ?? DateTime.UtcNow) - DateTime.UtcNow).TotalSeconds;
outMsg.Write(installTimeDiffSeconds);
@@ -301,7 +303,7 @@ namespace Barotrauma.Networking
}
}
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
public abstract void Disconnect(NetworkConnection conn, string msg = null);
}
}
@@ -106,7 +106,7 @@ namespace Barotrauma.Networking
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif
}
@@ -223,7 +223,7 @@ namespace Barotrauma.Networking
string ownerName = inc.ReadString();
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID)
{
Language = GameMain.Config.Language
Language = GameSettings.CurrentConfig.Language
};
OwnerConnection.SetOwnerSteamIDIfUnknown(OwnerSteamID);
@@ -250,7 +250,7 @@ namespace Barotrauma.Networking
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
}
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod)
public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
{
if (!started) { return; }
@@ -263,7 +263,7 @@ namespace Barotrauma.Networking
IWriteMessage msgToSend = new WriteOnlyMessage();
byte[] msgData = new byte[16];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msg.PrepareForSending(ref msgData, compressPastThreshold, out bool isCompressed, out int length);
msgToSend.Write(conn.SteamID);
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write((byte)((isCompressed ? PacketHeader.IsCompressed : PacketHeader.None) | PacketHeader.IsServerMessage));
@@ -248,7 +248,7 @@ namespace Barotrauma.Networking
}
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
shuttleGaps.ForEach(g => Spawner.AddToRemoveQueue(g));
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == RespawnShuttle && i.GetComponent<DockingPort>() != null);
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
@@ -376,7 +376,7 @@ namespace Barotrauma.Networking
{
if (campaign?.GetClientCharacterData(c) == null || c.CharacterInfo.Job == null)
{
c.CharacterInfo.Job = new Job(c.AssignedJob.First, Rand.RandSync.Unsynced, c.AssignedJob.Second);
c.CharacterInfo.Job = new Job(c.AssignedJob.Prefab, Rand.RandSync.Unsynced, c.AssignedJob.Variant);
}
}
@@ -390,17 +390,17 @@ namespace Barotrauma.Networking
if ((shuttlePos != null && Level.Loaded.GetRealWorldDepth(shuttlePos.Value.Y) > Level.DefaultRealWorldCrushDepth) ||
Level.Loaded.GetRealWorldDepth(Submarine.MainSub.WorldPosition.Y) > Level.DefaultRealWorldCrushDepth)
{
divingSuitPrefab = ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t.Equals("respawnsuitdeep", StringComparison.OrdinalIgnoreCase)));
divingSuitPrefab = ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuitdeep"));
}
if (divingSuitPrefab == null)
{
divingSuitPrefab =
ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t.Equals("respawnsuit", StringComparison.OrdinalIgnoreCase))) ??
ItemPrefab.Find(null, "divingsuit");
ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuit")) ??
ItemPrefab.Find(null, "divingsuit".ToIdentifier());
}
ItemPrefab oxyPrefab = ItemPrefab.Find(null, "oxygentank");
ItemPrefab scooterPrefab = ItemPrefab.Find(null, "underwaterscooter");
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell");
ItemPrefab oxyPrefab = ItemPrefab.Find(null, "oxygentank".ToIdentifier());
ItemPrefab scooterPrefab = ItemPrefab.Find(null, "underwaterscooter".ToIdentifier());
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell".ToIdentifier());
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
@@ -468,11 +468,11 @@ namespace Barotrauma.Networking
if (divingSuitPrefab != null && oxyPrefab != null)
{
var divingSuit = new Item(divingSuitPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(divingSuit, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit));
respawnItems.Add(divingSuit);
var oxyTank = new Item(oxyPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(oxyTank, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank));
divingSuit.Combine(oxyTank, user: null);
respawnItems.Add(oxyTank);
}
@@ -480,10 +480,10 @@ namespace Barotrauma.Networking
if (scooterPrefab != null && batteryPrefab != null)
{
var scooter = new Item(scooterPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(scooter, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(scooter));
var battery = new Item(batteryPrefab, pos, respawnSub);
Spawner.CreateNetworkEvent(battery, false);
Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(battery));
scooter.Combine(battery, user: null);
respawnItems.Add(scooter);
@@ -543,13 +543,13 @@ namespace Barotrauma.Networking
if (characterInfo?.Job == null) { return; }
foreach (Skill skill in characterInfo.Job.Skills)
{
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier.Equals(s.Identifier, StringComparison.OrdinalIgnoreCase));
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
if (skillPrefab == null) { continue; }
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.Start, SkillReductionOnCampaignMidroundRespawn);
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length);
@@ -268,7 +268,7 @@ namespace Barotrauma.Networking
doc.Root.SetAttributeValue("HiddenSubs", string.Join(",", HiddenSubs));
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => c.First + "-" + c.Second)));
doc.Root.SetAttributeValue("AllowedClientNameChars", string.Join(",", AllowedClientNameChars.Select(c => $"{c.Start}-{c.End}")));
SerializableProperty.SerializeProperties(this, doc.Root, true);
@@ -307,13 +307,13 @@ namespace Barotrauma.Networking
if (string.IsNullOrEmpty(doc.Root.GetAttributeString("losmode", "")))
{
LosMode = GameMain.Config.LosMode;
LosMode = GameSettings.CurrentConfig.Graphics.LosMode;
}
AutoRestart = doc.Root.GetAttributeBool("autorestart", false);
Voting.AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
Voting.AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
AllowSubVoting = SubSelectionMode == SelectionMode.Vote;
AllowModeVoting = ModeSelectionMode == SelectionMode.Vote;
selectedLevelDifficulty = doc.Root.GetAttributeFloat("LevelDifficulty", 20.0f);
GameMain.NetLobbyScreen.SetLevelDifficulty(selectedLevelDifficulty);
@@ -321,11 +321,16 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetTraitorsEnabled(traitorsEnabled);
HiddenSubs.UnionWith(doc.Root.GetAttributeStringArray("HiddenSubs", Array.Empty<string>()));
if (HiddenSubs.Any())
{
UpdateFlag(NetFlags.HiddenSubs);
}
SelectedSubmarine = SelectNonHiddenSubmarine(SelectedSubmarine);
string[] defaultAllowedClientNameChars =
new string[] {
new string[]
{
"32-33",
"38-46",
"48-57",
@@ -370,7 +375,12 @@ namespace Barotrauma.Networking
}
}
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Pair<int, int>(min, max)); }
if (min > max)
{
//swap min and max
(min, max) = (max, min);
}
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Range<int>(min, max)); }
}
AllowedRandomMissionTypes = new List<MissionType>();
@@ -399,12 +409,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(BotCount);
List<string> monsterNames = CharacterPrefab.Prefabs.Select(p => p.Identifier).ToList();
MonsterEnabled = new Dictionary<string, bool>();
foreach (string s in monsterNames)
{
if (!MonsterEnabled.ContainsKey(s)) MonsterEnabled.Add(s, true);
}
MonsterEnabled ??= CharacterPrefab.Prefabs.Select(p => (p.Identifier, true)).ToDictionary();
}
public string SelectNonHiddenSubmarine(string current = null)
@@ -7,59 +7,168 @@ namespace Barotrauma
{
partial class Voting
{
public bool AllowSubVoting
public interface IVote
{
get { return allowSubVoting; }
set { allowSubVoting = value; }
}
public bool AllowModeVoting
{
get { return allowModeVoting; }
set { allowModeVoting = value; }
}
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public struct SubmarineVote
public VoteState State { get; set; }
public void Finish(Voting voting, bool passed);
}
public class SubmarineVote : IVote
{
public Client VoteStarter;
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
public SubmarineInfo Sub;
public VoteType VoteType;
public float Timer;
public int DeliveryFee;
public VoteState State;
public SubmarineVote(Client starter, SubmarineInfo subInfo, int deliveryFee, VoteType voteType)
{
Sub = subInfo;
DeliveryFee = deliveryFee;
VoteType = voteType;
State = VoteState.Started;
VoteStarter = starter;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
GameMain.Server?.SwitchSubmarine();
}
voting.StopSubmarineVote(passed);
}
}
public static SubmarineVote SubVote;
public static IVote ActiveVote;
public class TransferVote : IVote
{
public Client VoteStarter { get; }
public VoteType VoteType { get; }
public float Timer { get; set; }
public VoteState State { get; set; }
//null = bank
public readonly Client From, To;
public readonly int TransferAmount;
public TransferVote(Client starter, Client from, int transferAmount, Client to)
{
VoteStarter = starter;
From = from;
To = to;
TransferAmount = transferAmount;
State = VoteState.Started;
VoteType = VoteType.TransferMoney;
}
public void Finish(Voting voting, bool passed)
{
if (passed)
{
Wallet fromWallet = From == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : From.Character?.Wallet;
if (fromWallet.TryDeduct(TransferAmount))
{
Wallet toWallet = To == null ? (GameMain.GameSession.GameMode as MultiPlayerCampaign)?.Bank : To.Character?.Wallet;
toWallet.Give(TransferAmount);
}
}
voting.StopMoneyTransferVote(passed);
}
}
private static readonly Queue<IVote> pendingVotes = new Queue<IVote>();
private void StartSubmarineVote(SubmarineInfo subInfo, VoteType voteType, Client sender)
{
SubVote.Sub = subInfo;
SubVote.DeliveryFee = voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0;
SubVote.VoteType = voteType;
SubVote.State = VoteState.Started;
SubVote.VoteStarter = sender;
VoteRunning = true;
sender.SetVote(voteType, 2);
if (ActiveVote == null)
{
sender.SetVote(voteType, 2);
}
var subVote = new SubmarineVote(
sender,
subInfo,
voteType == VoteType.SwitchSub ? GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation) : 0,
voteType);
StartOrEnqueueVote(subVote);
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
public void StopSubmarineVote(bool passed)
{
VoteRunning = false;
SubVote.State = passed ? VoteState.Passed : VoteState.Failed;
if (!(ActiveVote is SubmarineVote)) { return; }
StopActiveVote(passed);
}
GameMain.Server.UpdateVoteStatus();
public void StopMoneyTransferVote(bool passed)
{
if (!(ActiveVote is TransferVote)) { return; }
StopActiveVote(passed);
}
public void StopActiveVote(bool passed)
{
ActiveVote.State = passed ? VoteState.Passed : VoteState.Failed;
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
GameMain.NetworkMember.SubmarineVoteYesCount = GameMain.NetworkMember.SubmarineVoteNoCount = GameMain.NetworkMember.SubmarineVoteMax = 0;
for (int i = 0; i < GameMain.NetworkMember.ConnectedClients.Count; i++)
{
GameMain.NetworkMember.ConnectedClients[i].SetVote(SubVote.VoteType, 0);
GameMain.NetworkMember.ConnectedClients[i].SetVote(ActiveVote.VoteType, 0);
}
SubVote.Sub = null;
SubVote.DeliveryFee = 0;
SubVote.VoteType = VoteType.Unknown;
SubVote.Timer = 0.0f;
SubVote.State = VoteState.None;
SubVote.VoteStarter = null;
ActiveVote = null;
if (pendingVotes.Any())
{
ActiveVote = pendingVotes.Dequeue();
ActiveVote.VoteStarter?.SetVote(ActiveVote.VoteType, 2);
}
}
public void StartTransferVote(Client starter, Client from, int transferAmount, Client to)
{
if (ActiveVote == null)
{
starter.SetVote(VoteType.TransferMoney, 2);
}
StartOrEnqueueVote(new TransferVote(starter, from, transferAmount, to));
GameMain.Server.UpdateVoteStatus(checkActiveVote: false);
}
private void StartOrEnqueueVote(IVote vote)
{
if (ActiveVote == null)
{
ActiveVote = vote;
}
else
{
pendingVotes.Enqueue(vote);
}
}
public void Update(float deltaTime)
{
if (ActiveVote == null) { return; }
ActiveVote.Timer += deltaTime;
if (ActiveVote.Timer >= GameMain.NetworkMember.ServerSettings.VoteTimeout)
{
// Do not take unanswered into account for total
int yes = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
int no = GameMain.Server.ConnectedClients.Count(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
ActiveVote.Finish(this, passed: yes / (float)(yes + no) >= GameMain.NetworkMember.ServerSettings.VoteRequiredRatio);
}
}
public void ServerRead(IReadMessage inc, Client sender)
@@ -85,7 +194,7 @@ namespace Barotrauma
string hash = equalityCheckVal > 0 ? string.Empty : inc.ReadString();
SubmarineInfo sub = equalityCheckVal > 0 ?
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.EqualityCheckVal == equalityCheckVal) :
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.MD5Hash.Hash == hash);
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == SubmarineType.Player && s.MD5Hash.StringRepresentation == hash);
sender.SetVote(voteType, sub);
break;
case VoteType.Mode:
@@ -97,10 +206,6 @@ namespace Barotrauma
case VoteType.EndRound:
if (!sender.HasSpawned) { return; }
sender.SetVote(voteType, inc.ReadBoolean());
GameMain.NetworkMember.EndVoteCount = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
GameMain.NetworkMember.EndVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
break;
case VoteType.Kick:
byte kickedClientID = inc.ReadByte();
@@ -126,24 +231,34 @@ namespace Barotrauma
case VoteType.PurchaseAndSwitchSub:
case VoteType.PurchaseSub:
case VoteType.SwitchSub:
case VoteType.TransferMoney:
bool startVote = inc.ReadBoolean();
if (startVote)
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
if (voteType == VoteType.TransferMoney)
{
StartSubmarineVote(subInfo, voteType, sender);
int amount = inc.ReadInt32();
int fromClientId = inc.ReadByte();
int toClientId = inc.ReadByte();
pendingVotes.Enqueue(new TransferVote(sender,
GameMain.Server.ConnectedClients.Find(c => c.ID == fromClientId),
amount,
GameMain.Server.ConnectedClients.Find(c => c.ID == toClientId)));
}
else
{
string subName = inc.ReadString();
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign && (campaign.CanPurchaseSub(subInfo, sender) || GameMain.GameSession.IsSubmarineOwned(subInfo)))
{
StartSubmarineVote(subInfo, voteType, sender);
}
}
}
else
{
sender.SetVote(voteType, (int)inc.ReadByte());
}
GameMain.Server.SubmarineVoteYesCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 2);
GameMain.Server.SubmarineVoteNoCount = GameMain.Server.ConnectedClients.Count(c => c.GetVote<int>(SubVote.VoteType) == 1);
GameMain.Server.SubmarineVoteMax = GameMain.Server.ConnectedClients.Count(c => c.InGame);
break;
}
@@ -154,10 +269,10 @@ namespace Barotrauma
public void ServerWrite(IWriteMessage msg)
{
if (GameMain.Server == null) return;
if (GameMain.Server == null) { return; }
msg.Write(allowSubVoting);
if (allowSubVoting)
msg.Write(GameMain.Server.ServerSettings.AllowSubVoting);
if (GameMain.Server.ServerSettings.AllowSubVoting)
{
IReadOnlyDictionary<SubmarineInfo, int> voteList = GetVoteCounts<SubmarineInfo>(VoteType.Sub, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -167,8 +282,8 @@ namespace Barotrauma
msg.Write(vote.Key.Name);
}
}
msg.Write(AllowModeVoting);
if (allowModeVoting)
msg.Write(GameMain.Server.ServerSettings.AllowModeVoting);
if (GameMain.Server.ServerSettings.AllowModeVoting)
{
IReadOnlyDictionary<GameModePreset, int> voteList = GetVoteCounts<GameModePreset>(VoteType.Mode, GameMain.Server.ConnectedClients);
msg.Write((byte)voteList.Count);
@@ -178,60 +293,78 @@ namespace Barotrauma
msg.Write(vote.Key.Identifier);
}
}
msg.Write(AllowEndVoting);
if (AllowEndVoting)
msg.Write(GameMain.Server.ServerSettings.AllowEndVoting);
if (GameMain.Server.ServerSettings.AllowEndVoting)
{
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
}
msg.Write(AllowVoteKick);
msg.Write(GameMain.Server.ServerSettings.AllowVoteKick);
msg.Write((byte)SubVote.State);
if (SubVote.State != VoteState.None)
msg.Write((byte)(ActiveVote?.State ?? VoteState.None));
if (ActiveVote != null)
{
msg.Write((byte)SubVote.VoteType);
if (SubVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 2);
msg.Write((byte)ActiveVote.VoteType);
if (ActiveVote.State != VoteState.None && ActiveVote.VoteType != VoteType.Unknown)
{
var yesClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 2);
msg.Write((byte)yesClients.Count);
foreach (Client c in yesClients)
{
msg.Write(c.ID);
}
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<int>(SubVote.VoteType) == 1);
var noClients = GameMain.Server.ConnectedClients.FindAll(c => c.InGame && c.GetVote<int>(ActiveVote.VoteType) == 1);
msg.Write((byte)noClients.Count);
foreach (Client c in noClients)
{
msg.Write(c.ID);
}
msg.Write((byte)GameMain.Server.SubmarineVoteMax);
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.InGame));
switch (SubVote.State)
switch (ActiveVote.State)
{
case VoteState.Started:
msg.Write(SubVote.Sub.Name);
msg.Write(SubVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.SubmarineVoteTimeout);
msg.Write(ActiveVote.VoteStarter.ID);
msg.Write((byte)GameMain.Server.ServerSettings.VoteTimeout);
switch (ActiveVote.VoteType)
{
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
break;
case VoteType.TransferMoney:
var transferVote = (ActiveVote as TransferVote);
msg.Write(transferVote.From?.ID ?? 0);
msg.Write(transferVote.To?.ID ?? 0);
msg.Write(transferVote.TransferAmount);
break;
}
break;
case VoteState.Running:
// Nothing specific
break;
case VoteState.Passed:
case VoteState.Failed:
msg.Write(SubVote.State == VoteState.Passed);
msg.Write(SubVote.Sub.Name);
if (SubVote.State == VoteState.Passed)
msg.Write(ActiveVote.State == VoteState.Passed);
switch (ActiveVote.VoteType)
{
msg.Write((short)SubVote.DeliveryFee);
case VoteType.PurchaseSub:
case VoteType.PurchaseAndSwitchSub:
case VoteType.SwitchSub:
msg.Write((ActiveVote as SubmarineVote).Sub.Name);
msg.Write((short)(ActiveVote as SubmarineVote).DeliveryFee);
break;
}
break;
}
}
}
}
}
var readyClients = GameMain.Server.ConnectedClients.FindAll(c => c.GetVote<bool>(VoteType.StartRound));
msg.Write((byte)readyClients.Count);
@@ -6,7 +6,7 @@ namespace Barotrauma
{
partial class PhysicsBody
{
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
public void ServerWrite(IWriteMessage msg)
{
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
@@ -5,6 +5,7 @@ using System;
using Barotrauma.IO;
using System.Linq;
using System.Text;
using Barotrauma.Networking;
#if LINUX
using System.Runtime.InteropServices;
#endif
@@ -26,6 +27,20 @@ namespace Barotrauma
private static extern void setLinuxEnv();
#endif
public static bool TryStartChildServerRelay(string[] commandLineArgs)
{
for (int i = 0; i < commandLineArgs.Length; i++)
{
switch (commandLineArgs[i].Trim())
{
case "-pipes":
ChildServerRelay.Start(commandLineArgs[i + 2], commandLineArgs[i + 1]);
return true;
}
}
return false;
}
/// <summary>
/// The main entry point for the application.
/// </summary>
@@ -36,6 +51,7 @@ namespace Barotrauma
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
#endif
TryStartChildServerRelay(args);
#if LINUX
setLinuxEnv();
@@ -66,22 +82,49 @@ namespace Barotrauma
static GameMain Game;
private static void NotifyCrash(string reportFilePath, Exception e)
{
string errorMsg = $"{reportFilePath}||\n{e.Message} ({e.GetType().Name}) {e.StackTrace}";
if (e.InnerException != null)
{
var innerMost = e.GetInnermost();
errorMsg += $"\nInner exception: {innerMost.Message} ({innerMost.GetType().Name}) {e.StackTrace}";
}
if (errorMsg.Length > ushort.MaxValue) { errorMsg = errorMsg[..ushort.MaxValue]; }
ChildServerRelay.NotifyCrash(errorMsg);
GameMain.Server?.NotifyCrash();
}
private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
{
void swallowExceptions(Action action)
{
try
{
action();
}
catch
{
//discard exceptions and keep going
}
}
string reportFilePath = "";
try
{
Game?.Exit();
CrashDump("servercrashreport.log", (Exception)args.ExceptionObject);
GameMain.Server?.NotifyCrash();
reportFilePath = "servercrashreport.log";
CrashDump(ref reportFilePath, (Exception)args.ExceptionObject);
}
catch
{
//exception handler is broken, we have a serious problem here!!
return;
//fuck
reportFilePath = "";
}
swallowExceptions(() => NotifyCrash(reportFilePath, (Exception)args.ExceptionObject));
swallowExceptions(() => Game?.Exit());
}
static void CrashDump(string filePath, Exception exception)
static void CrashDump(ref string filePath, Exception exception)
{
try
{
@@ -112,13 +155,10 @@ namespace Barotrauma
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
sb.AppendLine("\n");
sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
if (GameMain.Config != null)
sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
if (ContentPackageManager.EnabledPackages.All != null)
{
sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none"));
if (GameMain.Config.AllEnabledPackages != null)
{
sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name))));
}
sb.AppendLine("Selected content packages: " + (!ContentPackageManager.EnabledPackages.All.Any() ? "None" : string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => c.Name))));
}
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
@@ -180,7 +220,8 @@ namespace Barotrauma
File.WriteAllText(filePath, sb.ToString());
if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); }
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
|| GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
if (GameAnalyticsManager.SendUserStatistics)
{
@@ -54,14 +54,14 @@ namespace Barotrauma
}
}
public string SelectedModeIdentifier
public Identifier SelectedModeIdentifier
{
get { return GameModes[SelectedModeIndex].Identifier; }
set
{
for (int i = 0; i < GameModes.Length; i++)
{
if (GameModes[i].Identifier.ToLower() == value.ToLower())
if (GameModes[i].Identifier == value)
{
SelectedModeIndex = i;
break;
@@ -197,7 +197,7 @@ namespace Barotrauma
public override void Select()
{
base.Select();
GameMain.Server.ServerSettings.Voting.ResetVotes(GameMain.Server.ConnectedClients);
GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients);
if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this)
{
GameMain.GameSession = null;
@@ -4,13 +4,11 @@ namespace Barotrauma.Steam
{
partial class SteamManager
{
#region Server
private static void InitializeProjectSpecific() { isInitialized = true; }
private static void InitializeProjectSpecific() { IsInitialized = true; }
public static bool CreateServer(Networking.GameServer server, bool isPublic)
{
isInitialized = true;
IsInitialized = true;
Steamworks.SteamServerInit options = new Steamworks.SteamServerInit("Barotrauma", "Barotrauma")
{
@@ -40,26 +38,26 @@ namespace Barotrauma.Steam
public static bool RefreshServerDetails(Networking.GameServer server)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid)
if (!IsInitialized || !Steamworks.SteamServer.IsValid)
{
return false;
}
var contentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
var contentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent);
// These server state variables may be changed at any time. Note that there is no longer a mechanism
// to send the player count. The player count is maintained by steam and you should use the player
// to send the player count. The player count is maintained by Steam and you should use the player
// creation/authentication functions to maintain your player count.
Steamworks.SteamServer.ServerName = server.ServerName;
Steamworks.SteamServer.MaxPlayers = server.ServerSettings.MaxPlayers;
Steamworks.SteamServer.Passworded = server.ServerSettings.HasPassword;
Steamworks.SteamServer.MapName = GameMain.NetLobbyScreen?.SelectedSub?.DisplayName ?? "";
Steamworks.SteamServer.MapName = GameMain.NetLobbyScreen?.SelectedSub?.DisplayName?.Value ?? "";
Steamworks.SteamServer.SetKey("haspassword", server.ServerSettings.HasPassword.ToString());
Steamworks.SteamServer.SetKey("message", GameMain.Server.ServerSettings.ServerMessageText);
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
Steamworks.SteamServer.SetKey("playercount", GameMain.Server.ConnectedClients.Count.ToString());
Steamworks.SteamServer.SetKey("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash)));
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
Steamworks.SteamServer.SetKey("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopId)));
Steamworks.SteamServer.SetKey("usingwhitelist", (server.ServerSettings.Whitelist != null && server.ServerSettings.Whitelist.Enabled).ToString());
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
@@ -69,7 +67,7 @@ namespace Barotrauma.Steam
Steamworks.SteamServer.SetKey("allowrespawn", server.ServerSettings.AllowRespawn.ToString());
Steamworks.SteamServer.SetKey("traitors", server.ServerSettings.TraitorsEnabled.ToString());
Steamworks.SteamServer.SetKey("gamestarted", server.GameStarted.ToString());
Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier);
Steamworks.SteamServer.SetKey("gamemode", server.ServerSettings.GameModeIdentifier.Value);
Steamworks.SteamServer.SetKey("playstyle", server.ServerSettings.PlayStyle.ToString());
Steamworks.SteamServer.DedicatedServer = true;
@@ -79,7 +77,7 @@ namespace Barotrauma.Steam
public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return Steamworks.BeginAuthResult.ServerNotConnectedToSteam;
if (!IsInitialized || !Steamworks.SteamServer.IsValid) return Steamworks.BeginAuthResult.ServerNotConnectedToSteam;
DebugConsole.Log("SteamManager authenticating Steam client " + clientSteamID);
Steamworks.BeginAuthResult startResult = Steamworks.SteamServer.BeginAuthSession(authTicketData, clientSteamID);
@@ -93,7 +91,7 @@ namespace Barotrauma.Steam
public static void StopAuthSession(ulong clientSteamID)
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return;
if (!IsInitialized || !Steamworks.SteamServer.IsValid) return;
DebugConsole.Log("SteamManager ending auth session with Steam client " + clientSteamID);
Steamworks.SteamServer.EndSession(clientSteamID);
@@ -101,13 +99,11 @@ namespace Barotrauma.Steam
public static bool CloseServer()
{
if (!isInitialized || !Steamworks.SteamServer.IsValid) return false;
if (!IsInitialized || !Steamworks.SteamServer.IsValid) return false;
Steamworks.SteamServer.Shutdown();
return true;
}
#endregion
}
}
@@ -29,7 +29,8 @@ namespace Barotrauma
public virtual IEnumerable<string> CompletedTextKeys => new string[] { };
public virtual IEnumerable<string> CompletedTextValues(Traitor traitor) => new string[] { };
protected virtual string FormatText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => TextManager.FormatServerMessageWithGenderPronouns(traitor?.Character?.Info?.Gender ?? Gender.None, textId, keys, values);
protected virtual string FormatText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
=> TextManager.FormatServerMessageWithPronouns(traitor.Character.Info, textId, keys.Zip(values, (k,v) => (k,v)).ToArray());
protected internal virtual string GetStatusText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
protected internal virtual string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
@@ -51,11 +51,11 @@ namespace Barotrauma
{
continue;
}
var identifierMatches = matchIdentifier && item.prefab.Identifier == tag;
var identifierMatches = matchIdentifier && ((MapEntity)item).Prefab.Identifier == tag;
if (identifierMatches && tagPrefabName == null)
{
var textId = item.Prefab.GetItemNameTextId();
tagPrefabName = textId != null ? TextManager.FormatServerMessage(textId) : item.Prefab.Name;
tagPrefabName = textId != null ? TextManager.FormatServerMessage(textId) : item.Prefab.Name.Value;
}
if (identifierMatches || (matchTag && item.HasTag(tag)))
{
@@ -28,7 +28,7 @@ namespace Barotrauma
private enum EntityTypes { Character, Item }
private string[] entities;
private Identifier[] entities;
private EntityTypes[] entityTypes;
public override void Update(float deltaTime)
@@ -67,7 +67,7 @@ namespace Barotrauma
{
continue;
}
if (character.SpeciesName.Equals(entities[activeEntityIndex], StringComparison.OrdinalIgnoreCase) && Vector2.Distance(activeEntitySavedPosition, character.WorldPosition) < graceDistance)
if (character.SpeciesName == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, character.WorldPosition) < graceDistance)
{
activeEntity = character;
transformationTime = 0.0;
@@ -82,7 +82,7 @@ namespace Barotrauma
{
continue;
}
if (item.prefab.Identifier == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, item.WorldPosition) < graceDistance)
if (((MapEntity)item).Prefab.Identifier == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, item.WorldPosition) < graceDistance)
{
activeEntity = item;
transformationTime = 0.0;
@@ -117,7 +117,7 @@ namespace Barotrauma
{
continue;
}
if (character.SpeciesName.Equals(entities[activeEntityIndex], StringComparison.OrdinalIgnoreCase))
if (character.SpeciesName == entities[activeEntityIndex])
{
activeEntity = character;
break;
@@ -131,7 +131,7 @@ namespace Barotrauma
{
continue;
}
if (item.prefab.Identifier.Equals(entities[0], StringComparison.OrdinalIgnoreCase))
if (((MapEntity)item).Prefab.Identifier == entities[0])
{
activeEntity = item;
break;
@@ -146,7 +146,7 @@ namespace Barotrauma
public GoalEntityTransformation(string[] entities, string[] entityTypes, string catalystItemIdentifier) : base()
{
this.entities = entities;
this.entities = entities.ToIdentifiers().ToArray();
this.entityTypes = new EntityTypes[entityTypes.Length];
@@ -15,7 +15,7 @@ namespace Barotrauma
private readonly bool preferNew;
private readonly bool allowNew;
private readonly bool allowExisting;
private readonly HashSet<string> allowedContainerIdentifiers = new HashSet<string>();
private readonly HashSet<Identifier> allowedContainerIdentifiers = new HashSet<Identifier>();
private ItemPrefab targetPrefab;
private ItemPrefab containedPrefab;
@@ -83,7 +83,7 @@ namespace Barotrauma
{
continue;
}
if (item.GetComponent<ItemContainer>() != null && allowedContainerIdentifiers.Contains(item.prefab.Identifier))
if (item.GetComponent<ItemContainer>() != null && allowedContainerIdentifiers.Contains(((MapEntity)item).Prefab.Identifier))
{
if ((includeNew && !item.OwnInventory.IsFull()) || (includeExisting && item.OwnInventory.FindItemByIdentifier(targetPrefabCandidate.Identifier) != null))
{
@@ -166,7 +166,7 @@ namespace Barotrauma
targetPrefabTextId = targetPrefab.GetItemNameTextId();
}
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name;
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name.Value;
targetContainer = FindTargetContainer(Traitors, targetPrefab);
if (targetContainer == null)
{
@@ -175,9 +175,9 @@ namespace Barotrauma
return false;
}
var containerPrefabTextId = targetContainer.Prefab.GetItemNameTextId();
targetContainerNameText = containerPrefabTextId != null ? TextManager.FormatServerMessage(containerPrefabTextId) : targetContainer.Prefab.Name;
var targetHullTextId = targetContainer.CurrentHull?.prefab.GetHullNameTextId();
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName ?? "";
targetContainerNameText = containerPrefabTextId != null ? TextManager.FormatServerMessage(containerPrefabTextId) : targetContainer.Prefab.Name.Value;
var targetHullTextId = targetContainer.CurrentHull?.Prefab.GetHullNameTextId();
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName.Value ?? "";
if (allowNew && !targetContainer.OwnInventory.IsFull())
{
existingItems.Clear();
@@ -185,7 +185,7 @@ namespace Barotrauma
{
existingItems.Add(item);
}
Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory, onSpawned: item =>
Entity.Spawner.AddItemToSpawnQueue(targetPrefab, targetContainer.OwnInventory, onSpawned: item =>
{
item.AddTag("traitormissionitem");
});
@@ -216,7 +216,7 @@ namespace Barotrauma
{
for (int i = 0; i < spawnAmount; i++)
{
Entity.Spawner.AddToSpawnQueue(containedPrefab, target.OwnInventory);
Entity.Spawner.AddItemToSpawnQueue(containedPrefab, target.OwnInventory);
}
}
existingItems.Clear();
@@ -224,7 +224,7 @@ namespace Barotrauma
}
}
public GoalFindItem(TraitorMission.CharacterFilter filter, string identifier, bool preferNew, bool allowNew, bool allowExisting, float percentage, params string[] allowedContainerIdentifiers)
public GoalFindItem(TraitorMission.CharacterFilter filter, string identifier, bool preferNew, bool allowNew, bool allowExisting, float percentage, params Identifier[] allowedContainerIdentifiers)
{
this.filter = filter;
this.identifier = identifier;
@@ -21,7 +21,7 @@ namespace Barotrauma
base.Update(deltaTime);
var validHullsCount = 0;
var floodingAmount = 0.0f;
foreach (Hull hull in Hull.hullList)
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine == null || hull.Submarine.Info.IsOutpost || Traitors.All(traitor => hull.Submarine.TeamID != traitor.Character.TeamID)) { continue; }
if (hull.Submarine == GameMain.Server?.RespawnManager?.RespawnShuttle) { continue; }
@@ -15,7 +15,7 @@ namespace Barotrauma
private bool isCompleted;
private const float gracePeriod = 1f;
private string speciesId;
private Identifier speciesId;
private string targetCharacterName;
private Character targetCharacter;
private float timer;
@@ -52,7 +52,7 @@ namespace Barotrauma
{
continue;
}
if (character.SpeciesName.Equals(speciesId, StringComparison.OrdinalIgnoreCase))
if (character.SpeciesName == speciesId)
{
targetCharacter = character;
break;
@@ -64,9 +64,9 @@ namespace Barotrauma
return targetCharacter != null;
}
public GoalKeepTransformedAlive(string speciesId) : base()
public GoalKeepTransformedAlive(Identifier speciesId) : base()
{
this.speciesId = speciesId.ToLowerInvariant();
this.speciesId = speciesId;
}
}
}
@@ -13,12 +13,12 @@ namespace Barotrauma
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[causeofdeath]", "[targethullname]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[]
{ traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", GetCauseOfDeath(), targetHull != null ? TextManager.Get($"roomname.{targetHull}") : string.Empty });
{ traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", GetCauseOfDeath().Value, targetHull != null ? TextManager.Get($"roomname.{targetHull}").Value : string.Empty });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
private CauseOfDeathType requiredCauseOfDeath;
private string afflictionId;
@@ -102,7 +102,7 @@ namespace Barotrauma
return true;
}
private string GetCauseOfDeath()
private LocalizedString GetCauseOfDeath()
{
if (requiredCauseOfDeath != CauseOfDeathType.Affliction || afflictionId == string.Empty)
{
@@ -8,8 +8,8 @@ namespace Barotrauma
{
public class GoalReplaceInventory : HumanoidGoal
{
private readonly HashSet<string> sabotageContainerIds = new HashSet<string>();
private readonly HashSet<string> validReplacementIds = new HashSet<string>();
private readonly HashSet<Identifier> sabotageContainerIds = new HashSet<Identifier>();
private readonly HashSet<Identifier> validReplacementIds = new HashSet<Identifier>();
private readonly float replaceAmount;
@@ -33,7 +33,7 @@ namespace Barotrauma
{
continue;
}
if (sabotageContainerIds.Contains(item.prefab.Identifier))
if (sabotageContainerIds.Contains(((MapEntity)item).Prefab.Identifier))
{
++totalAmount;
if (item.OwnInventory.AllItems.All(containedItem => !validReplacementIds.Contains(containedItem.Prefab.Identifier)))
@@ -59,7 +59,7 @@ namespace Barotrauma
return true;
}
public GoalReplaceInventory(string[] containerIds, string[] replacementIds, float replaceAmount)
public GoalReplaceInventory(Identifier[] containerIds, Identifier[] replacementIds, float replaceAmount)
{
sabotageContainerIds.UnionWith(containerIds);
validReplacementIds.UnionWith(replacementIds);
@@ -37,15 +37,10 @@ namespace Barotrauma
targetItems.Add(item);
}
}
//only target items in the main sub if there are any
if (targetItems.Count > 1 && targetItems.Any(it => it.Submarine == Submarine.MainSub))
{
targetItems.RemoveAll(it => it.Submarine != Submarine.MainSub);
}
if (targetItems.Count > 0)
{
var textId = targetItems[0].Prefab.GetItemNameTextId();
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetItems[0].Prefab.Name;
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetItems[0].Prefab.Name.Value;
}
return targetItems.Count > 0;
}
@@ -46,7 +46,7 @@ namespace Barotrauma
if (targetConnectionPanels.Count > 0)
{
var textId = targetConnectionPanels[0].Item.Prefab.GetItemNameTextId();
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetConnectionPanels[0].Item.Prefab.Name;
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetConnectionPanels[0].Item.Prefab.Name.Value;
}
return targetConnectionPanels.Count > 0;
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Barotrauma
@@ -14,12 +15,13 @@ namespace Barotrauma
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[duration]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { requiredDuration.ToString() });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { requiredDuration.ToString(CultureInfo.InvariantCulture) });
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(durationInfoTextId) && !infoText.Contains("[duration]") ? TextManager.FormatServerMessage(durationInfoTextId, new[] { "[infotext]", "[duration]" }, new[] { infoText, requiredDuration.ToString() }) : infoText;
return !string.IsNullOrEmpty(durationInfoTextId) && !infoText.Contains("[duration]") ? TextManager.FormatServerMessage(durationInfoTextId,
("[infotext]", infoText), ("[duration]", requiredDuration.ToString(CultureInfo.InvariantCulture))) : infoText;
}
private bool isCompleted = false;
@@ -17,7 +17,7 @@ namespace Barotrauma
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(timeLimitInfoTextId) ? TextManager.FormatServerMessage(timeLimitInfoTextId, new[] { "[infotext]", "[timelimit]" }, new[] { infoText, $"{TimeSpan.FromSeconds(timeLimit):g}" }) : infoText;
return !string.IsNullOrEmpty(timeLimitInfoTextId) ? TextManager.FormatServerMessage(timeLimitInfoTextId, ("[infotext]", infoText), ("[timelimit]", $"{TimeSpan.FromSeconds(timeLimit):g}")) : infoText;
}
public override bool CanBeCompleted(ICollection<Traitor> traitors) => base.CanBeCompleted(traitors) && (!Traitors.Any(IsStarted) || timeRemaining > 0.0f);
@@ -14,7 +14,7 @@ namespace Barotrauma
public override IEnumerable<string> StatusTextValues(Traitor traitor)
{
var values = base.StatusTextValues(traitor).ToArray();
values[1] = TextManager.GetServerMessage(StatusValueTextId);
values[1] = TextManager.FormatServerMessage(StatusValueTextId);
return values;
}
@@ -24,7 +24,7 @@ namespace Barotrauma
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(optionalInfoTextId) ? TextManager.FormatServerMessage(optionalInfoTextId, new[] { "[infotext]" }, new[] { infoText }) : infoText;
return !string.IsNullOrEmpty(optionalInfoTextId) ? TextManager.FormatServerMessage(optionalInfoTextId, ("[infotext]", infoText)) : infoText;
}
public GoalIsOptional(Goal goal, string optionalInfoTextId) : base(goal)
@@ -30,7 +30,7 @@ namespace Barotrauma
}
public override IEnumerable<string> StatusTextKeys => Goal.StatusTextKeys;
public override IEnumerable<string> StatusTextValues(Traitor traitor) => new [] { InfoText(traitor), TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> StatusTextValues(Traitor traitor) => new string[] { InfoText(traitor), TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> InfoTextKeys => Goal.InfoTextKeys;
public override IEnumerable<string> InfoTextValues(Traitor traitor) => Goal.InfoTextValues(traitor);
@@ -39,7 +39,7 @@ namespace Barotrauma
{
var statusText = goal.StatusText(Traitor);
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, ("[statustext]", $"[{index}.st]"))}";
}).ToArray()),
string.Join("", activeGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
@@ -49,7 +49,7 @@ namespace Barotrauma
{
var statusText = goal.StatusText(Traitor);
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, ("[statustext]", $"[{index}.st]"))}";
}).ToArray()),
string.Join("", allGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
@@ -57,13 +57,14 @@ namespace Barotrauma
public virtual IEnumerable<string> StartMessageKeys => new string[] { "[traitorgoalinfos]" };
public virtual IEnumerable<string> StartMessageValues => new string[] { GoalInfos };
public virtual string StartMessageText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageTextId, StartMessageKeys, StartMessageValues);
public virtual LocalizedString StartMessageText
=> TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, StartMessageTextId, StartMessageKeys.Zip(StartMessageValues, (k,v) => (k,v)).ToArray());
public virtual string StartMessageServerTextId { get; set; } = "TraitorObjectiveStartMessageServer";
public virtual IEnumerable<string> StartMessageServerKeys => StartMessageKeys.Concat(new string[] { "[traitorname]" });
public virtual IEnumerable<string> StartMessageServerValues => StartMessageValues.Concat(new string[] { Traitor?.Character?.Name ?? "(unknown)" });
public virtual string StartMessageServerText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageServerTextId, StartMessageServerKeys, StartMessageServerValues);
public virtual LocalizedString StartMessageServerText => TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, StartMessageServerTextId, StartMessageServerKeys.Zip(StartMessageServerValues, (k,v) => (k,v)).ToArray());
public virtual string EndMessageSuccessTextId { get; set; } = "TraitorObjectiveEndMessageSuccess";
public virtual string EndMessageSuccessDeadTextId { get; set; } = "TraitorObjectiveEndMessageSuccessDead";
@@ -83,7 +84,7 @@ namespace Barotrauma
var messageId = IsCompleted
? (traitorIsDead ? EndMessageSuccessDeadTextId : traitorIsDetained ? EndMessageSuccessDetainedTextId : EndMessageSuccessTextId)
: (traitorIsDead ? EndMessageFailureDeadTextId : traitorIsDetained ? EndMessageFailureDetainedTextId : EndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, messageId, EndMessageKeys.ToArray(), EndMessageValues.ToArray());
return TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, messageId, EndMessageKeys.Zip(EndMessageValues, (k,v)=>(k,v)).ToArray());
}
}
@@ -133,21 +134,21 @@ namespace Barotrauma
IsStarted = true;
traitor.SendChatMessageBox(StartMessageText, traitor.Mission?.Identifier);
traitor.UpdateCurrentObjective(GoalInfos, traitor.Mission?.Identifier);
traitor.SendChatMessageBox(StartMessageText.Value, traitor.Mission.Identifier);
traitor.UpdateCurrentObjective(GoalInfos, traitor.Mission.Identifier);
return true;
}
public void StartMessage()
{
Traitor.SendChatMessage(StartMessageText, Traitor.Mission?.Identifier);
Traitor.SendChatMessage(StartMessageText.Value, Traitor.Mission.Identifier);
}
public void EndMessage()
{
Traitor.SendChatMessageBox(EndMessageText, Traitor.Mission?.Identifier);
Traitor.SendChatMessage(EndMessageText, Traitor.Mission?.Identifier);
Traitor.SendChatMessageBox(EndMessageText, Traitor.Mission.Identifier);
Traitor.SendChatMessage(EndMessageText, Traitor.Mission.Identifier);
}
public void Update(float deltaTime)
@@ -170,12 +171,12 @@ namespace Barotrauma
pendingGoals.RemoveAt(i);
if (GameMain.Server != null)
{
Traitor.SendChatMessage(goal.CompletedText(Traitor), Traitor.Mission?.Identifier);
Traitor.SendChatMessage(goal.CompletedText(Traitor), Traitor.Mission.Identifier);
if (pendingGoals.Count > 0)
{
Traitor.SendChatMessageBox(goal.CompletedText(Traitor), Traitor.Mission?.Identifier);
Traitor.SendChatMessageBox(goal.CompletedText(Traitor), Traitor.Mission.Identifier);
}
Traitor.UpdateCurrentObjective(GoalInfos, Traitor.Mission?.Identifier);
Traitor.UpdateCurrentObjective(GoalInfos, Traitor.Mission.Identifier);
}
}
}
@@ -16,43 +16,41 @@ namespace Barotrauma
Role = role;
Character = character;
Character.IsTraitor = true;
GameMain.NetworkMember.CreateEntityEvent(Character, new object[] { NetEntityEvent.Type.Status });
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.CharacterStatusEventData());
}
public delegate void MessageSender(string message);
public void Greet(GameServer server, string codeWords, string codeResponse, MessageSender messageSender)
{
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText, new string[] {
"[codewords]", "[coderesponse]"
}, new string[] {
codeWords, codeResponse
});
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText,
("[codewords]", codeWords),
("[coderesponse]", codeResponse));
messageSender(greetingMessage);
Client traitorClient = server.ConnectedClients.Find(c => c.Character == Character);
Client ownerClient = server.ConnectedClients.Find(c => c.Connection == server.OwnerConnection);
if (traitorClient != ownerClient && ownerClient != null && ownerClient.Character == null)
{
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText, Mission?.Identifier, TraitorMessageType.ServerMessageBox);
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText.Value, Mission.Identifier, TraitorMessageType.ServerMessageBox);
}
}
public void SendChatMessage(string serverText, string iconIdentifier)
public void SendChatMessage(string serverText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.Server);
}
public void SendChatMessageBox(string serverText, string iconIdentifier)
public void SendChatMessageBox(string serverText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.ServerMessageBox);
}
public void UpdateCurrentObjective(string objectiveText, string iconIdentifier)
public void UpdateCurrentObjective(string objectiveText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
Character.TraitorCurrentObjective = objectiveText;
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective, iconIdentifier, TraitorMessageType.Objective);
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective.Value, iconIdentifier, TraitorMessageType.Objective);
}
}
}
@@ -169,7 +169,8 @@ namespace Barotrauma
else
{
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
if (mission != null) {
if (mission != null)
{
if (mission.CanBeStarted(server, this, CharacterTeamType.None))
{
if (mission.Start(server, this, CharacterTeamType.None))
@@ -43,7 +43,7 @@ namespace Barotrauma
public string GlobalEndMessageFailureDeadTextId { get; private set; }
public string GlobalEndMessageFailureDetainedTextId { get; private set; }
public readonly string Identifier;
public readonly Identifier Identifier;
public virtual IEnumerable<string> GlobalEndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> GlobalEndMessageValues {
@@ -60,7 +60,7 @@ namespace Barotrauma
{
get
{
if (Traitors.Any() && allObjectives.Count > 0)
if (Traitors.Any() && allObjectives.Count > 0)
{
return TextManager.JoinServerMessages("\n",
Traitors.Values.Select(traitor =>
@@ -71,7 +71,7 @@ namespace Barotrauma
var messageId = isSuccess
? (traitorIsDead ? GlobalEndMessageSuccessDeadTextId : traitorIsDetained ? GlobalEndMessageSuccessDetainedTextId : GlobalEndMessageSuccessTextId)
: (traitorIsDead ? GlobalEndMessageFailureDeadTextId : traitorIsDetained ? GlobalEndMessageFailureDetainedTextId : GlobalEndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(traitor.Character?.Info?.Gender ?? Gender.None, messageId, GlobalEndMessageKeys.ToArray(), GlobalEndMessageValues.ToArray());
return TextManager.FormatServerMessageWithPronouns(traitor.Character.Info, messageId, GlobalEndMessageKeys.Zip(GlobalEndMessageValues).ToArray());
}).ToArray());
}
return "";
@@ -384,7 +384,7 @@ namespace Barotrauma
}
}
public TraitorMission(string identifier, string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, IEnumerable<KeyValuePair<string, RoleFilter>> roles, ICollection<Objective> objectives)
public TraitorMission(Identifier identifier, string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, IEnumerable<KeyValuePair<string, RoleFilter>> roles, ICollection<Objective> objectives)
{
Identifier = identifier;
StartText = startText;
@@ -9,38 +9,27 @@ namespace Barotrauma
{
class TraitorMissionPrefab
{
public class TraitorMissionEntry
public class TraitorMissionEntry : Prefab
{
public static PrefabCollection<TraitorMissionEntry> Prefabs => TraitorMissionPrefab.Prefabs;
public readonly TraitorMissionPrefab Prefab;
public float SelectedWeight;
public TraitorMissionEntry(XElement element)
public TraitorMissionEntry(ContentXElement element, TraitorMissionsFile file) : base(file, element)
{
Prefab = new TraitorMissionPrefab(element);
}
}
public static readonly List<TraitorMissionEntry> List = new List<TraitorMissionEntry>();
public static void Init()
{
var files = GameMain.Instance.GetFilesOfType(ContentType.TraitorMissions);
foreach (ContentFile file in files)
{
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
if (doc?.Root == null) continue;
foreach (XElement element in doc.Root.Elements())
{
List.Add(new TraitorMissionEntry(element));
}
}
public override void Dispose() { }
}
public static readonly PrefabCollection<TraitorMissionEntry> Prefabs = new PrefabCollection<TraitorMissionEntry>();
public static TraitorMissionPrefab RandomPrefab()
{
var selected = ToolBox.SelectWeightedRandom(List, List.Select(mission => Math.Max(mission.SelectedWeight, 0.1f)).ToList(), TraitorManager.Random);
var selected = ToolBox.SelectWeightedRandom(Prefabs.ToList(), Prefabs.Select(mission => Math.Max(mission.SelectedWeight, 0.1f)).ToList(), TraitorManager.Random);
//the weight of the missions that didn't get selected keeps growing the make them more likely to get picked
foreach (var mission in List)
foreach (var mission in Prefabs)
{
mission.SelectedWeight += 10;
}
@@ -103,7 +92,7 @@ namespace Barotrauma
private delegate bool TargetFilter(string value, Character character);
private static Dictionary<string, TargetFilter> targetFilters = new Dictionary<string, TargetFilter>()
{
{ "job", (value, character) => value.Equals(character.Info.Job.Prefab.Identifier, StringComparison.OrdinalIgnoreCase) },
{ "job", (value, character) => value == character.Info.Job.Prefab.Identifier },
{ "role", (value, character) => value.Equals(GameMain.Server.TraitorManager.GetTraitorRole(character), StringComparison.OrdinalIgnoreCase) }
};
@@ -180,12 +169,29 @@ namespace Barotrauma
itemCountFilters.Add((character) => filter(attribute.Value, character));
}
}
goal = new Traitor.GoalFindItem((character) => itemCountFilters.All(f => f(character)), Config.GetAttributeString("identifier", null), Config.GetAttributeBool("preferNew", true), Config.GetAttributeBool("allowNew", true), Config.GetAttributeBool("allowExisting", true), Config.GetAttributeFloat("percentage", -1f), Config.GetAttributeStringArray("allowedContainers", new string[] {"steelcabinet", "mediumsteelcabinet", "suppliescabinet"}));
goal = new Traitor.GoalFindItem((character) => itemCountFilters.All(f => f(character)),
Config.GetAttributeString("identifier",
null),
Config.GetAttributeBool("preferNew",
true),
Config.GetAttributeBool("allowNew",
true),
Config.GetAttributeBool("allowExisting",
true),
Config.GetAttributeFloat("percentage",
-1f),
Config.GetAttributeIdentifierArray("allowedContainers",
new string[]
{
"steelcabinet",
"mediumsteelcabinet",
"suppliescabinet"
}.ToIdentifiers()));
break;
case "replaceinventory":
checker.Required("containers", "replacements");
checker.Optional("percentage");
goal = new Traitor.GoalReplaceInventory(Config.GetAttributeStringArray("containers", new string[] { }), Config.GetAttributeStringArray("replacements", new string[] { }), Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
goal = new Traitor.GoalReplaceInventory(Config.GetAttributeIdentifierArray("containers", new Identifier[] { }), Config.GetAttributeIdentifierArray("replacements", new Identifier[] { }), Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
break;
case "reachdistancefromsub":
checker.Optional("distance");
@@ -221,7 +227,7 @@ namespace Barotrauma
break;
case "keeptransformedalive":
checker.Required("speciesname");
goal = new Traitor.GoalKeepTransformedAlive(Config.GetAttributeString("speciesname", null));
goal = new Traitor.GoalKeepTransformedAlive(Config.GetAttributeIdentifier("speciesname", Identifier.Empty));
break;
default:
GameServer.Log($"Unrecognized goal type \"{goalType}\".", ServerLog.MessageType.Error);
@@ -425,7 +431,7 @@ namespace Barotrauma
}
public readonly Dictionary<string, Role> Roles = new Dictionary<string, Role>();
public readonly string Identifier;
public readonly Identifier Identifier;
public readonly string StartText;
public readonly string EndMessageSuccessText;
public readonly string EndMessageSuccessDeadText;
@@ -575,14 +581,14 @@ namespace Barotrauma
if (jobs != null)
{
var jobsSet = new HashSet<string>(jobs.Select(job => job.ToLower(CultureInfo.InvariantCulture)));
filters.Add(character => character.Info?.Job != null && jobsSet.Contains(character.Info.Job.Name.ToLower(CultureInfo.InvariantCulture)));
filters.Add(character => character.Info?.Job != null && jobsSet.Contains(character.Info.Job.Name.ToLower().Value));
}
return new Role(filters);
}
public TraitorMissionPrefab(XElement missionRoot)
public TraitorMissionPrefab(ContentXElement missionRoot)
{
Identifier = missionRoot.GetAttributeString("identifier", null);
Identifier = missionRoot.GetAttributeIdentifier("identifier", Identifier.Empty);
foreach (var element in missionRoot.Elements())
{
using (var checker = new AttributeChecker(element))