Unstable 0.17.1.0

This commit is contained in:
Markus Isberg
2022-03-17 01:25:04 +09:00
parent 3974067915
commit 6d410cc1b7
302 changed files with 5878 additions and 3317 deletions
@@ -42,7 +42,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
IsAlive = msg.ReadBoolean();
}
@@ -112,395 +112,395 @@ namespace Barotrauma
}
}
public virtual void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientWriteInput(IWriteMessage msg)
{
if (extraData != null)
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
if (memInput.Count > 60)
{
switch ((NetEntityEvent.Type)extraData[0])
memInput.RemoveRange(60, memInput.Count - 60);
}
msg.Write(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
memInput[i].states.HasFlag(InputNetFlags.Grab))
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 0, 4);
Inventory.ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.Treatment:
msg.WriteRangedInteger(1, 0, 4);
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, 0, 4);
break;
case NetEntityEvent.Type.UpdateTalents:
msg.WriteRangedInteger(3, 0, 4);
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.Prefab.UintIdentifier);
}
break;
msg.Write(memInput[i].interact);
}
}
}
public virtual void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); }
msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData)
{
case InventoryStateEventData inventoryStateEventData:
Inventory.ClientEventWrite(msg, inventoryStateEventData);
break;
case TreatmentEventData _:
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break;
case StatusEventData _:
//do nothing
break;
case UpdateTalentsEventData _:
msg.Write((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents)
{
msg.Write(unlockedTalent.Prefab.UintIdentifier);
}
break;
default:
throw new Exception($"Malformed character event: did not expect {eventData.GetType().Name}");
}
}
public void ClientReadPosition(IReadMessage msg, float sendingTime)
{
bool facingRight = AnimController.Dir > 0.0f;
lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
AnimController.Frozen = false;
Enabled = true;
UInt16 networkUpdateID = 0;
if (msg.ReadBoolean())
{
networkUpdateID = msg.ReadUInt16();
}
else
{
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
bool aimInput = msg.ReadBoolean();
keys[(int)InputType.Aim].Held = aimInput;
keys[(int)InputType.Aim].SetState(false, aimInput);
if (memInput.Count > 60)
bool shootInput = msg.ReadBoolean();
keys[(int)InputType.Shoot].Held = shootInput;
keys[(int)InputType.Shoot].SetState(false, shootInput);
bool useInput = msg.ReadBoolean();
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
if (AnimController is HumanoidAnimController)
{
memInput.RemoveRange(60, memInput.Count - 60);
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
msg.Write(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
memInput[i].states.HasFlag(InputNetFlags.Grab))
{
msg.Write(memInput[i].interact);
}
}
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
keys[(int)InputType.Attack].SetState(false, attackInput);
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
TransformCursorPos();
bool ragdollInput = msg.ReadBoolean();
keys[(int)InputType.Ragdoll].Held = ragdollInput;
keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);
facingRight = msg.ReadBoolean();
}
msg.WritePadBits();
}
public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
switch (type)
bool entitySelected = msg.ReadBoolean();
Character selectedCharacter = null;
Item selectedItem = null;
AnimController.Animation animation = AnimController.Animation.None;
if (entitySelected)
{
case ServerNetObject.ENTITY_POSITION:
bool facingRight = AnimController.Dir > 0.0f;
lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
AnimController.Frozen = false;
Enabled = true;
UInt16 networkUpdateID = 0;
if (msg.ReadBoolean())
ushort characterID = msg.ReadUInt16();
ushort itemID = msg.ReadUInt16();
selectedCharacter = FindEntityByID(characterID) as Character;
selectedItem = FindEntityByID(itemID) as Item;
if (characterID != NullEntityID)
{
bool doingCpr = msg.ReadBoolean();
if (doingCpr && SelectedCharacter != null)
{
networkUpdateID = msg.ReadUInt16();
animation = AnimController.Animation.CPR;
}
else
{
bool aimInput = msg.ReadBoolean();
keys[(int)InputType.Aim].Held = aimInput;
keys[(int)InputType.Aim].SetState(false, aimInput);
bool shootInput = msg.ReadBoolean();
keys[(int)InputType.Shoot].Held = shootInput;
keys[(int)InputType.Shoot].SetState(false, shootInput);
bool useInput = msg.ReadBoolean();
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
if (AnimController is HumanoidAnimController)
{
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
keys[(int)InputType.Attack].SetState(false, attackInput);
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
TransformCursorPos();
bool ragdollInput = msg.ReadBoolean();
keys[(int)InputType.Ragdoll].Held = ragdollInput;
keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);
facingRight = msg.ReadBoolean();
}
bool entitySelected = msg.ReadBoolean();
Character selectedCharacter = null;
Item selectedItem = null;
AnimController.Animation animation = AnimController.Animation.None;
if (entitySelected)
{
ushort characterID = msg.ReadUInt16();
ushort itemID = msg.ReadUInt16();
selectedCharacter = FindEntityByID(characterID) as Character;
selectedItem = FindEntityByID(itemID) as Item;
if (characterID != NullEntityID)
{
bool doingCpr = msg.ReadBoolean();
if (doingCpr && SelectedCharacter != null)
{
animation = AnimController.Animation.CPR;
}
}
}
Vector2 pos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
Vector2 linearVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
bool fixedRotation = msg.ReadBoolean();
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
}
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
ReadStatus(msg);
AIController?.ClientRead(msg);
}
msg.ReadPadBits();
int index = 0;
if (GameMain.Client.Character == this && CanMove)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
networkUpdateID,
facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++;
memState.Insert(index, posInfo);
}
else
{
var posInfo = new CharacterStateInfo(
pos, rotation,
linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++;
memState.Insert(index, posInfo);
}
break;
case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 13);
switch (eventType)
{
case 0: //NetEntityEvent.Type.InventoryState
if (Inventory == null)
{
string errorMsg = "Received an inventory update message for an entity with no inventory ([name], removed: " + Removed + ")";
DebugConsole.ThrowError(errorMsg.Replace("[name]", Name));
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName.Value));
//read anyway to prevent messing up reading the rest of the message
_ = msg.ReadUInt16();
byte inventoryItemCount = msg.ReadByte();
for (int i = 0; i < inventoryItemCount; i++)
{
msg.ReadUInt16();
}
}
else
{
Inventory.ClientRead(type, msg, sendingTime);
}
break;
case 1: //NetEntityEvent.Type.Control
bool myCharacter = msg.ReadBoolean();
byte ownerID = msg.ReadByte();
ResetNetState();
if (myCharacter)
{
if (controlled != null)
{
LastNetworkUpdateID = controlled.LastNetworkUpdateID;
}
if (!IsDead) { Controlled = this; }
IsRemotePlayer = false;
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
}
else
{
if (controlled == this)
{
Controlled = null;
IsRemotePlayer = ownerID > 0;
}
}
break;
case 2: //NetEntityEvent.Type.Status
ReadStatus(msg);
break;
case 3: //NetEntityEvent.Type.UpdateSkills
int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++)
{
Identifier skillIdentifier = msg.ReadIdentifier();
float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel);
}
break;
case 4: // NetEntityEvent.Type.SetAttackTarget
case 5: //NetEntityEvent.Type.ExecuteAttack
int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte();
Vector2 targetSimPos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
//255 = entity already removed, no need to do anything
if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid {(eventType == 4 ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
break;
}
Limb attackLimb = AnimController.Limbs[attackLimbIndex];
Limb targetLimb = null;
IDamageable targetEntity = FindEntityByID(targetEntityID) as IDamageable;
if (targetEntity == null && eventType == 4)
{
DebugConsole.ThrowError($"Received invalid SetAttackTarget message. Target entity not found (ID {targetEntityID})");
break;
}
if (targetEntity is Character targetCharacter)
{
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid {(eventType == 4 ? "SetAttackTarget" : "ExecuteAttack")} message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
break;
}
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
}
if (attackLimb?.attack != null && Controlled != this)
{
if (eventType == 4)
{
SetAttackTarget(attackLimb, targetEntity, targetSimPos);
PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
}
else
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
}
}
break;
case 6: //NetEntityEvent.Type.AssignCampaignInteraction
byte campaignInteractionType = msg.ReadByte();
bool requireConsciousness = msg.ReadBoolean();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
RequireConsciousnessForCustomInteract = requireConsciousness;
break;
case 7: //NetEntityEvent.Type.ObjectiveManagerState
// 1 = order, 2 = objective
int msgType = msg.ReadRangedInteger(0, 2);
if (msgType == 0) { break; }
bool validData = msg.ReadBoolean();
if (!validData) { break; }
if (msgType == 1)
{
UInt32 orderPrefabUintIdentifier = msg.ReadUInt32();
var orderPrefab = OrderPrefab.Prefabs.Find(p => p.UintIdentifier == orderPrefabUintIdentifier);
Identifier option = Identifier.Empty;
if (orderPrefab.HasOptions)
{
int optionIndex = msg.ReadRangedInteger(-1, orderPrefab.AllOptions.Length);
if (optionIndex > -1)
{
option = orderPrefab.AllOptions[optionIndex];
}
}
GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option);
}
else if (msgType == 2)
{
Identifier identifier = msg.ReadIdentifier();
Identifier option = msg.ReadIdentifier();
ushort objectiveTargetEntityId = msg.ReadUInt16();
var objectiveTargetEntity = FindEntityByID(objectiveTargetEntityId);
GameMain.GameSession?.CrewManager?.CreateObjectiveIcon(this, identifier, option, objectiveTargetEntity);
}
break;
case 8: //NetEntityEvent.Type.TeamChange
byte newTeamId = msg.ReadByte();
ChangeTeam((CharacterTeamType)newTeamId);
break;
case 9: //NetEntityEvent.Type.AddToCrew
GameMain.GameSession.CrewManager.AddCharacter(this);
CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte();
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
ushort itemID = msg.ReadUInt16();
if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; }
item.AllowStealing = true;
var wifiComponent = item.GetComponent<WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = teamID;
}
var idCard = item.GetComponent<IdCard>();
if (idCard != null)
{
idCard.TeamID = teamID;
idCard.SubmarineSpecificID = 0;
}
}
break;
case 10: //NetEntityEvent.Type.UpdateExperience
int experienceAmount = msg.ReadInt32();
info?.SetExperience(experienceAmount);
break;
case 11: //NetEntityEvent.Type.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
for (int i = 0; i < talentCount; i++)
{
bool addedThisRound = msg.ReadBoolean();
UInt32 talentIdentifier = msg.ReadUInt32();
GiveTalent(talentIdentifier, addedThisRound);
}
break;
case 12: //NetEntityEvent.Type.UpdateMoney:
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
case 13: //NetEntityEvent.Type.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++)
{
string statIdentifier = msg.ReadString();
float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
}
break;
}
msg.ReadPadBits();
break;
}
}
Vector2 pos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
Vector2 linearVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
bool fixedRotation = msg.ReadBoolean();
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
}
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
ReadStatus(msg);
AIController?.ClientRead(msg);
}
msg.ReadPadBits();
int index = 0;
if (GameMain.Client.Character == this && CanMove)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
networkUpdateID,
facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++;
memState.Insert(index, posInfo);
}
else
{
var posInfo = new CharacterStateInfo(
pos, rotation,
linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++;
memState.Insert(index, posInfo);
}
}
public virtual void ClientEventRead(IReadMessage msg, float sendingTime)
{
EventType eventType = (EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventType)
{
case EventType.InventoryState:
if (Inventory == null)
{
string errorMsg = "Received an inventory update message for an entity with no inventory ([name], removed: " + Removed + ")";
DebugConsole.ThrowError(errorMsg.Replace("[name]", Name));
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName.Value));
//read anyway to prevent messing up reading the rest of the message
_ = msg.ReadUInt16();
byte inventoryItemCount = msg.ReadByte();
for (int i = 0; i < inventoryItemCount; i++)
{
msg.ReadUInt16();
}
}
else
{
Inventory.ClientEventRead(msg, sendingTime);
}
break;
case EventType.Control:
bool myCharacter = msg.ReadBoolean();
byte ownerID = msg.ReadByte();
ResetNetState();
if (myCharacter)
{
if (controlled != null)
{
LastNetworkUpdateID = controlled.LastNetworkUpdateID;
}
if (!IsDead) { Controlled = this; }
IsRemotePlayer = false;
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
}
else
{
if (controlled == this)
{
Controlled = null;
IsRemotePlayer = ownerID > 0;
}
}
break;
case EventType.Status:
ReadStatus(msg);
break;
case EventType.UpdateSkills:
int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++)
{
Identifier skillIdentifier = msg.ReadIdentifier();
float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel);
}
break;
case EventType.SetAttackTarget:
case EventType.ExecuteAttack:
int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte();
float targetX = msg.ReadSingle();
float targetY = msg.ReadSingle();
Vector2 targetSimPos = new Vector2(targetX, targetY);
//255 = entity already removed, no need to do anything
if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
break;
}
Limb attackLimb = AnimController.Limbs[attackLimbIndex];
Limb targetLimb = null;
IDamageable targetEntity = FindEntityByID(targetEntityID) as IDamageable;
if (targetEntity == null && eventType == EventType.SetAttackTarget)
{
DebugConsole.ThrowError($"Received invalid SetAttackTarget message. Target entity not found (ID {targetEntityID})");
break;
}
if (targetEntity is Character targetCharacter)
{
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
break;
}
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
}
if (attackLimb?.attack != null && Controlled != this)
{
if (eventType == EventType.SetAttackTarget)
{
SetAttackTarget(attackLimb, targetEntity, targetSimPos);
PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
}
else
{
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
}
}
break;
case EventType.AssignCampaignInteraction:
byte campaignInteractionType = msg.ReadByte();
bool requireConsciousness = msg.ReadBoolean();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
RequireConsciousnessForCustomInteract = requireConsciousness;
break;
case EventType.ObjectiveManagerState:
// 1 = order, 2 = objective
AIObjectiveManager.ObjectiveType msgType
= (AIObjectiveManager.ObjectiveType)msg.ReadRangedInteger(
(int)AIObjectiveManager.ObjectiveType.MinValue,
(int)AIObjectiveManager.ObjectiveType.MaxValue);
if (msgType == 0) { break; }
bool validData = msg.ReadBoolean();
if (!validData) { break; }
if (msgType == AIObjectiveManager.ObjectiveType.Order)
{
UInt32 orderPrefabUintIdentifier = msg.ReadUInt32();
var orderPrefab = OrderPrefab.Prefabs.Find(p => p.UintIdentifier == orderPrefabUintIdentifier);
Identifier option = Identifier.Empty;
if (orderPrefab.HasOptions)
{
int optionIndex = msg.ReadRangedInteger(-1, orderPrefab.AllOptions.Length);
if (optionIndex > -1)
{
option = orderPrefab.AllOptions[optionIndex];
}
}
GameMain.GameSession?.CrewManager?.SetOrderHighlight(this, orderPrefab.Identifier, option);
}
else if (msgType == AIObjectiveManager.ObjectiveType.Objective)
{
Identifier identifier = msg.ReadIdentifier();
Identifier option = msg.ReadIdentifier();
ushort objectiveTargetEntityId = msg.ReadUInt16();
var objectiveTargetEntity = FindEntityByID(objectiveTargetEntityId);
GameMain.GameSession?.CrewManager?.CreateObjectiveIcon(this, identifier, option, objectiveTargetEntity);
}
break;
case EventType.TeamChange:
byte newTeamId = msg.ReadByte();
ChangeTeam((CharacterTeamType)newTeamId);
break;
case EventType.AddToCrew:
GameMain.GameSession.CrewManager.AddCharacter(this);
CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte();
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
ushort itemID = msg.ReadUInt16();
if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; }
item.AllowStealing = true;
var wifiComponent = item.GetComponent<WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = teamID;
}
var idCard = item.GetComponent<IdCard>();
if (idCard != null)
{
idCard.TeamID = teamID;
idCard.SubmarineSpecificID = 0;
}
}
break;
case EventType.UpdateExperience:
int experienceAmount = msg.ReadInt32();
info?.SetExperience(experienceAmount);
break;
case EventType.UpdateTalents:
ushort talentCount = msg.ReadUInt16();
for (int i = 0; i < talentCount; i++)
{
bool addedThisRound = msg.ReadBoolean();
UInt32 talentIdentifier = msg.ReadUInt32();
GiveTalent(talentIdentifier, addedThisRound);
}
break;
case EventType.UpdateMoney:
int moneyAmount = msg.ReadInt32();
SetMoney(moneyAmount);
break;
case EventType.UpdatePermanentStats:
byte savedStatValueCount = msg.ReadByte();
StatTypes statType = (StatTypes)msg.ReadByte();
info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++)
{
string statIdentifier = msg.ReadString();
float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
}
break;
}
msg.ReadPadBits();
}
public static Character ReadSpawnData(IReadMessage inc)
@@ -542,6 +542,8 @@ namespace Barotrauma
{
bool hasOwner = inc.ReadBoolean();
int ownerId = hasOwner ? inc.ReadByte() : -1;
int balance = hasOwner ? inc.ReadInt32() : -1;
int rewardDistribution = hasOwner ? inc.ReadRangedInteger(0, 100) : -1;
byte teamID = inc.ReadByte();
bool hasAi = inc.ReadBoolean();
Identifier infoSpeciesName = inc.ReadIdentifier();
@@ -558,6 +560,8 @@ namespace Barotrauma
}
character.TeamID = (CharacterTeamType)teamID;
character.CampaignInteractionType = (CampaignMode.InteractionType)inc.ReadByte();
character.Wallet.Balance = balance;
character.Wallet.RewardDistribution = rewardDistribution;
if (character.CampaignInteractionType != CampaignMode.InteractionType.None)
{
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(character, character.CampaignInteractionType);
@@ -597,7 +601,7 @@ namespace Barotrauma
: Identifier.Empty)
.WithManualPriority(orderPriority)
.WithOrderGiver(orderGiver);
character.SetOrder(order, speak: false, force: true);
character.SetOrder(order, isNewOrder: true, speak: false, force: true);
}
else
{
@@ -1,37 +0,0 @@
namespace Barotrauma
{
partial class AfflictionHusk : Affliction
{
private InfectionState? prevDisplayedMessage;
partial void UpdateMessages()
{
if (character != Character.Controlled) { return; }
if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
switch (State)
{
case InfectionState.Dormant:
if (Strength < DormantThreshold * 0.5f)
{
return;
}
GUI.AddMessage(TextManager.Get("HuskDormant"), GUIStyle.Red);
break;
case InfectionState.Transition:
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUIStyle.Red);
break;
case InfectionState.Active:
if (character.Params.UseHuskAppendage)
{
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Attack)), GUIStyle.Red);
}
break;
case InfectionState.Final:
default:
break;
}
prevDisplayedMessage = State;
}
}
}
@@ -300,7 +300,7 @@ namespace Barotrauma
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Treatment });
GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.TreatmentEventData());
}
return true;
@@ -400,7 +400,7 @@ namespace Barotrauma
{
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Status });
GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.StatusEventData());
}
else
{
@@ -573,10 +573,10 @@ namespace Barotrauma
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
limb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull);
if (blood != null && !inWater)
if (blood != null)
{
blood.Size *= bloodParticleSize;
if (!string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f)
if (!inWater && !string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f)
{
blood.OnCollision += (Vector2 pos, Hull hull) =>
{
@@ -523,7 +523,7 @@ namespace Barotrauma
private string GetSpritePath(ContentPath texturePath)
{
if (!character.IsHumanoid) { return texturePath.Value; }
return GetSpritePath(texturePath, character?.Info);
return GetSpritePath(texturePath, character.Info);
}
partial void LoadParamsProjSpecific()
@@ -559,7 +559,7 @@ namespace Barotrauma
GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams);
}, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().ToArray() }));
}, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() }));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) =>
{
@@ -628,7 +628,7 @@ namespace Barotrauma
DebugConsoleMapping.Instance.Remove(key);
NewMessage("Keybind unbound.", GUIStyle.Green);
return;
}, isCheat: false, getValidArgs: () => new[] { DebugConsoleMapping.Instance.Bindings.Keys.Select(keys => keys.ToString()).Distinct().ToArray() }));
}, isCheat: false, getValidArgs: () => new[] { DebugConsoleMapping.Instance.Bindings.Keys.Select(keys => keys.ToString()).Distinct().OrderBy(k => k).ToArray() }));
commands.Add(new Command("savebinds", "savebinds: Writes current keybinds into the config file.", (string[] args) =>
{
@@ -710,6 +710,7 @@ namespace Barotrauma
commands.Add(new Command("traitorlist", "", (string[] args) => { }));
AssignRelayToServer("traitorlist", true);
AssignRelayToServer("money", true);
AssignRelayToServer("showmoney", true);
AssignRelayToServer("setskill", true);
AssignRelayToServer("readycheck", true);
@@ -1734,7 +1735,7 @@ namespace Barotrauma
return new string[][]
{
propertyList.Distinct().Select(i => i.Value).ToArray(),
propertyList.Distinct().Select(i => i.Value).OrderBy(n => n).ToArray(),
Array.Empty<string>()
};
}));
@@ -1763,17 +1764,26 @@ namespace Barotrauma
foreach (var missionPrefab in MissionPrefab.Prefabs)
{
Identifier missionId = (missionPrefab.ConfigElement.Attribute("textidentifier") == null ? missionPrefab.Identifier : missionPrefab.ConfigElement.GetAttributeIdentifier("textidentifier", Identifier.Empty));
Identifier nameIdentifier = $"missionname.{missionId}".ToIdentifier();
if (!tags[language].Contains(nameIdentifier))
addIfMissing($"missionname.{missionId}".ToIdentifier(), language);
addIfMissing($"missiondescription.{missionId}".ToIdentifier(), language);
}
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
{
foreach (var property in itemComponentType.GetProperties())
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
}
Identifier descriptionIdentifier = $"missiondescription.{missionId}".ToIdentifier();
if (!tags[language].Contains(descriptionIdentifier))
{
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[descriptionIdentifier].Add(language);
if (!property.IsDefined(typeof(InGameEditable), false)) { continue; }
string propertyTag = $"{property.DeclaringType.Name}.{property.Name}";
addIfMissingAll(language,
propertyTag.ToIdentifier(),
property.Name.ToIdentifier(),
$"sp.{propertyTag}.name".ToIdentifier());
addIfMissingAll(language,
$"sp.{propertyTag}.description".ToIdentifier(),
$"{property.Name.ToIdentifier()}.description".ToIdentifier());
}
}
@@ -1781,18 +1791,8 @@ namespace Barotrauma
{
if (sub.Type != SubmarineType.Player || !sub.IsVanillaSubmarine()) { continue; }
Identifier nameIdentifier = $"submarine.name.{sub.Name}".ToIdentifier();
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
}
Identifier descriptionIdentifier = ("submarine.description." + sub.Name).ToIdentifier();
if (!tags[language].Contains(descriptionIdentifier))
{
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[descriptionIdentifier].Add(language);
}
addIfMissing($"submarine.name.{sub.Name}".ToIdentifier(), language);
addIfMissing(("submarine.description." + sub.Name).ToIdentifier(), language);
}
foreach (AfflictionPrefab affliction in AfflictionPrefab.List)
@@ -1806,42 +1806,21 @@ namespace Barotrauma
}
Identifier afflictionId = affliction.TranslationIdentifier;
Identifier nameIdentifier = $"afflictionname.{afflictionId}".ToIdentifier();
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
}
Identifier descriptionIdentifier = $"afflictiondescription.{afflictionId}".ToIdentifier();
if (!tags[language].Contains(descriptionIdentifier))
{
if (!missingTags.ContainsKey(descriptionIdentifier)) { missingTags[descriptionIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[descriptionIdentifier].Add(language);
}
addIfMissing($"afflictionname.{afflictionId}".ToIdentifier(), language);
addIfMissing($"afflictiondescription.{afflictionId}".ToIdentifier(), language);
}
foreach (var talentTree in TalentTree.JobTalentTrees)
{
foreach (var talentSubTree in talentTree.TalentSubTrees)
{
Identifier nameIdentifier = $"talenttree.{talentSubTree.Identifier}".ToIdentifier();
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
}
addIfMissing($"talenttree.{talentSubTree.Identifier}".ToIdentifier(), language);
}
}
foreach (var talent in TalentPrefab.TalentPrefabs)
{
Identifier nameIdentifier = $"talentname.{talent.Identifier}".ToIdentifier();
if (!tags[language].Contains(nameIdentifier))
{
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
}
addIfMissing($"talentname.{talent.Identifier}".ToIdentifier(), language);
}
//check missing entity names
@@ -1849,17 +1828,25 @@ namespace Barotrauma
{
Identifier nameIdentifier = ("entityname." + me.Identifier).ToIdentifier();
if (tags[language].Contains(nameIdentifier)) { continue; }
if (me.HideInMenus) { continue; }
ContentXElement configElement = null;
if (me is ItemPrefab itemPrefab)
{
nameIdentifier = itemPrefab.ConfigElement?.GetAttributeIdentifier("nameidentifier", nameIdentifier) ?? nameIdentifier;
if (nameIdentifier != null)
{
if (tags[language].Contains("entityname." + nameIdentifier)) { continue; }
}
configElement = itemPrefab.ConfigElement;
}
else if (me is StructurePrefab structurePrefab)
{
configElement = structurePrefab.ConfigElement;
}
if (configElement != null)
{
var overrideIdentifier = configElement.GetAttributeIdentifier("nameidentifier", null);
if (overrideIdentifier != null && tags[language].Contains("entityname." + overrideIdentifier)) { continue; }
}
if (!missingTags.ContainsKey(nameIdentifier)) { missingTags[nameIdentifier] = new HashSet<LanguageIdentifier>(); }
missingTags[nameIdentifier].Add(language);
addIfMissing(nameIdentifier, language);
}
}
@@ -1868,11 +1855,7 @@ namespace Barotrauma
foreach (LanguageIdentifier language in TextManager.AvailableLanguages)
{
if (language == TextManager.DefaultLanguage) { continue; }
if (!tags[language].Contains(englishTag))
{
if (!missingTags.ContainsKey(englishTag)) { missingTags[englishTag] = new HashSet<LanguageIdentifier>(); }
missingTags[englishTag].Add(language);
}
addIfMissing(englishTag, language);
}
}
@@ -1920,6 +1903,24 @@ namespace Barotrauma
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
SwapLanguage(TextManager.DefaultLanguage);
void addIfMissing(Identifier tag, LanguageIdentifier language)
{
if (!tags[language].Contains(tag))
{
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); }
missingTags[tag].Add(language);
}
}
void addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
{
if (!potentialTags.Any(t => tags[language].Contains(t)))
{
var tag = potentialTags.First();
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); }
missingTags[tag].Add(language);
}
}
}));
commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) =>
@@ -1944,7 +1945,10 @@ namespace Barotrauma
}
var content1 = getContent(doc1.Root);
var language1 = doc1.Root.GetAttributeIdentifier("language", string.Empty);
var content2 = getContent(doc2.Root);
var language2 = doc2.Root.GetAttributeIdentifier("language", string.Empty);
foreach (KeyValuePair<string, string> kvp in content1)
{
@@ -1952,12 +1956,9 @@ namespace Barotrauma
{
ThrowError($"File 2 doesn't contain the text tag \"{kvp.Key}\"");
}
else
else if (language1 == language2 && content2[kvp.Key] != kvp.Value)
{
if (content2[kvp.Key] != kvp.Value)
{
ThrowError($"Texts for the tag \"{kvp.Key}\" don't match:\n1. {kvp.Value}\n2. {content2[kvp.Key]}");
}
ThrowError($"Texts for the tag \"{kvp.Key}\" don't match:\n1. {kvp.Value}\n2. {content2[kvp.Key]}");
}
}
foreach (KeyValuePair<string, string> kvp in content2)
@@ -2319,7 +2320,14 @@ namespace Barotrauma
}
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines);
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
try
{
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
}
catch (Exception e)
{
ThrowError($"Failed to open the file \"{filePath}\".", e);
}
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
@@ -671,7 +671,7 @@ namespace Barotrauma
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.Channel = channel;
GameMain.Client?.CreateEntityEvent(radio.Item, new object[] { NetEntityEvent.Type.ChangeProperty, radio.SerializableProperties["channel".ToIdentifier()] });
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()]));
if (setText)
{
@@ -172,7 +172,7 @@ namespace Barotrauma
{
AutoScaleVertical = true,
TextScale = 1.1f,
TextGetter = () => FormatCurrency(campaign.Money)
TextGetter = () => FormatCurrency(campaign.Wallet.Balance)
};
var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
@@ -630,7 +630,7 @@ namespace Barotrauma
total += ((InfoSkill)c.UserData).CharacterInfo.Salary;
});
totalBlock.Text = FormatCurrency(total);
bool enoughMoney = campaign != null ? total <= campaign.Money : true;
bool enoughMoney = campaign == null || campaign.Wallet.CanAfford(total);
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any();
}
@@ -652,7 +652,7 @@ namespace Barotrauma
int total = nonDuplicateHires.Aggregate(0, (total, info) => total + info.Salary);
if (total > campaign.Money) { return false; }
if (!campaign.Wallet.CanAfford(total)) { return false; }
bool atLeastOneHired = false;
foreach (CharacterInfo ci in nonDuplicateHires)
@@ -299,14 +299,16 @@ namespace Barotrauma
return;
}
if (GameMain.ShowFPS || GameMain.DebugDraw)
if (GameMain.ShowFPS || GameMain.DebugDraw || GameMain.ShowPerf)
{
DrawString(spriteBatch, new Vector2(10, 10),
float y = 10.0f;
DrawString(spriteBatch, new Vector2(10, y),
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0)
{
DrawString(spriteBatch, new Vector2(10, 25),
y += GameSettings.CurrentConfig.Graphics.TextScale * 15.0f;
DrawString(spriteBatch, new Vector2(10, y),
$"Physics: {GameMain.CurrentUpdateRate}",
(GameMain.CurrentUpdateRate < Timing.FixedUpdateRate) ? Color.Red : Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
}
@@ -336,8 +338,15 @@ namespace Barotrauma
DrawString(spriteBatch, new Vector2(300, y),
key + ": " + elapsedMillisecs.ToString("0.00"),
Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15;
foreach (string childKey in GameMain.PerformanceCounter.GetSavedPartialIdentifiers(key))
{
elapsedMillisecs = GameMain.PerformanceCounter.GetPartialAverageElapsedMillisecs(key, childKey);
DrawString(spriteBatch, new Vector2(315, y),
childKey + ": " + elapsedMillisecs.ToString("0.00"),
Color.Lerp(Color.LightGreen, GUIStyle.Red, elapsedMillisecs / 10.0f), Color.Black * 0.5f, 0, GUIStyle.SmallFont);
y += 15;
}
}
if (Powered.Grids != null)
@@ -1453,7 +1462,7 @@ namespace Barotrauma
3 => radii.Start,
_ => throw new InvalidOperationException()
};
int getDirectionIndex(int vertexIndex)
static int getDirectionIndex(int vertexIndex)
=> (vertexIndex % 4) switch
{
0 => (vertexIndex / 4) + 0,
@@ -5,7 +5,7 @@ using Microsoft.Xna.Framework;
namespace Barotrauma
{
public class GUIColorPicker : GUIComponent
public class GUIColorPicker : GUIComponent, IDisposable
{
public delegate bool OnColorSelectedHandler(GUIColorPicker component, Color color);
public OnColorSelectedHandler? OnColorSelected;
@@ -34,11 +34,6 @@ namespace Barotrauma
public GUIColorPicker(RectTransform rectT, string? style = null) : base(style, rectT) { }
~GUIColorPicker()
{
DisposeTextures();
}
private void Init()
{
int tWidth = Rect.Width;
@@ -170,10 +165,12 @@ namespace Barotrauma
}
}
public void DisposeTextures()
public void Dispose()
{
mainTexture?.Dispose();
mainTexture = null;
hueTexture?.Dispose();
hueTexture = null;
}
public void RefreshHue()
@@ -161,7 +161,7 @@ namespace Barotrauma
}
}
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false) : base(style, rectT)
public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft) : base(style, rectT)
{
text ??= new RawLString("");
@@ -170,9 +170,10 @@ namespace Barotrauma
this.selectMultiple = selectMultiple;
button = new GUIButton(new RectTransform(Vector2.One, rectT), text, Alignment.CenterLeft, style: "GUIDropDown")
button = new GUIButton(new RectTransform(Vector2.One, rectT), text, textAlignment, style: "GUIDropDown")
{
OnClicked = OnClicked
OnClicked = OnClicked,
TextBlock = { OverflowClip = true }
};
GUIStyle.Apply(button, "", this);
button.TextBlock.SetTextPos();
@@ -96,11 +96,15 @@ namespace Barotrauma
switch (child.ScaleBasis)
{
case ScaleBasis.BothHeight:
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
break;
case ScaleBasis.Smallest when Rect.Height <= Rect.Width:
case ScaleBasis.Largest when Rect.Height > Rect.Width:
child.MinSize = new Point((int)((child.Rect.Height * child.RelativeSize.X) / child.RelativeSize.Y), child.MinSize.Y);
break;
case ScaleBasis.BothWidth:
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
break;
case ScaleBasis.Smallest when Rect.Width <= Rect.Height:
case ScaleBasis.Largest when Rect.Width > Rect.Height:
child.MinSize = new Point(child.MinSize.X, (int)((child.Rect.Width * child.RelativeSize.Y) / child.RelativeSize.X));
@@ -12,9 +12,12 @@ namespace Barotrauma
Int, Float
}
public delegate void OnValueEnteredHandler(GUINumberInput numberInput);
public OnValueEnteredHandler OnValueEntered;
public delegate void OnValueChangedHandler(GUINumberInput numberInput);
public OnValueChangedHandler OnValueChanged;
public GUITextBox TextBox { get; private set; }
public GUIButton PlusButton { get; private set; }
@@ -209,6 +212,8 @@ namespace Barotrauma
{
ClampFloatValue();
}
OnValueEntered?.Invoke(this);
};
TextBox.OnEnterPressed += (textBox, text) =>
{
@@ -220,6 +225,8 @@ namespace Barotrauma
{
ClampFloatValue();
}
OnValueEntered?.Invoke(this);
return true;
};
@@ -312,6 +312,7 @@ namespace Barotrauma
MoveButton(new Vector2(
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Bar.Rect.Width * barScale,
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Bar.Rect.Height * barScale));
OnReleased?.Invoke(this, BarScroll);
}
}
}
@@ -271,7 +271,7 @@ namespace Barotrauma
healList.PriceBlock.Text = UpgradeStore.FormatCurrency(totalCost);
healList.PriceBlock.TextColor = GUIStyle.Red;
healList.HealButton.Enabled = false;
if (medicalClinic.GetMoney() > totalCost)
if (medicalClinic.GetWallet().CanAfford(totalCost))
{
healList.PriceBlock.TextColor = GUIStyle.TextColorNormal;
if (medicalClinic.PendingHeals.Any())
@@ -467,7 +467,7 @@ namespace Barotrauma
GUITextBlock moneyLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), balanceLayout.RectTransform), string.Empty, textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont)
{
TextGetter = () => UpgradeStore.FormatCurrency(medicalClinic.GetMoney()),
TextGetter = () => UpgradeStore.FormatCurrency(medicalClinic.GetWallet().Balance),
AutoScaleVertical = true,
TextScale = 1.1f
};
@@ -577,7 +577,7 @@ namespace Barotrauma
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), footerLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight);
GUIButton healButton = new GUIButton(new RectTransform(new Vector2(0.33f, 1f), buttonLayout.RectTransform), TextManager.Get("medicalclinic.heal"))
{
Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetTotalCost() < medicalClinic.GetMoney(),
Enabled = medicalClinic.PendingHeals.Any() && medicalClinic.GetWallet().CanAfford(medicalClinic.GetTotalCost()),
OnClicked = (button, _) =>
{
button.Enabled = false;
@@ -3,6 +3,7 @@ using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@@ -67,8 +68,7 @@ namespace Barotrauma
private CargoManager CargoManager => campaignUI.Campaign.CargoManager;
private Location CurrentLocation => campaignUI.Campaign.Map?.CurrentLocation;
private int PlayerMoney => campaignUI.Campaign.Money;
private Wallet PlayerWallet => campaignUI.Campaign.Wallet;
private bool IsBuying => activeTab switch
{
StoreTab.Buy => true,
@@ -715,24 +715,30 @@ namespace Barotrauma
private LocalizedString GetMerchantBalanceText() => GetCurrencyFormatted(CurrentLocation?.StoreCurrentBalance ?? 0);
private LocalizedString GetPlayerBalanceText() => GetCurrencyFormatted(PlayerMoney);
private LocalizedString GetPlayerBalanceText() => GetCurrencyFormatted(PlayerWallet.Balance);
private GUILayoutGroup CreateDealsGroup(GUIListBox parentList, int elementCount = 4)
{
var elementHeight = (int)(GUI.yScale * 80);
var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, elementCount * elementHeight + 3), parent: parentList.Content.RectTransform), style: null);
frame.UserData = "deals";
var frame = new GUIFrame(new RectTransform(new Point(parentList.Content.Rect.Width, elementCount * elementHeight + 3), parent: parentList.Content.RectTransform), style: null)
{
UserData = "deals"
};
var dealsGroup = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter);
var dealsHeader = new GUILayoutGroup(new RectTransform(new Point((int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
dealsHeader.UserData = "header";
var dealsHeader = new GUILayoutGroup(new RectTransform(new Point((int)(0.95f * parentList.Content.Rect.Width), elementHeight), parent: dealsGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
UserData = "header"
};
var iconWidth = (0.9f * dealsHeader.Rect.Height) / dealsHeader.Rect.Width;
var dealsIcon = new GUIImage(new RectTransform(new Vector2(iconWidth, 0.9f), dealsHeader.RectTransform), "StoreDealIcon", scaleToFit: true);
var text = TextManager.Get(parentList == storeBuyList ? "campaignstore.dailyspecials" : "campaignstore.requestedgoods");
var dealsText = new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 0.9f), dealsHeader.RectTransform), text, font: GUIStyle.LargeFont);
storeSpecialColor = dealsIcon.Color;
dealsText.TextColor = storeSpecialColor;
var divider = new GUIImage(new RectTransform(new Point(dealsGroup.Rect.Width, 3), dealsGroup.RectTransform), "HorizontalLine");
divider.UserData = "divider";
var divider = new GUIImage(new RectTransform(new Point(dealsGroup.Rect.Width, 3), dealsGroup.RectTransform), "HorizontalLine")
{
UserData = "divider"
};
frame.CanBeFocused = dealsGroup.CanBeFocused = dealsHeader.CanBeFocused = dealsIcon.CanBeFocused = dealsText.CanBeFocused = divider.CanBeFocused = false;
return dealsGroup;
}
@@ -1801,7 +1807,7 @@ namespace Barotrauma
private void SetOwnedText(GUIComponent itemComponent, GUITextBlock ownedLabel = null)
{
ownedLabel ??= itemComponent?.FindChild("owned", recursive: true) as GUITextBlock;
if (itemComponent == null && ownedLabel == null) { return; }
if (itemComponent == null && ownedLabel == null) { return; }
PurchasedItem purchasedItem = itemComponent?.UserData as PurchasedItem;
ItemQuantity itemQuantity = null;
LocalizedString ownedLabelText = string.Empty;
@@ -1970,7 +1976,7 @@ namespace Barotrauma
DebugConsole.ShowError($"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
return false;
}
}
}
private bool BuyItems()
{
@@ -1990,7 +1996,7 @@ namespace Barotrauma
}
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
if (itemsToPurchase.None() || totalPrice > PlayerMoney) { return false; }
if (itemsToPurchase.None() || !PlayerWallet.CanAfford(totalPrice)) { return false; }
CargoManager.PurchaseItems(itemsToPurchase, true);
GameMain.Client?.SendCampaignState();
@@ -2047,7 +2053,7 @@ namespace Barotrauma
if (IsBuying)
{
shoppingCrateTotal.Text = GetCurrencyFormatted(buyTotal);
shoppingCrateTotal.TextColor = buyTotal > PlayerMoney ? Color.Red : Color.White;
shoppingCrateTotal.TextColor = !PlayerWallet.CanAfford(buyTotal) ? Color.Red : Color.White;
}
else
{
@@ -2093,7 +2099,7 @@ namespace Barotrauma
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
activeTab switch
{
StoreTab.Buy => buyTotal <= PlayerMoney,
StoreTab.Buy => PlayerWallet.CanAfford(buyTotal),
StoreTab.Sell => CurrentLocation != null && sellTotal <= CurrentLocation.StoreCurrentBalance,
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= CurrentLocation.StoreCurrentBalance,
_ => false
@@ -2109,9 +2115,12 @@ namespace Barotrauma
private float ownedItemsUpdateTimer = 0.0f, sellableItemsFromSubUpdateTimer = 0.0f;
private const float timerUpdateInterval = 1.5f;
private readonly Stopwatch updateStopwatch = new Stopwatch();
public void Update(float deltaTime)
{
updateStopwatch.Restart();
if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y)
{
CreateUI();
@@ -2124,10 +2133,10 @@ namespace Barotrauma
{
var prevOwnedItems = new Dictionary<ItemPrefab, ItemQuantity>(OwnedItems);
UpdateOwnedItems();
var refresh = (prevOwnedItems.Count != OwnedItems.Count) ||
(prevOwnedItems.Select(kvp => kvp.Value.Total).Sum() != OwnedItems.Select(kvp => kvp.Value.Total).Sum()) ||
(OwnedItems.Any(kvp => kvp.Value.Total > 0 && !prevOwnedItems.ContainsKey(kvp.Key)) ||
prevOwnedItems.Any(kvp => !OwnedItems.TryGetValue(kvp.Key, out ItemQuantity itemQuantity) || kvp.Value.Total != itemQuantity.Total));
bool refresh = OwnedItems.Count != prevOwnedItems.Count ||
OwnedItems.Values.Sum(v => v.Total) != prevOwnedItems.Values.Sum(v => v.Total) ||
OwnedItems.Any(kvp => !prevOwnedItems.TryGetValue(kvp.Key, out ItemQuantity v) || kvp.Value.Total != v.Total) ||
prevOwnedItems.Any(kvp => !OwnedItems.ContainsKey(kvp.Key));
if (refresh)
{
needsItemsToSellRefresh = true;
@@ -2138,8 +2147,13 @@ namespace Barotrauma
sellableItemsFromSubUpdateTimer += deltaTime;
if (sellableItemsFromSubUpdateTimer >= timerUpdateInterval)
{
needsItemsToSellFromSubRefresh = true;
needsRefresh = true;
var prevSubItems = new List<PurchasedItem>(itemsToSellFromSub);
RefreshItemsToSellFromSub();
needsRefresh = needsRefresh ||
itemsToSellFromSub.Count != prevSubItems.Count ||
itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
itemsToSellFromSub.Any(i => !(prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is PurchasedItem prev) || i.Quantity != prev.Quantity) ||
prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
}
}
@@ -2148,7 +2162,10 @@ namespace Barotrauma
if (needsRefresh || HavePermissionsChanged()) { Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy)) { RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell)) { RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f); }
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub)) { RefreshSellingFromSub(updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); }
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub)) { RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f); }
updateStopwatch.Stop();
GameMain.PerformanceCounter.AddPartialElapsedTicks("GameSessionUpdate", "StoreUpdate", updateStopwatch.ElapsedTicks);
}
}
}
@@ -581,7 +581,7 @@ namespace Barotrauma
private void ShowTransferPrompt()
{
if (GameMain.GameSession.Campaign.Money < deliveryFee && deliveryFee > 0)
if (!GameMain.GameSession.Campaign.Wallet.CanAfford(deliveryFee) && deliveryFee > 0)
{
new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext",
("[currencyname]", currencyLongText),
@@ -629,7 +629,7 @@ namespace Barotrauma
private void ShowBuyPrompt(bool purchaseOnly)
{
if (GameMain.GameSession.Campaign.Money < selectedSubmarine.Price)
if (!GameMain.GameSession.Campaign.Wallet.CanAfford(selectedSubmarine.Price))
{
new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext",
("[currencyname]", currencyLongText),
@@ -37,6 +37,12 @@ namespace Barotrauma
private GUIFrame pendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private bool isTransferMenuOpen;
private bool isSending;
private GUIComponent transferMenu;
private GUIButton transferMenuButton;
private float transferMenuOpenState;
private bool transferMenuStateCompleted;
private class LinkedGUI
{
@@ -133,8 +139,43 @@ namespace Barotrauma
SelectInfoFrameTab(SelectedTab);
}
public void Update()
public void Update(float deltaTime)
{
float menuOpenSpeed = deltaTime * 10f;
if (isTransferMenuOpen)
{
if (transferMenuStateCompleted)
{
transferMenuOpenState = transferMenuOpenState < 0.25f ? Math.Min(0.25f, transferMenuOpenState + (menuOpenSpeed / 2f)) : 0.25f;
}
else
{
if (transferMenuOpenState > 0.15f)
{
transferMenuStateCompleted = false;
transferMenuOpenState = Math.Max(0.15f, transferMenuOpenState - menuOpenSpeed);
}
else
{
transferMenuStateCompleted = true;
}
}
}
else
{
transferMenuStateCompleted = false;
if (transferMenuOpenState < 1f)
{
transferMenuOpenState = Math.Min(1f, transferMenuOpenState + menuOpenSpeed);
}
}
if (transferMenu != null && transferMenuButton != null)
{
int pos = (int)(transferMenuOpenState * -transferMenu.Rect.Height);
transferMenu.RectTransform.AbsoluteOffset = new Point(0, pos);
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
}
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
if (Character.Controlled is { } controlled && talentResetButton != null && talentApplyButton != null)
{
@@ -243,10 +284,7 @@ namespace Barotrauma
var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation");
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), "", textAlignment: Alignment.Right)
{
TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money))
};
GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right);
GUIFrame bottomDisclaimerFrame = new GUIFrame(new RectTransform(new Vector2(contentFrameSize.X, 0.1f), infoFrame.RectTransform)
{
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
@@ -258,6 +296,18 @@ namespace Barotrauma
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
SetBalanceText(balanceText, campaignMode.Bank.Balance);
campaignMode.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateInfoFrame).ToIdentifier(), e =>
{
if (e.Wallet != campaignMode.Bank) { return; }
SetBalanceText(balanceText, e.Wallet.Balance);
});
static void SetBalanceText(GUITextBlock text, int balance)
{
text.Text = TextManager.GetWithVariable("bankbalanceformat", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", balance));
}
}
else
{
@@ -329,7 +379,8 @@ namespace Barotrauma
private void CreateCrewListFrame(GUIFrame crewFrame)
{
crew = GameMain.GameSession.CrewManager.GetCharacters();
// FIXME remove TestScreen stuff
crew = GameMain.GameSession?.CrewManager?.GetCharacters() ?? new []{ TestScreen.dummyCharacter };
teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
// Show own team first when there's more than one team
@@ -743,7 +794,7 @@ namespace Barotrauma
Client client = userData as Client;
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) infoFrameHolder.RemoveChild(existingPreview);
if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); }
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) })
{
@@ -760,17 +811,291 @@ namespace Barotrauma
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
GameMain.Client.SelectCrewCharacter(character, preview);
CreateWalletFrame(background, character);
}
}
else if (client != null)
{
GUIComponent preview = CreateClientInfoFrame(background, client, GetPermissionIcon(client));
if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewClient(client, preview);
if (GameMain.NetworkMember != null) { GameMain.Client.SelectCrewClient(client, preview); }
CreateWalletFrame(background, client.Character);
}
return true;
}
private void CreateWalletFrame(GUIComponent parent, Character character)
{
if (character is null) { throw new ArgumentNullException(nameof(character), "Tried to create a wallet frame for a null character");}
isTransferMenuOpen = false;
transferMenuOpenState = 1f;
ImmutableArray<Character> salaryCrew = Mission.GetSalaryEligibleCrew().Where(c => c != character).ToImmutableArray();
Wallet targetWallet = character.Wallet;
GUIFrame walletFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), parent.RectTransform, anchor: Anchor.TopLeft)
{
RelativeOffset = new Vector2(0, 1.02f)
});
GUILayoutGroup walletLayout = new GUILayoutGroup(new RectTransform(ToolBox.PaddingSizeParentRelative(walletFrame.RectTransform, 0.9f), walletFrame.RectTransform, anchor: Anchor.Center));
GUILayoutGroup headerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.33f), walletLayout.RectTransform), isHorizontal: true);
GUIImage icon = new GUIImage(new RectTransform(Vector2.One, headerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "StoreTradingIcon", scaleToFit: true);
float relativeX = icon.RectTransform.NonScaledSize.X / (float)icon.Parent.RectTransform.NonScaledSize.X;
GUILayoutGroup headerTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - relativeX, 1f), headerLayout.RectTransform), isHorizontal: true) { Stretch = true };
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), TextManager.Get("crewwallet.wallet"), font: GUIStyle.LargeFont);
GUITextBlock moneyBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), headerTextLayout.RectTransform), UpgradeStore.FormatCurrency(targetWallet.Balance), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right);
GUILayoutGroup middleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.66f), walletLayout.RectTransform));
GUILayoutGroup salaryTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true);
GUITextBlock salaryTitle = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), TextManager.Get("crewwallet.salary"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
GUITextBlock rewardBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), salaryTextLayout.RectTransform), $"{Mission.GetRewardShare(targetWallet.RewardDistribution, salaryCrew, Option<int>.None()).Percentage}%", textAlignment: Alignment.BottomRight);
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), middleLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center);
GUIScrollBar salarySlider = new GUIScrollBar(new RectTransform(new Vector2(0.9f, 1f), sliderLayout.RectTransform), style: "GUISlider", barSize: 0.03f)
{
Range = Vector2.UnitY,
BarScrollValue = targetWallet.RewardDistribution / 100f,
Step = 0.01f,
BarSize = 0.1f,
OnMoved = (bar, scroll) =>
{
rewardBlock.Text = $"{Mission.GetRewardShare((int)(scroll * 100f), salaryCrew, Option<int>.None()).Percentage}%";
return true;
},
OnReleased = (bar, scroll) =>
{
int newRewardDistribution = (int)(scroll * 100);
if (newRewardDistribution == targetWallet.RewardDistribution) { return false; }
SetRewardDistribution(character, newRewardDistribution);
return true;
}
};
// @formatter:off
GUIScissorComponent scissorComponent = new GUIScissorComponent(new RectTransform(new Vector2(0.85f, 1.25f), walletFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter))
{
CanBeFocused = false
};
transferMenu = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform));
GUILayoutGroup transferMenuLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.8f), transferMenu.RectTransform, Anchor.BottomLeft), childAnchor: Anchor.Center);
GUILayoutGroup paddedTransferMenuLayout = new GUILayoutGroup(new RectTransform(ToolBox.PaddingSizeParentRelative(transferMenuLayout.RectTransform, 0.85f), transferMenuLayout.RectTransform));
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), paddedTransferMenuLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUILayoutGroup leftLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), mainLayout.RectTransform));
GUITextBlock leftName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), leftLayout.RectTransform), character.Name, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
GUITextBlock leftBalance = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), leftLayout.RectTransform), UpgradeStore.FormatCurrency(targetWallet.Balance), textAlignment: Alignment.Left) { TextColor = GUIStyle.Blue };
GUILayoutGroup rightLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), mainLayout.RectTransform), childAnchor: Anchor.TopRight);
GUITextBlock rightName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), rightLayout.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight);
GUITextBlock rightBalance = new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), rightLayout.RectTransform), string.Empty, textAlignment: Alignment.Right) { TextColor = GUIStyle.Red };
GUILayoutGroup centerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), mainLayout.RectTransform, Anchor.Center), childAnchor: Anchor.Center) { IgnoreLayoutGroups = true };
new GUIFrame(new RectTransform(new Vector2(0f, 1f), centerLayout.RectTransform, Anchor.Center), style: "VerticalLine") { IgnoreLayoutGroups = true };
GUIButton centerButton = new GUIButton(new RectTransform(new Vector2(0.6f), centerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight, anchor: Anchor.Center), style: "GUIButtonTransferArrow");
GUILayoutGroup inputLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center);
GUINumberInput transferAmountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), inputLayout.RectTransform), GUINumberInput.NumberType.Int, hidePlusMinusButtons: true)
{
MinValueInt = 0
};
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center);
GUILayoutGroup centerButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1f), buttonLayout.RectTransform), isHorizontal: true);
GUIButton confirmButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("confirm"), style: "GUIButtonFreeScale") { Enabled = false };
GUIButton resetButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), centerButtonLayout.RectTransform), TextManager.Get("reset"), style: "GUIButtonFreeScale") { Enabled = false };
// @formatter:on
transferMenuButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), walletFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter), style: "UIToggleButtonVertical")
{
OnClicked = (button, o) =>
{
isTransferMenuOpen = !isTransferMenuOpen;
if (!isTransferMenuOpen)
{
transferAmountInput.IntValue = 0;
}
ToggleTransferMenuIcon(button, open: isTransferMenuOpen);
return true;
}
};
ToggleTransferMenuIcon(transferMenuButton, open: isTransferMenuOpen);
ToggleCenterButton(centerButton, isSending);
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
if (!(Character.Controlled is { } myCharacter))
{
salarySlider.Enabled = false;
transferAmountInput.Enabled = false;
centerButton.Enabled = false;
confirmButton.Enabled = false;
return;
}
bool hasPermissions = campaign.AllowedToManageCampaign();
salarySlider.Enabled = hasPermissions;
Wallet otherWallet;
switch (hasPermissions)
{
case true:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
break;
case false when character == myCharacter:
rightName.Text = TextManager.Get("crewwallet.bank");
otherWallet = campaign.Bank;
isSending = true;
ToggleCenterButton(centerButton, isSending);
break;
default:
rightName.Text = myCharacter.Name;
otherWallet = campaign.PersonalWallet;
break;
}
if (!hasPermissions)
{
centerButton.Enabled = centerButton.CanBeFocused = false;
salarySlider.Enabled = salarySlider.CanBeFocused = false;
}
leftBalance.Text = UpgradeStore.FormatCurrency(otherWallet.Balance);
UpdateAllInputs();
centerButton.OnClicked = (btn, o) =>
{
isSending = !isSending;
ToggleCenterButton(btn, isSending);
UpdateAllInputs();
return true;
};
transferAmountInput.OnValueChanged = input =>
{
UpdateInputs();
};
transferAmountInput.OnValueEntered = input =>
{
UpdateAllInputs();
};
campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(CreateWalletFrame).ToIdentifier(), e =>
{
if (e.Wallet == targetWallet)
{
moneyBlock.Text = UpgradeStore.FormatCurrency(e.Info.Balance);
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
}
UpdateAllInputs();
});
resetButton.OnClicked = (button, o) =>
{
transferAmountInput.IntValue = 0;
UpdateAllInputs();
return true;
};
confirmButton.OnClicked = (button, o) =>
{
int amount = transferAmountInput.IntValue;
if (amount == 0) { return false; }
Option<Character> target1 = Option<Character>.Some(character),
target2 = otherWallet == campaign.Bank ? Option<Character>.None() : Option<Character>.Some(myCharacter);
if (isSending) { (target1, target2) = (target2, target1); }
SendTransaction(target1, target2, amount);
isTransferMenuOpen = false;
ToggleTransferMenuIcon(transferMenuButton, isTransferMenuOpen);
return true;
};
void UpdateAllInputs()
{
UpdateInputs();
UpdateMaxInput();
}
void UpdateInputs()
{
confirmButton.Enabled = resetButton.Enabled = transferAmountInput.IntValue > 0;
if (transferAmountInput.IntValue == 0)
{
rightBalance.Text = UpgradeStore.FormatCurrency(otherWallet.Balance);
rightBalance.TextColor = GUIStyle.TextColorNormal;
leftBalance.Text = UpgradeStore.FormatCurrency(targetWallet.Balance);
leftBalance.TextColor = GUIStyle.TextColorNormal;
}
else if (isSending)
{
rightBalance.Text = UpgradeStore.FormatCurrency(otherWallet.Balance + transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Blue;
leftBalance.Text = UpgradeStore.FormatCurrency(targetWallet.Balance - transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Red;
}
else
{
rightBalance.Text = UpgradeStore.FormatCurrency(otherWallet.Balance - transferAmountInput.IntValue);
rightBalance.TextColor = GUIStyle.Red;
leftBalance.Text = UpgradeStore.FormatCurrency(targetWallet.Balance + transferAmountInput.IntValue);
leftBalance.TextColor = GUIStyle.Blue;
}
}
void UpdateMaxInput()
{
transferAmountInput.MaxValueInt = isSending ? targetWallet.Balance : otherWallet.Balance;
}
}
static void ToggleTransferMenuIcon(GUIButton btn, bool open)
{
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = open ? SpriteEffects.None : SpriteEffects.FlipVertically;
}
}
static void ToggleCenterButton(GUIButton btn, bool isSending)
{
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = isSending ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
}
}
static void SendTransaction(Option<Character> to, Option<Character> from, int amount)
{
INetSerializableStruct transfer = new NetWalletTransfer
{
Sender = from.Select(option => option.ID),
Receiver = to.Select(option => option.ID),
Amount = amount
};
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.MONEY);
transfer.Write(msg);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
static void SetRewardDistribution(Character character, int newValue)
{
INetSerializableStruct transfer = new NetWalletSalaryUpdate
{
Target = character.ID,
NewRewardDistribution = newValue
};
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.REWARD_DISTRIBUTION);
transfer.Write(msg);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
static int GetRewardDistributionPercentage(int distribution, ImmutableArray<Character> crew)
{
return Mission.GetRewardShare(distribution, crew, Option<int>.None()).Percentage;
}
}
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
{
GUIComponent paddedFrame;
@@ -1209,8 +1534,6 @@ namespace Barotrauma
}
}
private Color unselectedColor = new Color(240, 255, 255, 225);
private Color selectedColor = new Color(220, 255, 220, 225);
private Color ownedColor = new Color(140, 180, 140, 225);
private Color unselectableColor = new Color(100, 100, 100, 225);
private Color pressedColor = new Color(60, 60, 60, 225);
@@ -1427,7 +1750,7 @@ namespace Barotrauma
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (TalentPrefab talent in talentOption.Talents)
foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier))
{
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{
@@ -1652,7 +1975,7 @@ namespace Barotrauma
controlledCharacter.GiveTalent(talent);
if (GameMain.Client != null)
{
GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents });
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
}
}
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
@@ -41,7 +41,7 @@ namespace Barotrauma
private readonly CampaignUI campaignUI;
private CampaignMode? Campaign => campaignUI.Campaign;
private int AvailableMoney => Campaign?.Money ?? 0;
private Wallet PlayerWallet => Campaign?.Wallet ?? Wallet.Invalid;
private UpgradeTab selectedUpgradeTab = UpgradeTab.Upgrade;
private GUIMessageBox? currectConfirmation;
@@ -61,7 +61,7 @@ namespace Barotrauma
private Vector2[][] subHullVertices = new Vector2[0][];
private List<Structure> submarineWalls = new List<Structure>();
public MapEntity? HoveredItem;
public MapEntity? HoveredEntity;
private bool highlightWalls;
private UpgradeCategory? currentUpgradeCategory;
@@ -105,6 +105,7 @@ namespace Barotrauma
Campaign.UpgradeManager.OnUpgradesChanged += RefreshAll;
Campaign.CargoManager.OnPurchasedItemsChanged += RefreshAll;
Campaign.CargoManager.OnSoldItemsChanged += RefreshAll;
Campaign.OnMoneyChanged.RegisterOverwriteExisting(nameof(UpgradeStore).ToIdentifier(), e => { RefreshAll(); } );
}
public void RefreshAll()
@@ -184,7 +185,7 @@ namespace Barotrauma
}
// reset the order first
foreach (UpgradeCategory category in UpgradeCategory.Categories)
foreach (UpgradeCategory category in UpgradeCategory.Categories.OrderBy(c => c.Name))
{
GUIComponent component = categoryList.Content.FindChild(c => c.UserData is CategoryData categoryData && categoryData.Category == category);
component?.SetAsLastChild();
@@ -286,7 +287,7 @@ namespace Barotrauma
GUILayoutGroup rightLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout), childAnchor: Anchor.TopRight);
GUILayoutGroup priceLayout = new GUILayoutGroup(rectT(1, 0.8f, rightLayout), childAnchor: Anchor.Center) { RelativeSpacing = 0.08f };
new GUITextBlock(rectT(1f, 0f, priceLayout), TextManager.Get("CampaignStore.Balance"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right);
new GUITextBlock(rectT(1f, 0f, priceLayout), FormatCurrency(AvailableMoney, format: true), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => FormatCurrency(AvailableMoney, format: true) };
new GUITextBlock(rectT(1f, 0f, priceLayout), FormatCurrency(PlayerWallet.Balance, format: true), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right) { TextGetter = () => FormatCurrency(PlayerWallet.Balance, format: true) };
new GUIFrame(rectT(0.5f, 0.1f, rightLayout, Anchor.BottomRight), style: "HorizontalLine") { IgnoreLayoutGroups = true };
repairButton.OnClicked = upgradeButton.OnClicked = (button, o) =>
@@ -343,15 +344,15 @@ namespace Barotrauma
private void DrawItemSwapPreview(SpriteBatch spriteBatch, GUICustomComponent component)
{
var selectedItem = customizeTabOpen ?
activeItemSwapSlideDown?.UserData as Item ?? HoveredItem as Item :
HoveredItem as Item;
activeItemSwapSlideDown?.UserData as Item ?? HoveredEntity as Item :
HoveredEntity as Item;
if (selectedItem?.Prefab.SwappableItem == null) { return; }
Sprite schematicsSprite = selectedItem.Prefab.SwappableItem.SchematicSprite;
if (schematicsSprite == null) { return; }
float schematicsScale = Math.Min(component.Rect.Width / 2 / schematicsSprite.size.X, component.Rect.Height / schematicsSprite.size.Y);
Vector2 center = new Vector2(component.Rect.Center.X, component.Rect.Center.Y);
schematicsSprite.Draw(spriteBatch, new Vector2(component.Rect.X, center.Y), GUIStyle.Green, new Vector2(0, schematicsSprite.size.Y / 2),
schematicsSprite.Draw(spriteBatch, new Vector2(component.Rect.X, center.Y), GUIStyle.Green, new Vector2(0, schematicsSprite.size.Y / 2),
scale: schematicsScale);
var swappableItemList = selectedUpgradeCategoryLayout?.FindChild("prefablist", true) as GUIListBox;
@@ -426,14 +427,14 @@ namespace Barotrauma
return false;
}
if (AvailableMoney >= hullRepairCost)
if (PlayerWallet.CanAfford(hullRepairCost))
{
LocalizedString body = TextManager.GetWithVariable("WallRepairs.PurchasePromptBody", "[amount]", hullRepairCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (AvailableMoney >= hullRepairCost)
if (PlayerWallet.Balance >= hullRepairCost)
{
Campaign.Money -= hullRepairCost;
PlayerWallet.TryDeduct(hullRepairCost);
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
Campaign.PurchasedHullRepairs = true;
button.Enabled = false;
@@ -461,14 +462,14 @@ namespace Barotrauma
CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) =>
{
if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs)
if (PlayerWallet.Balance >= itemRepairCost && !Campaign.PurchasedItemRepairs)
{
LocalizedString body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (AvailableMoney >= itemRepairCost && !Campaign.PurchasedItemRepairs)
if (PlayerWallet.Balance >= itemRepairCost && !Campaign.PurchasedItemRepairs)
{
Campaign.Money -= itemRepairCost;
PlayerWallet.TryDeduct(itemRepairCost);
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
Campaign.PurchasedItemRepairs = true;
button.Enabled = false;
@@ -507,14 +508,14 @@ namespace Barotrauma
return false;
}
if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
if (PlayerWallet.CanAfford(shuttleRetrieveCost) && !Campaign.PurchasedLostShuttles)
{
LocalizedString body = TextManager.GetWithVariable("ReplaceLostShuttles.PurchasePromptBody", "[amount]", shuttleRetrieveCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
{
if (AvailableMoney >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
if (PlayerWallet.Balance >= shuttleRetrieveCost && !Campaign.PurchasedLostShuttles)
{
Campaign.Money -= shuttleRetrieveCost;
PlayerWallet.TryDeduct(shuttleRetrieveCost);
GameAnalyticsManager.AddMoneySpentEvent(hullRepairCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
Campaign.PurchasedLostShuttles = true;
button.Enabled = false;
@@ -572,13 +573,13 @@ namespace Barotrauma
new GUITextBlock(rectT(1, 0, textLayout), title, font: GUIStyle.SubHeadingFont) { CanBeFocused = false, AutoScaleHorizontal = true };
new GUITextBlock(rectT(1, 0, textLayout), FormatCurrency(price));
GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, contentLayout), childAnchor: Anchor.Center) { UserData = "buybutton" };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = AvailableMoney >= price && !isDisabled, OnClicked = onPressed };
new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "RepairBuyButton") { ClickSound = GUISoundType.HireRepairClick, Enabled = PlayerWallet.Balance >= price && !isDisabled, OnClicked = onPressed };
contentLayout.Recalculate();
buyButtonLayout.Recalculate();
if (disableElement)
{
frameChild.Enabled = AvailableMoney >= price && !isDisabled;
frameChild.Enabled = PlayerWallet.Balance >= price && !isDisabled;
}
if (!HasPermission)
@@ -610,9 +611,9 @@ namespace Barotrauma
Dictionary<UpgradeCategory, List<UpgradePrefab>> upgrades = new Dictionary<UpgradeCategory, List<UpgradePrefab>>();
foreach (UpgradeCategory category in UpgradeCategory.Categories)
foreach (UpgradeCategory category in UpgradeCategory.Categories.OrderBy(c => c.Name))
{
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs.OrderBy(p => p.Name))
{
if (prefab.UpgradeCategories.Contains(category))
{
@@ -963,12 +964,12 @@ namespace Barotrauma
buttonStyle: isPurchased ? "WeaponInstallButton" : "StoreAddToCrateButton"));
if (!(frames.Last().FindChild(c => c is GUIButton, recursive: true) is GUIButton buyButton)) { continue; }
if (Campaign.Money >= price)
if (PlayerWallet.CanAfford(price))
{
buyButton.Enabled = true;
buyButton.OnClicked += (button, o) =>
{
LocalizedString promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody",
LocalizedString promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody",
("[itemtoinstall]", replacement.Name),
("[amount]", (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
@@ -1183,7 +1184,7 @@ namespace Barotrauma
buyButton.OnClicked += (button, o) =>
{
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name),
("[amount]", prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
@@ -1334,16 +1335,16 @@ namespace Barotrauma
{
if (GUI.MouseOn == frame)
{
if (HoveredItem != item) { CreateItemTooltip(item); }
HoveredItem = item;
if (HoveredEntity != item) { CreateItemTooltip(item); }
HoveredEntity = item;
if (PlayerInput.PrimaryMouseButtonClicked() && selectedUpgradeTab == UpgradeTab.Upgrade && currentStoreLayout != null)
{
if (customizeTabOpen)
{
if (selectedUpgradeCategoryLayout != null)
{
var linkedItems = HoveredItem is Item ? Campaign.UpgradeManager.GetLinkedItemsToSwap((Item)HoveredItem) : new List<Item>();
if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData as Item == HoveredItem || linkedItems.Contains((Item)c.UserData), recursive: true) is GUIButton itemElement)
var linkedItems = HoveredEntity is Item hoveredItem ? Campaign.UpgradeManager.GetLinkedItemsToSwap(hoveredItem) : new List<Item>();
if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData is Item item && (item == HoveredEntity || linkedItems.Contains(item)), recursive: true) is GUIButton itemElement)
{
if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); }
(itemElement.Parent?.Parent?.Parent as GUIListBox)?.ScrollToElement(itemElement);
@@ -1370,8 +1371,8 @@ namespace Barotrauma
// use pnpoly algorithm to detect if our mouse is within any of the hull polygons
if (subHullVertices.Any(hullVertex => ToolBox.PointIntersectsWithPolygon(PlayerInput.MousePosition, hullVertex)))
{
if (HoveredItem != firstStructure && !(firstStructure is null)) { CreateItemTooltip(firstStructure); }
HoveredItem = firstStructure;
if (HoveredEntity != firstStructure && !(firstStructure is null)) { CreateItemTooltip(firstStructure); }
HoveredEntity = firstStructure;
isMouseOnStructure = true;
GUI.MouseCursor = CursorState.Hand;
@@ -1382,7 +1383,7 @@ namespace Barotrauma
}
}
if (!isMouseOnStructure) { HoveredItem = null; }
if (!isMouseOnStructure) { HoveredEntity = null; }
}
// flip the tooltip if it is outside of the screen
@@ -1582,12 +1583,12 @@ namespace Barotrauma
priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade");
}
}
GUIButton button = buttonParent.GetChild<GUIButton>();
if (button != null)
{
button.Enabled = currentLevel < prefab.MaxLevel;
if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || price > campaign.Money)
if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || !campaign.Wallet.CanAfford(price))
{
button.Enabled = false;
}
@@ -184,7 +184,7 @@ namespace Barotrauma
}
// Exchange money
Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue;
campaign.Wallet.TryDeduct(itemValue);
GameAnalyticsManager.AddMoneyGainedEvent(itemValue, GameAnalyticsManager.MoneySource.Store, item.ItemPrefab.Identifier.Value);
// Remove from the sell crate
@@ -236,7 +236,7 @@ namespace Barotrauma
if (crewManager != null)
{
Order order = new Order(orderPrefab, Identifier.Empty, CharacterInfo.HighestManualOrderPriority, Order.OrderType.Current, null, null, orderGiver: Character.Controlled);
Order order = orderPrefab.CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: Character.Controlled);
crewManager.SetCharacterOrder(null, order);
if (crewManager.IsSinglePlayer) { HumanAIController.ReportProblem(Character.Controlled, order); }
}
@@ -290,16 +290,6 @@ namespace Barotrauma
return crewArea.Rect;
}
public IEnumerable<Character> GetCharacters()
{
return characters;
}
public IEnumerable<CharacterInfo> GetCharacterInfos()
{
return characterInfos;
}
/// <summary>
/// Remove the character from the crew (and crew menus).
/// </summary>
@@ -708,7 +698,8 @@ namespace Barotrauma
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
c.SpeechImpediment <= 100.0f &&
c.CharacterHealth.GetAllAfflictions(a => a is AfflictionHusk huskInfection && huskInfection.Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true }).None());
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
}
@@ -837,7 +828,7 @@ namespace Barotrauma
var orderGiver = order?.OrderGiver;
if (IsSinglePlayer)
{
character.SetOrder(order, speak: orderGiver != character);
character.SetOrder(order, isNewOrder, speak: orderGiver != character);
string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName?.Value, givingOrderToSelf: character == orderGiver, orderOption: order?.Option ?? Identifier.Empty, isNewOrder: isNewOrder);
orderGiver?.Speak(message);
}
@@ -2437,26 +2428,26 @@ namespace Barotrauma
optionNodes.Add(new OptionNode(node, Keys.D0 + hotkey % 10));
}
/// <remarks>
/// The order giver doesn't need to be set for the Order instances as it will be set when the node button is clicked.
/// </remarks>
private void CreateShortcutNodes()
{
bool HasAppropriateJobId(Character c, Identifier jobId) => c.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(jobId);
bool HasAppropriateJob(Character c, string jobId) => HasAppropriateJobId(c, jobId.ToIdentifier());
var sub = GetTargetSubmarine();
if (sub == null) { return; }
if (!(GetTargetSubmarine() is { } sub)) { return; }
shortcutNodes.Clear();
if (CanFitMoreNodes() && sub.GetItems(false).Find(i => i.HasTag("reactor") && i.IsPlayerTeamInteractable)?.GetComponent<Reactor>() is Reactor reactor)
var subItems = sub.GetItems(false);
if (CanFitMoreNodes() && subItems.Find(i => i.HasTag("reactor") && i.IsPlayerTeamInteractable)?.GetComponent<Reactor>() is Reactor reactor)
{
float reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// ---> Create shortcut node for "Operate Reactor" order's "Power Up" option
// --> Create shortcut node for "Operate Reactor" order's "Power Up" option
if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item))
{
var orderPrefab = OrderPrefab.Prefabs["operatereactor"];
var order = new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor, Character.Controlled);
var order = new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor);
if (IsNonDuplicateOrder(order))
{
shortcutNodes.Add(CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNode(order);
}
}
}
@@ -2464,11 +2455,11 @@ namespace Barotrauma
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering, Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
AddOrderNode(order);
}
// If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order
@@ -2476,8 +2467,7 @@ namespace Barotrauma
ActiveOrders.Any(o => o.Order.Identifier == "reportintruders") &&
IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["fightintruders"]))
{
var order = new Order(OrderPrefab.Prefabs["fightintruders"], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("fightintruders");
}
// If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order
@@ -2485,8 +2475,7 @@ namespace Barotrauma
IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["fixleaks"]) &&
ActiveOrders.Any(o => o.Order.Identifier == "reportbreach"))
{
var order = new Order(OrderPrefab.Prefabs["fixleaks"], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("fixleaks");
}
// --> Create shortcut nodes for the Repair orders
if (CanFitMoreNodes() && ActiveOrders.Any(o => o.Order.Identifier == "reportbrokendevices"))
@@ -2494,33 +2483,27 @@ namespace Barotrauma
var reportBrokenDevices = OrderPrefab.Prefabs["reportbrokendevices"];
// TODO: Doesn't work for player issued reports, because they don't have a target.
bool useSpecificRepairOrder = false;
string tag = "repairelectrical";
if (CanFitMoreNodes() && ShouldDelegateOrder(tag) &&
if (CanFitMoreNodes() && ShouldDelegateOrder("repairelectrical") &&
ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical")))
{
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[tag]))
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairelectrical"]))
{
var order = new Order(OrderPrefab.Prefabs[tag], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("repairelectrical");
}
useSpecificRepairOrder = true;
}
tag = "repairmechanical";
if (CanFitMoreNodes() && ShouldDelegateOrder(tag) &&
if (CanFitMoreNodes() && ShouldDelegateOrder("repairmechanical") &&
ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical")))
{
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[tag]))
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairmechanical"]))
{
var order = new Order(OrderPrefab.Prefabs[tag], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("repairmechanical");
}
useSpecificRepairOrder = true;
}
tag = "repairsystems";
if (!useSpecificRepairOrder && CanFitMoreNodes() && ShouldDelegateOrder(tag) && OrderPrefab.Prefabs[tag] is OrderPrefab repairOrder && IsNonDuplicateOrderPrefab(repairOrder))
if (!useSpecificRepairOrder && CanFitMoreNodes() && ShouldDelegateOrder("repairsystems") && OrderPrefab.Prefabs["repairsystems"] is OrderPrefab repairOrder && IsNonDuplicateOrderPrefab(repairOrder))
{
var order = new Order(OrderPrefab.Prefabs[tag], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("repairsystems");
}
}
// If fire is reported
@@ -2528,8 +2511,7 @@ namespace Barotrauma
if (CanFitMoreNodes() && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["extinguishfires"]) &&
ActiveOrders.Any(o => o.Order.Identifier == "reportfire"))
{
var order = new Order(OrderPrefab.Prefabs["extinguishfires"], null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
AddOrderNodeWithIdentifier("extinguishfires");
}
if (CanFitMoreNodes() && characterContext?.Info?.Job?.Prefab?.AppropriateOrders != null)
{
@@ -2541,8 +2523,8 @@ namespace Barotrauma
{
if (!orderPrefab.MustSetTarget || orderPrefab.GetMatchingItems(sub, true, interactableFor: characterContext ?? Character.Controlled).Any())
{
var order = new Order(orderPrefab, null, orderGiver: Character.Controlled);
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
var order = orderPrefab.CreateInstance(OrderPrefab.OrderTargetType.Entity);
AddOrderNode(order);
}
if (!CanFitMoreNodes()) { break; }
}
@@ -2550,9 +2532,8 @@ namespace Barotrauma
}
if (CanFitMoreNodes() && characterContext != null && !characterContext.IsDismissed)
{
var order = new Order(OrderPrefab.Dismissal, null, orderGiver: Character.Controlled);
shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1));
var order = OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity);
AddOrderNode(order);
}
shortcutNodes.RemoveAll(n => n.UserData is Order o && !IsOrderAvailable(o));
if (shortcutNodes.Count < 1) { return; }
@@ -2593,18 +2574,30 @@ namespace Barotrauma
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier) :
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi.Option == option));
}
void AddOrderNodeWithIdentifier(string identifier)
{
var order = OrderPrefab.Prefabs[identifier].CreateInstance(OrderPrefab.OrderTargetType.Entity);
AddOrderNode(order);
}
void AddOrderNode(Order order)
{
var node = order.Option.IsEmpty ?
CreateOrderNode(shortcutNodeSize, null, Point.Zero, order, -1) :
CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, -1);
shortcutNodes.Add(node);
}
}
private void CreateOrderNodes(OrderCategory orderCategory)
{
var orderPrefabs = OrderPrefab.Prefabs.Where(o => o.Category == orderCategory && !o.IsReport && IsOrderAvailable(o)).ToArray();
var orderPrefabs = OrderPrefab.Prefabs.Where(o => o.Category == orderCategory && !o.IsReport && IsOrderAvailable(o)).OrderBy(o => o.Identifier).ToArray();
Order order;
bool disableNode;
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance,
GetCircumferencePointCount(orderPrefabs.Length), GetFirstNodeAngle(orderPrefabs.Length));
for (int i = 0; i < orderPrefabs.Length; i++)
{
order = new Order(orderPrefabs[i], null, orderGiver: Character.Controlled);
order = orderPrefabs[i].CreateInstance(OrderPrefab.OrderTargetType.Entity);
disableNode = !CanCharacterBeHeard() ||
(order.MustSetTarget && (order.ItemComponentType != null || order.GetTargetItems().Any() || order.RequireItems.Any()) &&
order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None());
@@ -2617,11 +2610,13 @@ namespace Barotrauma
/// <summary>
/// Create order nodes based on the item context
/// </summary>
/// <remarks>
/// The order giver doesn't need to be set for the Order instances as it will be set when the node button is clicked.
/// </remarks>
private void CreateContextualOrderNodes()
{
if (contextualOrders.None())
{
string orderIdentifier;
// Check if targeting an item or a hull
if (itemContext != null && itemContext.IsPlayerTeamInteractable)
{
@@ -2636,66 +2631,62 @@ namespace Barotrauma
{
if (p.TargetItemsMatchItem(itemContext, option))
{
contextualOrders.Add(new Order(p, itemContext, targetComponent, Character.Controlled).WithOption(option));
contextualOrders.Add(new Order(p, option, itemContext, targetComponent));
}
}
}
else if (p.TargetItemsMatchItem(itemContext) || p.TryGetTargetItemComponent(itemContext, out targetComponent))
{
contextualOrders.Add(p.HasOptions ?
new Order(p, null, orderGiver: Character.Controlled) :
new Order(p, itemContext, targetComponent, Character.Controlled));
p.CreateInstance(OrderPrefab.OrderTargetType.Entity) :
new Order(p, itemContext, targetComponent));
}
}
// If targeting a periscope connected to a turret, show the 'operateweapons' order
orderIdentifier = "operateweapons";
var operateWeaponsPrefab = OrderPrefab.Prefabs[orderIdentifier];
if (contextualOrders.None(o => o.Identifier == orderIdentifier) && itemContext.Components.Any(c => c is Controller))
var operateWeaponsPrefab = OrderPrefab.Prefabs["operateweapons"];
if (contextualOrders.None(o => o.Identifier == "operateweapons") && itemContext.Components.Any(c => c is Controller))
{
var turret = itemContext.GetConnectedComponents<Turret>().FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ??
itemContext.GetConnectedComponents<Turret>(recursive: true).FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item));
if (turret != null)
{
contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled));
contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret));
}
}
// If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order
orderIdentifier = "repairsystems";
if (contextualOrders.None(order => order.Identifier == orderIdentifier) && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold))
if (contextualOrders.None(order => order.Identifier == "repairsystems") && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold))
{
if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical"))))
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairelectrical"], itemContext, targetItem: null, Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairelectrical"], itemContext, targetItem: null));
}
else if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical"))))
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairmechanical"], itemContext, targetItem: null, Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairmechanical"], itemContext, targetItem: null));
}
else
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs[orderIdentifier], itemContext, targetItem: null, Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairsystems"], itemContext, targetItem: null));
}
}
// Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot)
orderIdentifier = "pumpwater";
if (contextualOrders.FirstOrDefault(order => order.Identifier.Equals(orderIdentifier)) is Order pumpOrder &&
if (contextualOrders.FirstOrDefault(order => order.Identifier.Equals("pumpwater")) is Order pumpOrder &&
itemContext.Components.FirstOrDefault(c => c.GetType() == pumpOrder.ItemComponentType) is Pump pump && pump.IsAutoControlled)
{
contextualOrders.Remove(pumpOrder);
}
orderIdentifier = "cleanupitems";
if (contextualOrders.None(info => info.Identifier.Equals(orderIdentifier)))
if (contextualOrders.None(info => info.Identifier.Equals("cleanupitems")))
{
if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled))
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs[orderIdentifier], itemContext, targetItem: null, Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["cleanupitems"], itemContext, targetItem: null));
}
}
AddIgnoreOrder(itemContext);
}
else if (hullContext != null)
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs["fixleaks"], hullContext, targetItem: null, Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["fixleaks"], hullContext, targetItem: null));
if (wallContext != null)
{
AddIgnoreOrder(wallContext);
@@ -2729,26 +2720,23 @@ namespace Barotrauma
}
}
}
orderIdentifier = "wait";
if (contextualOrders.None(order => order.Identifier.Equals(orderIdentifier)))
if (contextualOrders.None(order => order.Identifier.Equals("wait")))
{
Vector2 position = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
Hull hull = Hull.FindHull(position, guess: Character.Controlled?.CurrentHull);
contextualOrders.Add(new Order(OrderPrefab.Prefabs[orderIdentifier], new OrderTarget(position, hull), Character.Controlled));
contextualOrders.Add(new Order(OrderPrefab.Prefabs["wait"], new OrderTarget(position, hull)));
}
if (contextualOrders.None(order => order.Category != OrderCategory.Movement) && characters.Any(c => c != Character.Controlled))
{
orderIdentifier = "follow";
if (contextualOrders.None(order => order.Identifier.Equals(orderIdentifier)))
if (contextualOrders.None(order => order.Identifier.Equals("follow")))
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs[orderIdentifier], null, orderGiver: Character.Controlled));
contextualOrders.Add(OrderPrefab.Prefabs["follow"].CreateInstance(OrderPrefab.OrderTargetType.Entity));
}
}
// Show 'dismiss' order only when there are crew members with active orders
orderIdentifier = "dismissed";
if (contextualOrders.None(order => order.Identifier.Equals(orderIdentifier)) && characters.Any(c => !c.IsDismissed))
if (contextualOrders.None(order => order.IsDismissal) && characters.Any(c => !c.IsDismissed))
{
contextualOrders.Add(new Order(OrderPrefab.Prefabs[orderIdentifier], null, orderGiver: Character.Controlled));
contextualOrders.Add(OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity));
}
}
contextualOrders.RemoveAll(o => !IsOrderAvailable(o));
@@ -0,0 +1,26 @@
namespace Barotrauma
{
internal partial class Wallet
{
public bool IsOwnWallet =>
GameMain.GameSession?.Campaign switch
{
null => false,
SinglePlayerCampaign spCampaign => this == spCampaign.Bank,
MultiPlayerCampaign mpCampaign => this == mpCampaign.PersonalWallet,
_ => false
};
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged)
{
CampaignMode campaign = GameMain.GameSession?.Campaign;
WalletChangedData data = new WalletChangedData
{
BalanceChanged = balanceChanged,
RewardDistributionChanged = rewardChanged,
};
campaign?.OnMoneyChanged.Invoke(new WalletChangedEvent(this, data, CreateWalletInfo()));
}
}
}
@@ -63,6 +63,8 @@ namespace Barotrauma
}
}
public virtual Wallet Wallet => GetWallet();
public override void ShowStartMessage()
{
foreach (Mission mission in Missions.ToList())
@@ -310,7 +312,7 @@ namespace Barotrauma
if (ShowCampaignUI || ForceMapUI)
{
campaignUIContainer?.AddToGUIUpdateList();
if (CampaignUI?.UpgradeStore?.HoveredItem != null)
if (CampaignUI?.UpgradeStore?.HoveredEntity != null)
{
if (CampaignUI.SelectedTab != InteractionType.Upgrade) { return; }
CampaignUI?.UpgradeStore?.ItemInfoFrame.AddToGUIUpdateList(order: 1);
@@ -21,7 +21,7 @@ namespace Barotrauma
private UInt16 pendingSaveID = 1;
public UInt16 PendingSaveID
{
get
get
{
return pendingSaveID;
}
@@ -34,6 +34,14 @@ namespace Barotrauma
}
}
public Wallet PersonalWallet => Character.Controlled?.Wallet ?? Wallet.Invalid;
public override Wallet Wallet => GetWallet();
public override Wallet GetWallet(Client client = null)
{
return PersonalWallet;
}
public static void StartCampaignSetup(IEnumerable<string> saveFiles)
{
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
@@ -195,6 +203,11 @@ namespace Barotrauma
yield return CoroutineStatus.Running;
}
if (GameMain.Client == null)
{
yield return CoroutineStatus.Failure;
}
if (GameMain.Client.LateCampaignJoin)
{
GameMain.Client.LateCampaignJoin = false;
@@ -618,7 +631,6 @@ namespace Barotrauma
bool forceMapUI = msg.ReadBoolean();
int money = msg.ReadInt32();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
@@ -812,12 +824,10 @@ namespace Barotrauma
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
bool shouldRefresh = campaign.Money != money ||
campaign.PurchasedHullRepairs != purchasedHullRepairs ||
bool shouldRefresh = campaign.PurchasedHullRepairs != purchasedHullRepairs ||
campaign.PurchasedItemRepairs != purchasedItemRepairs ||
campaign.PurchasedLostShuttles != purchasedLostShuttles;
campaign.Money = money;
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
@@ -896,6 +906,43 @@ namespace Barotrauma
}
}
public void ClientReadMoney(IReadMessage inc)
{
NetWalletUpdate update = INetSerializableStruct.Read<NetWalletUpdate>(inc);
foreach (NetWalletTransaction transaction in update.Transactions)
{
WalletInfo info = transaction.Info;
switch (transaction.CharacterID)
{
case Some<ushort> { Value: var charID}:
{
Character targetCharacter = Character.CharacterList?.FirstOrDefault(c => c.ID == charID);
if (targetCharacter is null) { break; }
Wallet wallet = targetCharacter.Wallet;
wallet.Balance = info.Balance;
wallet.RewardDistribution = info.RewardDistribution;
TryInvokeEvent(wallet, transaction.ChangedData, info);
break;
}
case None<ushort> _:
{
Bank.Balance = info.Balance;
TryInvokeEvent(Bank, transaction.ChangedData, info);
break;
}
}
}
void TryInvokeEvent(Wallet wallet, WalletChangedData data, WalletInfo info)
{
if (data.BalanceChanged.IsSome() || data.RewardDistributionChanged.IsSome())
{
OnMoneyChanged.Invoke(new WalletChangedEvent(wallet, data, info));
}
}
}
public override void Save(XElement element)
{
//do nothing, the clients get the save files from the server
@@ -908,10 +955,10 @@ namespace Barotrauma
string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, "gamesession.xml");
XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath);
if (doc == null)
if (doc == null)
{
DebugConsole.ThrowError($"Failed to load the state of a multiplayer campaign. Could not open the file \"{gamesessionDocPath}\".");
return;
return;
}
Load(doc.Root.Element("MultiPlayerCampaign"));
GameMain.GameSession.OwnedSubmarines = SaveUtil.LoadOwnedSubmarines(doc, out SubmarineInfo selectedSub);
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -108,6 +109,9 @@ namespace Barotrauma
case "pets":
petsElement = subElement;
break;
case Wallet.LowerCaseSaveElementName:
Bank = new Wallet(subElement);
break;
case "stats":
LoadStats(subElement);
break;
@@ -121,7 +125,15 @@ namespace Barotrauma
InitUI();
Money = element.GetAttributeInt("money", 0);
int oldMoney = element.GetAttributeInt("money", 0);
if (oldMoney > 0)
{
Bank = new Wallet
{
Balance = oldMoney
};
}
PurchasedLostShuttles = element.GetAttributeBool("purchasedlostshuttles", false);
PurchasedHullRepairs = element.GetAttributeBool("purchasedhullrepairs", false);
PurchasedItemRepairs = element.GetAttributeBool("purchaseditemrepairs", false);
@@ -729,7 +741,6 @@ namespace Barotrauma
public override void Save(XElement element)
{
XElement modeElement = new XElement("SinglePlayerCampaign",
new XAttribute("money", Money),
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
@@ -756,13 +767,14 @@ namespace Barotrauma
petsElement = new XElement("pets");
PetBehavior.SavePets(petsElement);
modeElement.Add(petsElement);
modeElement.Add(petsElement);
CrewManager.Save(modeElement);
CampaignMetadata.Save(modeElement);
Map.Save(modeElement);
CargoManager?.SavePurchasedItems(modeElement);
UpgradeManager?.Save(modeElement);
modeElement.Add(Bank.Save());
element.Add(modeElement);
}
}
@@ -24,7 +24,7 @@ namespace Barotrauma
public TestGameMode(GameModePreset preset) : base(preset)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs.OrderBy(p => p.Identifier))
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
@@ -190,7 +190,7 @@ namespace Barotrauma
}
else
{
tabMenu.Update();
tabMenu.Update(deltaTime);
if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) &&
!(GUI.KeyboardDispatcher.Subscriber is GUITextBox))
{
@@ -181,6 +181,11 @@ namespace Barotrauma
}
}
private void OnMoneyChanged(WalletChangedEvent e)
{
if (e.Wallet.IsOwnWallet) { OnUpdate?.Invoke(); }
}
// if you have more than 5000 ping there are probably more important things to worry about but hey just in case
private static DateTimeOffset GetTimeout() => DateTimeOffset.Now.AddSeconds(5).AddMilliseconds(GetPing());
@@ -241,6 +246,7 @@ namespace Barotrauma
private void HealRequestReceived(IReadMessage inc)
{
NetHealRequest request = INetSerializableStruct.Read<NetHealRequest>(inc);
if (request.Result == HealRequestResult.Success)
{
HealAllPending(force: true);
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
@@ -15,7 +16,6 @@ namespace Barotrauma
private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
private readonly SubmarineInfo sub;
private readonly List<Mission> selectedMissions;
private readonly Location startLocation, endLocation;
@@ -30,11 +30,8 @@ namespace Barotrauma
public GUIComponent Frame { get; private set; }
public RoundSummary(SubmarineInfo sub, GameMode gameMode, IEnumerable<Mission> selectedMissions, Location startLocation, Location endLocation)
public RoundSummary(GameMode gameMode, IEnumerable<Mission> selectedMissions, Location startLocation, Location endLocation)
{
this.sub = sub;
this.gameMode = gameMode;
this.selectedMissions = selectedMissions.ToList();
this.startLocation = startLocation;
@@ -320,6 +317,16 @@ namespace Barotrauma
{
LocalizedString rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), RichString.Rich(displayedMission.GetMissionRewardText(Submarine.MainSub)));
if (Character.Controlled is { } controlled)
{
var (share, percentage) = Mission.GetRewardShare(controlled.Wallet.RewardDistribution, Mission.GetSalaryEligibleCrew(), Option<int>.Some(reward));
if (share > 0)
{
string shareFormatted = string.Format(CultureInfo.InvariantCulture, "{0:N0}", share);
RichString yourShareString = RichString.Rich(TextManager.GetWithVariables("crewwallet.missionreward.get", ("[money]", $"{shareFormatted}"), ("[share]", $"{percentage}")));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), yourShareString);
}
}
}
if (displayedMission != missionsToDisplay.Last())
@@ -407,7 +414,7 @@ namespace Barotrauma
CreatePathUnlockElement(locationFrame, null, startLocation);
}
foreach (Faction faction in campaignMode.Factions)
foreach (Faction faction in campaignMode.Factions.OrderBy(f => f.Prefab.MenuOrder).ThenBy(f => f.Prefab.Name))
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
@@ -728,7 +735,7 @@ namespace Barotrauma
if (Math.Abs(reputationChange) > 0)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
string colorStr = XMLExtensions.ColorToString(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red);
string colorStr = XMLExtensions.ToStringHex(reputationChange > 0 ? GUIStyle.Green : GUIStyle.Red);
var richText = RichString.Rich($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)");
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
richText,
@@ -283,9 +283,9 @@ namespace Barotrauma.Items.Components
}
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public override void ClientEventRead(IReadMessage msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
base.ClientEventRead(msg, sendingTime);
bool open = msg.ReadBoolean();
bool broken = msg.ReadBoolean();
@@ -54,7 +54,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
CurrPowerConsumption = powerConsumption;
charging = true;
@@ -68,7 +68,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Tainted = msg.ReadBoolean();
if (Tainted)
@@ -156,7 +156,7 @@ namespace Barotrauma.Items.Components
private readonly object mutex = new object();
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Health = msg.ReadRangedSingle(0, MaxHealth, 8);
int startOffset = msg.ReadRangedInteger(-1, MaximumVines);
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Diagnostics.Tracing;
namespace Barotrauma.Items.Components
{
@@ -58,18 +59,23 @@ namespace Barotrauma.Items.Components
GUI.DrawRectangle(spriteBatch, new Vector2(attachPos.X - 2, -attachPos.Y - 2), Vector2.One * 5, GUIStyle.Red, thickness: 3);
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<EventData>(data, out _);
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (!attachable || body == null) { return; }
var eventData = ExtractEventData<EventData>(extraData);
Vector2 attachPos = (Vector2)extraData[2];
Vector2 attachPos = eventData.AttachPos;
msg.Write(attachPos.X);
msg.Write(attachPos.Y);
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public override void ClientEventRead(IReadMessage msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
base.ClientEventRead(msg, sendingTime);
bool readAttachData = msg.ReadBoolean();
if (!readAttachData) { return; }
@@ -96,7 +96,7 @@ namespace Barotrauma.Items.Components
ContentXElement getElementFromList(List<ContentXElement> list, int index)
=> CharacterInfo.IsValidIndex(index, list)
? list[index]
: characterInfo.GetRandomElement(list);
: null;
var disguisedHairElement = getElementFromList(disguisedHairs, disguisedHairIndex);
var disguisedBeardElement = getElementFromList(disguisedBeards, disguisedBeardIndex);
@@ -566,14 +566,14 @@ namespace Barotrauma.Items.Components
protected virtual void CreateGUI() { }
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
protected void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
if (delayedCorrectionCoroutine != null) { CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); }
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(buffer, sendingTime, waitForMidRoundSync));
}
private IEnumerable<CoroutineStatus> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
private IEnumerable<CoroutineStatus> DoDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
{
while (GameMain.Client != null &&
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
@@ -587,7 +587,7 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
((IServerSerializable)this).ClientRead(type, buffer, sendingTime);
((IServerSerializable)this).ClientEventRead(buffer, sendingTime);
correctionTimer = 0.0f;
delayedCorrectionCoroutine = null;
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
textBlock.DrawManually(spriteBatch);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Text = msg.ReadString();
}
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class LevelResource : ItemComponent, IServerSerializable
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
deattachTimer = msg.ReadSingle();
if (deattachTimer >= DeattachDuration)
@@ -56,8 +56,9 @@ namespace Barotrauma.Items.Components
}
else
{
Light.Position = item.DrawPosition;
if (item.Submarine != null) { Light.Position -= item.Submarine.DrawPosition; }
Vector2 pos = item.DrawPosition;
if (item.Submarine != null) { pos -= item.Submarine.DrawPosition; }
Light.Position = pos;
}
PhysicsBody body = Light.ParentBody;
if (body != null)
@@ -120,7 +121,7 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
IsActive = msg.ReadBoolean();
lastReceivedState = IsActive;
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
}
#endif
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
State = msg.ReadBoolean();
ushort userID = msg.ReadUInt16();
@@ -251,12 +251,12 @@ namespace Barotrauma.Items.Components
return true;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write(pendingState);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
ushort userID = msg.ReadUInt16();
Character user = userID == Entity.NullEntityID ? null : Entity.FindEntityByID(userID) as Character;
@@ -156,17 +156,17 @@ namespace Barotrauma.Items.Components
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
//targetForce can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger((int)(targetForce / 10.0f), -10, 10);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(5 + 16), sendingTime);
StartDelayedCorrection(msg.ExtractBits(5 + 16), sendingTime);
return;
}
@@ -705,13 +705,13 @@ namespace Barotrauma.Items.Components
requiredTimeBlock.Text = ToolBox.SecondsToReadableTime(timeUntilReady > 0.0f ? timeUntilReady : requiredTime);
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
msg.Write(recipeHash);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
FabricatorState newState = (FabricatorState)msg.ReadByte();
float newTimeUntilReady = msg.ReadSingle();
@@ -216,14 +216,14 @@ namespace Barotrauma.Items.Components
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, 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);
msg.Write(IsActive);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
int msgStartPos = msg.BitPosition;
@@ -244,7 +244,7 @@ namespace Barotrauma.Items.Components
{
int msgLength = msg.BitPosition - msgStartPos;
msg.BitPosition = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
return;
}
@@ -608,9 +608,10 @@ namespace Barotrauma.Items.Components
Vector2 pointerPos = pos - new Vector2(0, 30) * scale;
float scaleMultiplier = 0.95f;
if (optimalRangeNormalized.X == optimalRangeNormalized.Y)
{
sectorSprite.Draw(spriteBatch, pointerPos, GUIStyle.Red, MathHelper.PiOver2, scale);
sectorSprite.Draw(spriteBatch, pointerPos, GUIStyle.Red, MathHelper.PiOver2, scale * scaleMultiplier);
}
else
{
@@ -619,7 +620,6 @@ namespace Barotrauma.Items.Components
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pointerPos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
float scaleMultiplier = 0.95f;
sectorSprite.Draw(spriteBatch, pointerPos, optimalRangeColor, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pointerPos, offRangeColor, optimalSectorRad.X, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pointerPos, warningColor, allowedSectorRad.X, scale * scaleMultiplier);
@@ -711,7 +711,7 @@ namespace Barotrauma.Items.Components
tempRangeIndicator?.Remove();
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write(autoTemp);
msg.Write(PowerOn);
@@ -721,11 +721,11 @@ namespace Barotrauma.Items.Components
correctionTimer = CorrectionDelay;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime);
StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime);
return;
}
@@ -766,7 +766,7 @@ namespace Barotrauma.Items.Components
if (activePingsCount == 0) { disruptedDirections.Clear(); }
foreach (AITarget t in AITarget.List)
{
if (t.Entity is Character c && c.Params.HideInSonar) { continue; }
if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; }
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
@@ -1264,7 +1264,7 @@ namespace Barotrauma.Items.Components
}
foreach (AITarget aiTarget in AITarget.List)
{
float disruption = aiTarget.Entity is Character c ? c.Params.SonarDisruption : aiTarget.SonarDisruption;
float disruption = aiTarget.Entity is Character c && !c.IsUnconscious ? c.Params.SonarDisruption : aiTarget.SonarDisruption;
if (disruption <= 0.0f || aiTarget.InDetectable) { continue; }
float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource);
if (distSqr > worldPingRadiusSqr) { continue; }
@@ -1413,7 +1413,7 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; }
if (c.Params.HideInSonar) { continue; }
if (!c.IsUnconscious && c.Params.HideInSonar) { continue; }
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; }
if (c.AnimController.SimplePhysicsEnabled)
@@ -1742,7 +1742,7 @@ namespace Barotrauma.Items.Components
MineralClusters = null;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write(currentMode == Mode.Active);
if (currentMode == Mode.Active)
@@ -1758,7 +1758,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
int msgStartPos = msg.BitPosition;
@@ -1782,7 +1782,7 @@ namespace Barotrauma.Items.Components
{
int msgLength = msg.BitPosition - msgStartPos;
msg.BitPosition = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
return;
}
@@ -822,7 +822,7 @@ namespace Barotrauma.Items.Components
Connection dockingConnection = item.Connections?.FirstOrDefault(c => c.Name == "toggle_docking");
if (dockingConnection != null)
{
connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection, ignoreInactiveRelays: true);
connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection, ignoreInactiveRelays: true, allowTraversingBackwards: false);
}
checkConnectedPortsTimer = CheckConnectedPortsInterval;
}
@@ -894,7 +894,7 @@ namespace Barotrauma.Items.Components
pathFinder = null;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.Write(AutoPilot);
msg.Write(dockingNetworkMessagePending);
@@ -921,7 +921,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
int msgStartPos = msg.BitPosition;
@@ -962,7 +962,7 @@ namespace Barotrauma.Items.Components
{
int msgLength = (int)(msg.BitPosition - msgStartPos);
msg.BitPosition = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
return;
}
@@ -163,16 +163,16 @@ namespace Barotrauma.Items.Components
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f);
}
public void ClientWrite(IWriteMessage msg, object[] extraData)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData)
{
msg.WriteRangedInteger((int)(rechargeSpeed / MaxRechargeSpeed * 10), 0, 10);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(4 + 8), sendingTime);
StartDelayedCorrection(msg.ExtractBits(4 + 8), sendingTime);
return;
}
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
{
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool launch = msg.ReadBoolean();
if (launch)
@@ -427,7 +427,7 @@ namespace Barotrauma.Items.Components
item.CreateClientEvent(this);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle();
@@ -446,7 +446,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
msg.Write(qteSuccess);
@@ -196,7 +196,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
snapped = msg.ReadBoolean();
@@ -16,7 +16,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool wasScanCompletedPreviously = IsScanCompleted;
scanTimer = msg.ReadSingle();
@@ -76,13 +76,14 @@ namespace Barotrauma.Items.Components
UserData = i,
OnClicked = (button, userData) =>
{
int signalIndex = (int)userData;
if (GameMain.IsSingleplayer)
{
SendSignal((int)userData, Character.Controlled);
SendSignal(signalIndex, Character.Controlled);
}
else
{
item.CreateClientEvent(this, new object[] { userData });
item.CreateClientEvent(this, new EventData(signalIndex));
}
return true;
}
@@ -104,12 +105,12 @@ namespace Barotrauma.Items.Components
Container.Inventory.RectTransform = containerHolder.RectTransform;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
Write(msg, extraData);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, isServerMessage: true);
}
@@ -139,7 +139,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
if (GameMain.Client.MidRoundSyncing)
{
@@ -161,7 +161,7 @@ namespace Barotrauma.Items.Components
}
int msgLength = (int)(msg.BitPosition - msgStartPos);
msg.BitPosition = (int)msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true);
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true);
}
else
{
@@ -137,13 +137,14 @@ namespace Barotrauma.Items.Components
};
btn.OnClicked += (_, userdata) =>
{
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;;
if (GameMain.Client == null)
{
ButtonClicked(userdata as CustomInterfaceElement);
ButtonClicked(btnElement);
}
else
{
GameMain.Client.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), userdata as CustomInterfaceElement });
item.CreateClientEvent(this, new EventData(btnElement));
}
return true;
};
@@ -301,7 +302,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
for (int i = 0; i < customInterfaceElementList.Count; i++)
@@ -323,12 +324,12 @@ 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]);
}
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class MemoryComponent : ItemComponent
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Value = msg.ReadString();
}
@@ -7,6 +7,16 @@ namespace Barotrauma.Items.Components
{
partial class Terminal : ItemComponent, IClientSerializable, IServerSerializable
{
private readonly struct ClientEventData : IEventData
{
public readonly string Text;
public ClientEventData(string text)
{
Text = text;
}
}
private GUIListBox historyBox;
private GUITextBlock fillerBlock;
private GUITextBox inputBox;
@@ -42,7 +52,7 @@ namespace Barotrauma.Items.Components
}
else
{
item.CreateClientEvent(this, new object[] { text });
item.CreateClientEvent(this, new ClientEventData(text));
}
textBox.Text = string.Empty;
return true;
@@ -133,17 +143,15 @@ namespace Barotrauma.Items.Components
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (extraData is null) { return; }
if (extraData[2] is string str)
if (TryExtractEventData(extraData, out ClientEventData eventData))
{
msg.Write(str);
msg.Write(eventData.Text);
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
SendOutput(msg.ReadString());
}
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Channel = msg.ReadRangedInteger(MinChannel, MaxChannel);
}
@@ -11,6 +11,16 @@ namespace Barotrauma.Items.Components
{
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{
private readonly struct ClientEventData : IEventData
{
public readonly int NodeCount;
public ClientEventData(int nodeCount)
{
NodeCount = nodeCount;
}
}
public static Color higlightColor = Color.LightGreen;
public static Color editorHighlightColor = Color.Yellow;
public static Color editorSelectedColor = Color.Red;
@@ -555,7 +565,7 @@ namespace Barotrauma.Items.Components
return false;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent));
int nodeCount = msg.ReadRangedInteger(0, MaxNodesPerNetworkEvent);
@@ -586,9 +596,13 @@ namespace Barotrauma.Items.Components
(item.ParentInventory is CharacterInventory characterInventory && ((characterInventory.Owner as Character)?.HasEquippedItem(item) ?? false));
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public override bool ValidateEventData(NetEntityEvent.IData data)
=> TryExtractEventData<ClientEventData>(data, out _);
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
int nodeCount = (int)extraData[2];
var eventData = ExtractEventData<ClientEventData>(extraData);
int nodeCount = eventData.NodeCount;
msg.Write((byte)nodeCount);
if (nodeCount > 0)
{
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
partial class TriggerComponent : ItemComponent, IServerSerializable
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
CurrentForceFluctuation = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
@@ -44,7 +44,9 @@ namespace Barotrauma.Items.Components
private readonly Dictionary<string, Widget> widgets = new Dictionary<string, Widget>();
private float prevAngle;
private float currentBarrelSpin = 0f;
private bool flashLowPower;
private bool flashNoAmmo, flashLoaderBroken;
private float flashTimer;
@@ -716,7 +718,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
UInt16 projectileID = msg.ReadUInt16();
float newTargetRotation = msg.ReadRangedSingle(minRotation, maxRotation, 16);
@@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool isDocked = msg.ReadBoolean();
@@ -1598,7 +1598,7 @@ namespace Barotrauma
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
{
containedState = itemContainer.Inventory.slots[0].ItemCount / (float)maxStackSize;
containedState = itemContainer.Inventory.slots[0].Items.Count / (float)maxStackSize;
}
}
}
@@ -1702,7 +1702,7 @@ namespace Barotrauma
}
if (maxStackSize > 1 && inventory != null)
{
int itemCount = slot.MouseOn() ? inventory.slots[slotIndex].ItemCount : inventory.slots[slotIndex].Items.Where(it => !DraggingItems.Contains(it)).Count();
int itemCount = slot.MouseOn() ? inventory.slots[slotIndex].Items.Count : inventory.slots[slotIndex].Items.Where(it => !DraggingItems.Contains(it)).Count();
if (item.IsFullCondition || MathUtils.NearlyEqual(item.Condition, 0.0f) || itemCount > 1)
{
Vector2 stackCountPos = new Vector2(rect.Right, rect.Bottom);
@@ -1795,20 +1795,10 @@ namespace Barotrauma
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
UInt16 lastEventID = msg.ReadUInt16();
byte slotCount = msg.ReadByte();
receivedItemIDs = new List<ushort>[slotCount];
for (int i = 0; i < slotCount; i++)
{
receivedItemIDs[i] = new List<ushort>();
int itemCount = msg.ReadRangedInteger(0, MaxStackSize);
for (int j = 0; j < itemCount; j++)
{
receivedItemIDs[i].Add(msg.ReadUInt16());
}
}
SharedRead(msg, out receivedItemIDs);
//delay applying the new state if less than 1 second has passed since this client last sent a state to the server
//prevents the inventory from briefly reverting to an old state if items are moved around in quick succession
@@ -1895,7 +1885,7 @@ namespace Barotrauma
receivedItemIDs = null;
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
SharedWrite(msg, extraData);
syncItemsDelay = 1.0f;
@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
@@ -1262,25 +1263,19 @@ namespace Barotrauma
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
if (type == ServerNetObject.ENTITY_POSITION)
{
ClientReadPosition(type, msg, sendingTime);
return;
}
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);
switch (eventType)
{
case NetEntityEvent.Type.ComponentState:
case EventType.ComponentState:
{
int componentIndex = msg.ReadRangedInteger(0, components.Count - 1);
if (components[componentIndex] is IServerSerializable serverSerializable)
{
serverSerializable.ClientRead(type, msg, sendingTime);
serverSerializable.ClientEventRead(msg, sendingTime);
}
else
{
@@ -1288,12 +1283,12 @@ namespace Barotrauma
}
}
break;
case NetEntityEvent.Type.InventoryState:
case EventType.InventoryState:
{
int containerIndex = msg.ReadRangedInteger(0, components.Count - 1);
if (components[containerIndex] is ItemContainer container)
{
container.Inventory.ClientRead(type, msg, sendingTime);
container.Inventory.ClientEventRead(msg, sendingTime);
}
else
{
@@ -1301,7 +1296,7 @@ namespace Barotrauma
}
}
break;
case NetEntityEvent.Type.Status:
case EventType.Status:
float prevCondition = condition;
condition = msg.ReadSingle();
if (prevCondition > 0.0f && condition <= 0.0f)
@@ -1314,10 +1309,10 @@ namespace Barotrauma
}
SetActiveSprite();
break;
case NetEntityEvent.Type.AssignCampaignInteraction:
case EventType.AssignCampaignInteraction:
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
break;
case NetEntityEvent.Type.ApplyStatusEffect:
case EventType.ApplyStatusEffect:
{
ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1);
byte componentIndex = msg.ReadByte();
@@ -1347,10 +1342,10 @@ namespace Barotrauma
}
}
break;
case NetEntityEvent.Type.ChangeProperty:
case EventType.ChangeProperty:
ReadPropertyChange(msg, false);
break;
case NetEntityEvent.Type.Upgrade:
case EventType.Upgrade:
Identifier identifier = msg.ReadIdentifier();
byte level = msg.ReadByte();
if (UpgradePrefab.Find(identifier) is { } upgradePrefab)
@@ -1371,51 +1366,65 @@ namespace Barotrauma
AddUpgrade(upgrade, false);
}
break;
case NetEntityEvent.Type.Invalid:
break;
default:
throw new Exception($"Malformed incoming item event: unsupported event type {eventType}");
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type))
Exception error(string reason)
{
return;
string errorMsg = $"Failed to write a network event for the item \"{Name}\" - {reason}";
GameAnalyticsManager.AddErrorEventOnce($"Item.ClientWrite:{Name}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return new Exception(errorMsg);
}
if (extraData is null) { throw error("event data was null"); }
if (!(extraData is IEventData eventData)) { throw error($"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0];
msg.WriteRangedInteger((int)eventType, 0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
switch (eventType)
EventType eventType = eventData.EventType;
msg.WriteRangedInteger((int)eventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData)
{
case NetEntityEvent.Type.ComponentState:
int componentIndex = (int)extraData[1];
case ComponentStateEventData componentStateEventData:
{
var component = componentStateEventData.Component;
if (component is null) { throw error("component was null"); }
if (!(component is IClientSerializable clientSerializable)) { throw error($"component was not {nameof(IClientSerializable)}"); }
int componentIndex = components.IndexOf(component);
if (componentIndex < 0) { throw error("component did not belong to item"); }
msg.WriteRangedInteger(componentIndex, 0, components.Count - 1);
(components[componentIndex] as IClientSerializable).ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.InventoryState:
int containerIndex = (int)extraData[1];
clientSerializable.ClientEventWrite(msg, extraData);
}
break;
case InventoryStateEventData inventoryStateEventData:
{
var container = inventoryStateEventData.Component;
if (container is null) { throw error("container was null"); }
int containerIndex = components.IndexOf(container);
if (containerIndex < 0) { throw error("container did not belong to item"); }
msg.WriteRangedInteger(containerIndex, 0, components.Count - 1);
(components[containerIndex] as ItemContainer).Inventory.ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.Treatment:
UInt16 characterID = (UInt16)extraData[1];
Limb targetLimb = (Limb)extraData[2];
container.Inventory.ClientEventWrite(msg, extraData);
}
break;
case TreatmentEventData treatmentEventData:
Character targetCharacter = treatmentEventData.TargetCharacter;
Character targetCharacter = FindEntityByID(characterID) as Character;
msg.Write(characterID);
msg.Write(targetCharacter == null ? (byte)255 : (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb));
msg.Write(targetCharacter.ID);
msg.Write(treatmentEventData.LimbIndex);
break;
case NetEntityEvent.Type.ChangeProperty:
WritePropertyChange(msg, extraData, true);
case ChangePropertyEventData changePropertyEventData:
WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: true);
editingHUDRefreshTimer = 1.0f;
break;
case NetEntityEvent.Type.Combine:
UInt16 combineTargetID = (UInt16)extraData[1];
msg.Write(combineTargetID);
case CombineEventData combineEventData:
Item combineTarget = combineEventData.CombineTarget;
msg.Write(combineTarget.ID);
break;
default:
throw error($"Unsupported event type {eventData.GetType().Name}");
}
msg.WritePadBits();
}
partial void UpdateNetPosition(float deltaTime)
@@ -1451,7 +1460,7 @@ namespace Barotrauma
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
}
public void ClientReadPosition(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientReadPosition(IReadMessage msg, float sendingTime)
{
if (body == null)
{
@@ -1465,7 +1474,7 @@ namespace Barotrauma
return;
}
var posInfo = body.ClientRead(type, msg, sendingTime, parentDebugName: Name);
var posInfo = body.ClientRead(msg, sendingTime, parentDebugName: Name);
msg.ReadPadBits();
if (posInfo != null)
{
@@ -1510,24 +1519,18 @@ namespace Barotrauma
}
public void CreateClientEvent<T>(T ic) where T : ItemComponent, IClientSerializable
=> CreateClientEvent(ic, null);
public void CreateClientEvent<T>(T ic, ItemComponent.IEventData extraData) where T : ItemComponent, IClientSerializable
{
if (GameMain.Client == null) return;
if (GameMain.Client == null) { return; }
int index = components.IndexOf(ic);
if (index == -1) return;
#warning TODO: this should throw an exception
if (!components.Contains(ic)) { return; }
GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ComponentState, index });
}
public void CreateClientEvent<T>(T ic, object[] extraData) where T : ItemComponent, IClientSerializable
{
if (GameMain.Client == null) return;
int index = components.IndexOf(ic);
if (index == -1) return;
object[] data = new object[] { NetEntityEvent.Type.ComponentState, index }.Concat(extraData).ToArray();
GameMain.Client.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.Client.CreateEntityEvent(this, eventData);
}
public static Item ReadSpawnData(IReadMessage msg, bool spawn = true)
@@ -1576,6 +1579,36 @@ namespace Barotrauma
bool allowStealing = msg.ReadBoolean();
int quality = msg.ReadRangedInteger(0, Items.Components.Quality.MaxQuality);
byte teamID = msg.ReadByte();
bool hasIdCard = msg.ReadBoolean();
string ownerName = "", ownerTags = "";
int ownerBeardIndex = -1, ownerHairIndex = -1, ownerMoustacheIndex = -1, ownerFaceAttachmentIndex = -1;
Color ownerHairColor = Microsoft.Xna.Framework.Color.White,
ownerFacialHairColor = Microsoft.Xna.Framework.Color.White,
ownerSkinColor = Microsoft.Xna.Framework.Color.White;
Identifier ownerJobId = Identifier.Empty;
Vector2 ownerSheetIndex = Vector2.Zero;
if (hasIdCard)
{
ownerName = msg.ReadString();
ownerTags = msg.ReadString();
ownerBeardIndex = msg.ReadByte() - 1;
ownerHairIndex = msg.ReadByte() - 1;
ownerMoustacheIndex = msg.ReadByte() - 1;
ownerFaceAttachmentIndex = msg.ReadByte() - 1;
ownerHairColor = msg.ReadColorR8G8B8();
ownerFacialHairColor = msg.ReadColorR8G8B8();
ownerSkinColor = msg.ReadColorR8G8B8();
ownerJobId = msg.ReadIdentifier();
int x = msg.ReadByte();
int y = msg.ReadByte();
ownerSheetIndex = (x, y);
}
bool tagsChanged = msg.ReadBoolean();
string tags = "";
if (tagsChanged)
@@ -1587,6 +1620,7 @@ namespace Barotrauma
tags = string.Join(',',itemPrefab.Tags.Where(t => !removedTags.Contains(t)).Concat(addedTags));
}
}
bool isNameTag = msg.ReadBoolean();
string writtenName = "";
if (isNameTag)
@@ -1672,6 +1706,17 @@ namespace Barotrauma
foreach (IdCard idCard in item.GetComponents<IdCard>())
{
idCard.TeamID = (CharacterTeamType)teamID;
idCard.OwnerName = ownerName;
idCard.OwnerTags = ownerTags;
idCard.OwnerBeardIndex = ownerBeardIndex;
idCard.OwnerHairIndex = ownerHairIndex;
idCard.OwnerMoustacheIndex = ownerMoustacheIndex;
idCard.OwnerFaceAttachmentIndex = ownerFaceAttachmentIndex;
idCard.OwnerHairColor = ownerHairColor;
idCard.OwnerFacialHairColor = ownerFacialHairColor;
idCard.OwnerSkinColor = ownerSkinColor;
idCard.OwnerJobId = ownerJobId;
idCard.OwnerSheetIndex = ownerSheetIndex;
}
if (descriptionChanged) { item.Description = itemDesc; }
if (tagsChanged) { item.Tags = tags; }
@@ -0,0 +1,35 @@
using System;
namespace Barotrauma
{
partial class Item
{
private readonly struct CombineEventData : IEventData
{
public EventType EventType => EventType.Combine;
public readonly Item CombineTarget;
public CombineEventData(Item combineTarget)
{
CombineTarget = combineTarget;
}
}
private readonly struct TreatmentEventData : IEventData
{
public EventType EventType => EventType.Treatment;
public readonly Character TargetCharacter;
public readonly Limb TargetLimb;
public byte LimbIndex
=> TargetCharacter?.AnimController?.Limbs is { } limbs
? (byte)Array.IndexOf(limbs, TargetLimb)
: byte.MaxValue;
public TreatmentEventData(Character targetCharacter, Limb targetLimb)
{
TargetCharacter = targetCharacter;
TargetLimb = targetLimb;
}
}
}
}
@@ -30,7 +30,7 @@ namespace Barotrauma
private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources;
private NetworkFireSource[] remoteFireSources = null;
private readonly List<BackgroundSection> remoteBackgroundSections = new List<BackgroundSection>();
private readonly List<RemoteDecal> remoteDecals = new List<RemoteDecal>();
@@ -175,15 +175,16 @@ namespace Barotrauma
{
if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any())
{
GameMain.NetworkMember?.CreateEntityEvent(this);
GameMain.NetworkMember?.CreateEntityEvent(this, new StatusEventData());
}
foreach (Decal decal in pendingDecalUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { decal });
GameMain.NetworkMember?.CreateEntityEvent(this, new DecalEventData(decal));
}
pendingDecalUpdates.Clear();
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate });
GameMain.NetworkMember?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate));
}
pendingSectionUpdates.Clear();
networkUpdatePending = false;
@@ -595,132 +596,101 @@ namespace Barotrauma
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
if (extraData == null)
{
msg.WriteRangedInteger(0, 0, 2);
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed hull event: expected {nameof(Hull)}.{nameof(IEventData)}"); }
msg.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{
msg.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);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
}
}
}
else if (extraData[0] is Decal decal)
msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData)
{
msg.WriteRangedInteger(1, 0, 2);
int decalIndex = decals.IndexOf(decal);
msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex));
msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8);
}
else
{
msg.WriteRangedInteger(2, 0, 2);
int sectorToUpdate = (int)extraData[0];
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
msg.WriteRangedInteger(sectorToUpdate, 0, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
msg.Write(BackgroundSections[i].Color.PackedValue);
}
case StatusEventData statusEventData:
SharedStatusWrite(msg);
break;
case BackgroundSectionsEventData backgroundSectionsEventData:
SharedBackgroundSectionsWrite(msg, backgroundSectionsEventData);
break;
case DecalEventData decalEventData:
var decal = decalEventData.Decal;
int decalIndex = decals.IndexOf(decal);
msg.Write((byte)(decalIndex < 0 ? 255 : decalIndex));
msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8);
break;
default:
throw new Exception($"Malformed hull event: did not expect {eventData.GetType().Name}");
}
}
public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool isBallastFloraUpdate = message.ReadBoolean();
if (isBallastFloraUpdate)
EventType eventType = (EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventType)
{
BallastFloraBehavior.NetworkHeader header = (BallastFloraBehavior.NetworkHeader) message.ReadByte();
if (header == BallastFloraBehavior.NetworkHeader.Spawn)
{
Identifier identifier = message.ReadIdentifier();
float x = message.ReadSingle();
float y = message.ReadSingle();
BallastFlora = new BallastFloraBehavior(this, BallastFloraPrefab.Find(identifier), new Vector2(x, y), firstGrowth: true)
{
PowerConsumptionTimer = message.ReadSingle()
};
}
else
{
BallastFlora?.ClientRead(message, header);
}
return;
}
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
bool hasFireSources = message.ReadBoolean();
remoteFireSources = new List<Vector3>();
if (hasFireSources)
{
int fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
{
remoteFireSources.Add(new Vector3(
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
message.ReadRangedSingle(0.0f, 1.0f, 8)));
}
}
bool hasExtraData = message.ReadBoolean();
if (hasExtraData)
{
bool hasSectionUpdate = message.ReadBoolean();
if (hasSectionUpdate)
{
int sectorToUpdate = message.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 = message.ReadRangedSingle(0.0f, 1.0f, 8);
Color color = new Color(message.ReadUInt32());
var remoteBackgroundSection = remoteBackgroundSections.Find(s => s.Index == i);
if (remoteBackgroundSection != null)
case EventType.Status:
remoteOxygenPercentage = msg.ReadRangedSingle(0.0f, 100.0f, 8);
SharedStatusRead(
msg,
out float newWaterVolume,
out NetworkFireSource[] newFireSources);
remoteWaterVolume = newWaterVolume;
remoteFireSources = newFireSources;
break;
case EventType.BackgroundSections:
SharedBackgroundSectionRead(
msg,
bsnu =>
{
remoteBackgroundSection.SetColorStrength(colorStrength);
remoteBackgroundSection.SetColor(color);
}
else
{
remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), (ushort)i, colorStrength, color, 0));
}
}
int i = bsnu.SectionIndex;
Color color = bsnu.Color;
float colorStrength = bsnu.ColorStrength;
var remoteBackgroundSection = remoteBackgroundSections.Find(s => s.Index == i);
if (remoteBackgroundSection != null)
{
remoteBackgroundSection.SetColorStrength(colorStrength);
remoteBackgroundSection.SetColor(color);
}
else
{
remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), (ushort)i, colorStrength, color, 0));
}
}, out _);
paintAmount = BackgroundSections.Sum(s => s.ColorStrength);
}
else
{
int decalCount = message.ReadRangedInteger(0, MaxDecalsPerHull);
break;
case EventType.Decal:
int decalCount = msg.ReadRangedInteger(0, MaxDecalsPerHull);
if (decalCount == 0) { decals.Clear(); }
remoteDecals.Clear();
for (int i = 0; i < decalCount; i++)
{
UInt32 decalId = message.ReadUInt32();
int spriteIndex = message.ReadByte();
float normalizedXPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float normalizedYPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float decalScale = message.ReadRangedSingle(0.0f, 2.0f, 12);
UInt32 decalId = msg.ReadUInt32();
int spriteIndex = msg.ReadByte();
float normalizedXPos = msg.ReadRangedSingle(0.0f, 1.0f, 8);
float normalizedYPos = msg.ReadRangedSingle(0.0f, 1.0f, 8);
float decalScale = msg.ReadRangedSingle(0.0f, 2.0f, 12);
remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale));
}
}
break;
case EventType.BallastFlora:
BallastFloraBehavior.NetworkHeader header = (BallastFloraBehavior.NetworkHeader) msg.ReadByte();
if (header == BallastFloraBehavior.NetworkHeader.Spawn)
{
Identifier identifier = msg.ReadIdentifier();
float x = msg.ReadSingle();
float y = msg.ReadSingle();
BallastFlora = new BallastFloraBehavior(this, BallastFloraPrefab.Find(identifier), new Vector2(x, y), firstGrowth: true)
{
PowerConsumptionTimer = msg.ReadSingle()
};
}
else
{
BallastFlora?.ClientRead(msg, header);
}
break;
default:
throw new Exception($"Malformed incoming hull event: {eventType} is not a supported event type");
}
if (serverUpdateDelay > 0.0f) { return; }
@@ -756,17 +726,15 @@ namespace Barotrauma
remoteDecals.Clear();
}
if (remoteFireSources == null) { return; }
if (remoteFireSources is null) { return; }
WaterVolume = remoteWaterVolume;
OxygenPercentage = remoteOxygenPercentage;
for (int i = 0; i < remoteFireSources.Count; i++)
for (int i = 0; i < remoteFireSources.Length; i++)
{
Vector2 pos = new Vector2(
rect.X + rect.Width * remoteFireSources[i].X,
rect.Y - rect.Height + (rect.Height * remoteFireSources[i].Y));
float size = remoteFireSources[i].Z * rect.Width;
Vector2 pos = remoteFireSources[i].Position;
float size = remoteFireSources[i].Size;
var newFire = i < FireSources.Count ?
FireSources[i] :
@@ -782,7 +750,7 @@ namespace Barotrauma
}
}
for (int i = FireSources.Count - 1; i >= remoteFireSources.Count; i--)
for (int i = FireSources.Count - 1; i >= remoteFireSources.Length; i--)
{
FireSources[i].Remove();
if (i < FireSources.Count)
@@ -121,34 +121,42 @@ namespace Barotrauma
{
renderer?.DrawForeground(spriteBatch, cam, LevelObjectManager);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool isGlobalUpdate = msg.ReadBoolean();
if (isGlobalUpdate)
EventType eventType = (EventType)msg.ReadByte();
switch (eventType)
{
foreach (LevelWall levelWall in ExtraWalls)
case EventType.GlobalDestructibleWall:
{
if (levelWall.Body.BodyType == BodyType.Static) { continue; }
Vector2 bodyPos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
levelWall.MoveState = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 16);
DestructibleLevelWall destructibleWall = levelWall as DestructibleLevelWall;
if (Vector2.DistanceSquared(bodyPos, levelWall.Body.Position) > 0.5f && (destructibleWall == null || !destructibleWall.Destroyed))
foreach (LevelWall levelWall in ExtraWalls)
{
levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation);
if (levelWall.Body.BodyType == BodyType.Static) { continue; }
Vector2 bodyPos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
levelWall.MoveState = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 16);
if (Vector2.DistanceSquared(bodyPos, levelWall.Body.Position) > 0.5f
&& !(levelWall is DestructibleLevelWall { Destroyed: true }))
{
levelWall.Body.SetTransformIgnoreContacts(ref bodyPos, levelWall.Body.Rotation);
}
}
}
}
else
{
int index = msg.ReadUInt16();
byte damageByte = msg.ReadByte();
if (index < ExtraWalls.Count && ExtraWalls[index] is DestructibleLevelWall destructibleWall)
break;
case EventType.SingleDestructibleWall:
{
destructibleWall.SetDamage(destructibleWall.MaxHealth * damageByte / 255.0f);
int index = msg.ReadUInt16();
float damageByte = msg.ReadByte();
if (index < ExtraWalls.Count && ExtraWalls[index] is DestructibleLevelWall destructibleWall)
{
destructibleWall.SetDamage(destructibleWall.MaxHealth * damageByte / 255.0f);
}
}
break;
default:
throw new Exception($"Malformed incoming level event: {eventType} is not a supported event type");
}
}
}
@@ -229,7 +229,7 @@ namespace Barotrauma
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
int objIndex = msg.ReadRangedInteger(0, objects.Count);
objects[objIndex].ClientRead(msg);
@@ -106,7 +106,8 @@ namespace Barotrauma
WaterEffect.Parameters["xBlurDistance"].SetValue(BlurAmount / 100.0f);
}
else
{ WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShader"];
{
WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShader"];
}
Vector2 offset = WavePos;
@@ -188,8 +188,10 @@ namespace Barotrauma.Lights
if (light.ParentBody != null)
{
light.ParentBody.UpdateDrawPosition();
light.Position = light.ParentBody.DrawPosition;
if (light.ParentSub != null) { light.Position -= light.ParentSub.DrawPosition; }
Vector2 pos = light.ParentBody.DrawPosition;
if (light.ParentSub != null) { pos -= light.ParentSub.DrawPosition; }
light.Position = pos;
}
float range = light.LightSourceParams.TextureRange;
@@ -70,6 +70,8 @@ namespace Barotrauma
private (SubmarineInfo pendingSub, float realWorldCrushDepth) pendingSubInfo;
private RichString beaconStationActiveText, beaconStationInactiveText;
/*private (Rectangle targetArea, string tip)? connectionTooltip;
private string sanitizedConnectionTooltip;
private List<RichTextData> connectionTooltipRichTextData;
@@ -153,6 +155,9 @@ namespace Barotrauma
DebugConsole.ThrowError($"Could not find campaign map sprites for the biome \"{missingBiome.Identifier}\". Using the sprites of the first biome instead...");
}
beaconStationActiveText = RichString.Rich(TextManager.Get("BeaconStationActiveTooltip"));
beaconStationInactiveText = RichString.Rich(TextManager.Get("BeaconStationInactiveTooltip"));
RemoveFogOfWar(StartLocation);
GenerateLocationConnectionVisuals();
@@ -619,7 +624,7 @@ namespace Barotrauma
if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom &&
(tooltip == null || IsPreferredTooltip(typeChangeIconPos)))
{
tooltip = (new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), location.LastTypeChangeMessage);
tooltip = (new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), RichString.Rich(location.LastTypeChangeMessage));
}
}
if (location != CurrentLocation && generationParams.MissionIcon != null)
@@ -920,7 +925,7 @@ namespace Barotrauma
if (connection.LevelData.HasBeaconStation)
{
var beaconStationIconStyle = connection.LevelData.IsBeaconActive ? "BeaconStationActive" : "BeaconStationInactive";
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip"));
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), connection.LevelData.IsBeaconActive ? beaconStationActiveText : beaconStationInactiveText);
}
if (connection.Locked)
@@ -942,9 +947,9 @@ namespace Barotrauma
DrawIcon(
"LockedLocationConnection", (int)(28 * zoom),
TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
RichString.Rich(TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
("[requiredreputation]", Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true)),
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true))));
("[currentreputation]", unlockReputation.GetFormattedReputationText(addColorTags: true)))));
}
else
{
@@ -955,7 +960,7 @@ namespace Barotrauma
if (connection.LevelData.HasHuntingGrounds)
{
DrawIcon("HuntingGrounds", (int)(28 * zoom), TextManager.Get("HuntingGroundsTooltip"));
DrawIcon("HuntingGrounds", (int)(28 * zoom), RichString.Rich(TextManager.Get("HuntingGroundsTooltip")));
}
if (crushDepthWarningIconStyle != null)
@@ -976,7 +981,7 @@ namespace Barotrauma
}
}
void DrawIcon(string iconStyle, int iconSize, LocalizedString tooltipText)
void DrawIcon(string iconStyle, int iconSize, RichString tooltipText)
{
Vector2 iconPos = (connectionStart.Value + connectionEnd.Value) / 2;
Vector2 iconDiff = Vector2.Normalize(connectionEnd.Value - connectionStart.Value) * iconSize;
@@ -526,22 +526,17 @@ namespace Barotrauma
return true;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
byte sectionCount = msg.ReadByte();
bool invalidMessage = false;
if (type != ServerNetObject.ENTITY_EVENT && type != ServerNetObject.ENTITY_EVENT_INITIAL)
{
DebugConsole.NewMessage($"Error while reading a network event for the structure \"{Name} ({ID})\". Invalid event type ({type}).", Color.Red);
return;
}
else if (sectionCount != Sections.Length)
if (sectionCount != Sections.Length)
{
invalidMessage = true;
string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
DebugConsole.NewMessage(errorMsg, Color.Red);
GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
for (int i = 0; i < sectionCount; i++)
@@ -15,7 +15,7 @@ using Barotrauma.Items.Components;
namespace Barotrauma
{
partial class Submarine : Entity, IServerSerializable
partial class Submarine : Entity, IServerPositionSync
{
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{
@@ -547,19 +547,50 @@ namespace Barotrauma
}
}
int disabledItemLightCount = 0;
foreach (Item item in Item.ItemList)
float entityCountWarningThreshold = 0.75f;
if (Item.ItemList.Count > SubEditorScreen.MaxItems * entityCountWarningThreshold)
{
if (item.ParentInventory == null) { continue; }
disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
if (!IsWarningSuppressed(SubEditorScreen.WarningType.ItemCount))
{
errorMsgs.Add(TextManager.Get("subeditor.itemcountwarning").Value);
warnings.Add(SubEditorScreen.WarningType.ItemCount);
}
}
int count = GameMain.LightManager.Lights.Count(l => l.CastShadows) - disabledItemLightCount;
if (count > 45)
if ((MapEntity.mapEntityList.Count - Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count) > SubEditorScreen.MaxStructures * entityCountWarningThreshold)
{
if (!IsWarningSuppressed(SubEditorScreen.WarningType.TooManyLights))
if (!IsWarningSuppressed(SubEditorScreen.WarningType.StructureCount))
{
errorMsgs.Add(TextManager.Get("subeditor.structurecountwarning").Value);
warnings.Add(SubEditorScreen.WarningType.StructureCount);
}
}
if (Structure.WallList.Count > SubEditorScreen.MaxStructures * entityCountWarningThreshold)
{
if (!IsWarningSuppressed(SubEditorScreen.WarningType.WallCount))
{
errorMsgs.Add(TextManager.Get("subeditor.wallcountwarning").Value);
warnings.Add(SubEditorScreen.WarningType.WallCount);
}
}
if (GetLightCount() > SubEditorScreen.MaxLights * entityCountWarningThreshold)
{
if (!IsWarningSuppressed(SubEditorScreen.WarningType.LightCount))
{
errorMsgs.Add(TextManager.Get("subeditor.lightcountwarning").Value);
warnings.Add(SubEditorScreen.WarningType.LightCount);
}
}
if (GetShadowCastingLightCount() > SubEditorScreen.MaxShadowCastingLights * entityCountWarningThreshold)
{
if (!IsWarningSuppressed(SubEditorScreen.WarningType.ShadowCastingLightCount))
{
errorMsgs.Add(TextManager.Get("subeditor.shadowcastinglightswarning").Value);
warnings.Add(SubEditorScreen.WarningType.TooManyLights);
warnings.Add(SubEditorScreen.WarningType.ShadowCastingLightCount);
}
}
@@ -627,14 +658,31 @@ namespace Barotrauma
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public static int GetLightCount()
{
if (type != ServerNetObject.ENTITY_POSITION)
int disabledItemLightCount = 0;
foreach (Item item in Item.ItemList)
{
DebugConsole.NewMessage($"Error while reading a network event for the submarine \"{Info.Name} ({ID})\". Invalid event type ({type}).", Color.Red);
if (item.ParentInventory == null) { continue; }
disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
}
return GameMain.LightManager.Lights.Count() - disabledItemLightCount;
}
var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Info.Name);
public static int GetShadowCastingLightCount()
{
int disabledItemLightCount = 0;
foreach (Item item in Item.ItemList)
{
if (item.ParentInventory == null) { continue; }
disabledItemLightCount += item.GetComponents<Items.Components.LightComponent>().Count();
}
return GameMain.LightManager.Lights.Count(l => l.CastShadows) - disabledItemLightCount;
}
public void ClientReadPosition(IReadMessage msg, float sendingTime)
{
var posInfo = PhysicsBody.ClientRead(msg, sendingTime, parentDebugName: Info.Name);
msg.ReadPadBits();
if (posInfo != null)
@@ -648,5 +696,10 @@ namespace Barotrauma
subBody.PositionBuffer.Insert(index, posInfo);
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
throw new Exception($"Error while reading a network event for the submarine \"{Info.Name} ({ID})\". Submarines are not even supposed to receive events!");
}
}
}
@@ -115,7 +115,7 @@ namespace Barotrauma.Networking
}
else
{
orderMessageInfo.TargetCharacter?.SetOrder(order);
orderMessageInfo.TargetCharacter?.SetOrder(order, orderMessageInfo.IsNewOrder);
}
}
}
@@ -125,7 +125,8 @@ namespace Barotrauma.Networking
Order order = null;
if (orderMessageInfo.TargetPosition != null)
{
order = new Order(orderPrefab, orderOption, orderMessageInfo.Priority, Order.OrderType.Current, null, orderMessageInfo.TargetPosition, orderGiver: senderCharacter);
order = new Order(orderPrefab, orderOption, orderMessageInfo.TargetPosition, orderGiver: senderCharacter)
.WithManualPriority(orderMessageInfo.Priority);
}
else if (orderMessageInfo.WallSectionIndex != null)
{
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO.Pipes;
using System.Linq;
namespace Barotrauma.Networking
{
@@ -12,6 +13,9 @@ namespace Barotrauma.Networking
public static void Start(ProcessStartInfo processInfo)
{
CrashString = null;
CrashReportFilePath = null;
writePipe = new AnonymousPipeServerStream(PipeDirection.Out, System.IO.HandleInheritability.Inheritable);
readPipe = new AnonymousPipeServerStream(PipeDirection.In, System.IO.HandleInheritability.Inheritable);
@@ -42,8 +46,8 @@ namespace Barotrauma.Networking
public static void ClosePipes()
{
writePipe?.Close();
readPipe?.Close();
writePipe?.Dispose(); writePipe = null;
readPipe?.Dispose(); readPipe = null;
shutDown = true;
}
@@ -54,5 +58,20 @@ namespace Barotrauma.Networking
PrivateShutDown();
}
public static string CrashString { get; private set; }
public static string CrashReportFilePath { get; private set; }
public static LocalizedString CrashMessage
=> string.IsNullOrEmpty(CrashReportFilePath)
? TextManager.Get("ServerProcessClosed")
: TextManager.GetWithVariable("ServerProcessCrashed", "[reportfilepath]", CrashReportFilePath);
static partial void HandleCrashString(string str)
{
DebugConsole.ThrowError($"The server has crashed: {str}");
CrashReportFilePath = str.Split("||").FirstOrDefault() ?? "servercrashreport.log";
CrashString = str;
}
}
}
@@ -5,7 +5,7 @@ namespace Barotrauma
{
partial class EntitySpawner : Entity, IServerSerializable
{
public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
public void ClientEventRead(IReadMessage message, float sendingTime)
{
bool remove = message.ReadBoolean();
@@ -666,7 +666,7 @@ namespace Barotrauma.Networking
if (ChildServerRelay.Process?.HasExited ?? true)
{
Disconnect();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), TextManager.Get("ServerProcessClosed"));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
}
}
@@ -937,6 +937,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.MEDICAL:
campaign?.MedicalClinic?.ClientRead(inc);
break;
case ServerPacketHeader.MONEY:
campaign?.ClientReadMoney(inc);
break;
case ServerPacketHeader.READY_CHECK:
ReadyCheck.ClientRead(inc);
break;
@@ -1203,7 +1206,7 @@ namespace Barotrauma.Networking
if (disconnectReason == DisconnectReason.ServerCrashed && IsServerOwner)
{
msg = TextManager.Get("ServerProcessCrashed");
msg = TextManager.GetWithVariable("ServerProcessCrashed", "[reportfilepath]", ChildServerRelay.CrashReportFilePath);
}
}
@@ -2240,10 +2243,11 @@ namespace Barotrauma.Networking
}
}
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
private void ReadIngameUpdate(IReadMessage inc)
{
List<IServerSerializable> entities = new List<IServerSerializable>();
debugEntityList.Clear();
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
ServerNetObject? prevObjHeader = null;
@@ -2284,15 +2288,15 @@ namespace Barotrauma.Networking
uint msgLength = inc.ReadVariableUInt32();
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
var entity = Entity.FindEntityByID(id) as IServerSerializable;
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return;
}
entities.Add(entity);
if (entity != null && (entity is Item || entity is Character || entity is Submarine))
debugEntityList.Add(entity);
if (entity != null)
{
if (entity is Item != isItem)
{
@@ -2307,7 +2311,7 @@ namespace Barotrauma.Networking
}
else
{
entity.ClientRead(objHeader.Value, inc, sendingTime);
entity.ClientReadPosition(inc, sendingTime);
}
}
@@ -2321,7 +2325,7 @@ namespace Barotrauma.Networking
break;
case ServerNetObject.ENTITY_EVENT:
case ServerNetObject.ENTITY_EVENT_INITIAL:
if (!entityEventManager.Read(objHeader.Value, inc, sendingTime, entities))
if (!entityEventManager.Read(objHeader.Value, inc, sendingTime, debugEntityList))
{
return;
}
@@ -2340,7 +2344,6 @@ namespace Barotrauma.Networking
prevBytePos = inc.BytePosition;
}
}
catch (Exception ex)
{
List<string> errorLines = new List<string>
@@ -2359,7 +2362,7 @@ namespace Barotrauma.Networking
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
{
foreach (IServerSerializable ent in entities)
foreach (IServerSerializable ent in debugEntityList)
{
if (ent == null)
{
@@ -2480,7 +2483,7 @@ namespace Barotrauma.Networking
outmsg.Write(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
}
Character.Controlled?.ClientWrite(outmsg);
Character.Controlled?.ClientWriteInput(outmsg);
GameMain.GameScreen.Cam?.ClientWrite(outmsg);
entityEventManager.Write(outmsg, clientPeer?.ServerConnection);
@@ -2711,7 +2714,7 @@ namespace Barotrauma.Networking
}
}
public override void CreateEntityEvent(INetSerializable entity, object[] extraData)
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
{
if (!(entity is IClientSerializable clientSerializable))
{
@@ -2770,7 +2773,7 @@ namespace Barotrauma.Networking
if (ChildServerRelay.Process != null)
{
int checks = 0;
while (ChildServerRelay.Process != null && !ChildServerRelay.Process.HasExited)
while (ChildServerRelay.Process is { HasExited: false })
{
if (checks > 10)
{
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Barotrauma.Networking
{
@@ -41,16 +42,16 @@ namespace Barotrauma.Networking
thisClient = client;
}
public void CreateEvent(IClientSerializable entity, object[] extraData = null)
public void CreateEvent(IClientSerializable entity, NetEntityEvent.IData extraData = null)
{
if (GameMain.Client?.Character == null) { return; }
if (!ValidateEntity(entity)) { return; }
var newEvent = new ClientEntityEvent(entity, (UInt16)(ID + 1))
{
CharacterStateID = GameMain.Client.Character.LastNetworkUpdateID
};
var newEvent = new ClientEntityEvent(
entity,
eventId: (UInt16)(ID + 1),
characterStateId: GameMain.Client.Character.LastNetworkUpdateID);
if (extraData != null) { newEvent.SetData(extraData); }
for (int i = events.Count - 1; i >= 0; i--)
@@ -144,6 +145,7 @@ namespace Barotrauma.Networking
entities.Clear();
msg.ReadPadBits();
UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte();
@@ -172,7 +174,6 @@ namespace Barotrauma.Networking
DebugConsole.NewMessage("received msg " + thisEventID + " (null entity)",
Microsoft.Xna.Framework.Color.Orange);
}
msg.ReadPadBits();
entities.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
continue;
@@ -207,7 +208,6 @@ namespace Barotrauma.Networking
}
msg.BitPosition += msgLength * 8;
msg.ReadPadBits();
}
else
{
@@ -239,22 +239,22 @@ namespace Barotrauma.Networking
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
}
}
catch (Exception e)
{
string errorMsg = "Failed to read event for entity \"" + entity.ToString() + "\" (" + e.Message + ")! (MidRoundSyncing: " + thisClient.MidRoundSyncing + ")\n" + e.StackTrace.CleanupStackTrace();
string errorMsg = $"Failed to read event {thisEventID} for entity \"{entity}\"" +
$"{(entity is Entity { ID: var entityId } ? $", id {entityId}" : "")} ";
DebugConsole.ThrowError(errorMsg, e);
errorMsg += $"({e.Message})! (MidRoundSyncing: {thisClient.MidRoundSyncing})\n{e.StackTrace.CleanupStackTrace()}";
errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 2; j >= 0; j--)
{
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
}
DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8);
msg.ReadPadBits();
}
}
}
@@ -272,7 +272,7 @@ namespace Barotrauma.Networking
protected void ReadEvent(IReadMessage buffer, IServerSerializable entity, float sendingTime)
{
entity.ClientRead(ServerNetObject.ENTITY_EVENT, buffer, sendingTime);
entity.ClientEventRead(buffer, sendingTime);
}
public void Clear()
@@ -4,20 +4,21 @@ namespace Barotrauma.Networking
{
class ClientEntityEvent : NetEntityEvent
{
private IClientSerializable serializable;
private readonly IClientSerializable serializable;
public UInt16 CharacterStateID;
public readonly UInt16 CharacterStateID;
public ClientEntityEvent(IClientSerializable entity, UInt16 id)
: base(entity, id)
public ClientEntityEvent(IClientSerializable entity, UInt16 eventId, UInt16 characterStateId)
: base(entity, eventId)
{
serializable = entity;
CharacterStateID = characterStateId;
}
public void Write(IWriteMessage msg)
{
msg.Write(CharacterStateID);
serializable.ClientWrite(msg, Data);
serializable.ClientEventWrite(msg, Data);
}
}
}
@@ -84,7 +84,7 @@ namespace Barotrauma.Networking
if (ownerKey != 0 && (ChildServerRelay.Process?.HasExited ?? true))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), TextManager.Get("ServerProcessClosed"));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
return;
}
@@ -193,7 +193,7 @@ namespace Barotrauma.Networking
if (ChildServerRelay.HasShutDown || (ChildServerRelay.Process?.HasExited ?? true))
{
Close();
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), TextManager.Get("ServerProcessClosed"));
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
msgBox.Buttons[0].OnClicked += (btn, obj) => { GameMain.MainMenuScreen.Select(); return false; };
return;
}
@@ -71,7 +71,7 @@ namespace Barotrauma.Networking
}, delay: delay);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
bool respawnPromptPending = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
@@ -153,7 +153,7 @@ namespace Barotrauma
}
}
public PosInfo ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime, string parentDebugName)
public PosInfo ClientRead(IReadMessage msg, float sendingTime, string parentDebugName)
{
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
@@ -278,6 +278,17 @@ namespace Barotrauma
characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant), jobPrefab));
}
}
if (characterInfos.Count == 0)
{
DebugConsole.ThrowError($"No starting crew found! If you're using mods, it may be that the mods have overridden the vanilla jobs without specifying which types of characters the starting crew should consist of. If you're the developer of the mod, ensure that you've set the {nameof(JobPrefab.InitialCount)} properties for the custom jobs.");
DebugConsole.AddWarning("Choosing the first available jobs as the starting crew...");
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
var variant = Rand.Range(0, jobPrefab.Variants);
characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant), jobPrefab));
if (characterInfos.Count >= 3) { break; }
}
}
characterInfos.Sort((a, b) => Math.Sign(b.Job.MinKarma - a.Job.MinKarma));
characterInfoColumns.ClearChildren();
@@ -143,14 +143,13 @@ namespace Barotrauma
{
if (Campaign.PurchasedHullRepairs)
{
Campaign.Money += CampaignMode.HullRepairCost;
Campaign.Wallet.Refund(CampaignMode.HullRepairCost);
Campaign.PurchasedHullRepairs = false;
}
else
{
if (Campaign.Money >= CampaignMode.HullRepairCost)
if (Campaign.Wallet.TryDeduct(CampaignMode.HullRepairCost))
{
Campaign.Money -= CampaignMode.HullRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.HullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs");
Campaign.PurchasedHullRepairs = true;
}
@@ -189,14 +188,13 @@ namespace Barotrauma
{
if (Campaign.PurchasedItemRepairs)
{
Campaign.Money += CampaignMode.ItemRepairCost;
Campaign.Wallet.Refund(CampaignMode.ItemRepairCost);
Campaign.PurchasedItemRepairs = false;
}
else
{
if (Campaign.Money >= CampaignMode.ItemRepairCost)
if (Campaign.Wallet.TryDeduct(CampaignMode.ItemRepairCost))
{
Campaign.Money -= CampaignMode.ItemRepairCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ItemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs");
Campaign.PurchasedItemRepairs = true;
}
@@ -242,14 +240,13 @@ namespace Barotrauma
if (Campaign.PurchasedLostShuttles)
{
Campaign.Money += CampaignMode.ShuttleReplaceCost;
Campaign.Wallet.Refund(CampaignMode.ShuttleReplaceCost);
Campaign.PurchasedLostShuttles = false;
}
else
{
if (Campaign.Money >= CampaignMode.ShuttleReplaceCost)
if (Campaign.Wallet.TryDeduct(CampaignMode.ShuttleReplaceCost))
{
Campaign.Money -= CampaignMode.ShuttleReplaceCost;
GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ShuttleReplaceCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle");
Campaign.PurchasedLostShuttles = true;
}
@@ -445,7 +442,7 @@ namespace Barotrauma
{
Color = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
ToolTip = TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip")
ToolTip = RichString.Rich(TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip"))
};
new GUITextBlock(new RectTransform(Vector2.One, beaconStationContent.RectTransform),
TextManager.Get("submarinetype.beaconstation", "beaconstationsonarlabel"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
@@ -462,7 +459,7 @@ namespace Barotrauma
{
Color = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
ToolTip = TextManager.Get("HuntingGroundsTooltip")
ToolTip = RichString.Rich(TextManager.Get("HuntingGroundsTooltip"))
};
new GUITextBlock(new RectTransform(Vector2.One, huntingGroundsContent.RectTransform),
TextManager.Get("missionname.huntinggrounds"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
@@ -705,11 +702,11 @@ namespace Barotrauma
{
case CampaignMode.InteractionType.Repair:
repairHullsButton.Enabled =
(Campaign.PurchasedHullRepairs || Campaign.Money >= CampaignMode.HullRepairCost) &&
(Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost)) &&
Campaign.AllowedToManageCampaign();
repairHullsButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedHullRepairs;
repairItemsButton.Enabled =
(Campaign.PurchasedItemRepairs || Campaign.Money >= CampaignMode.ItemRepairCost) &&
(Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost)) &&
Campaign.AllowedToManageCampaign();
repairItemsButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedItemRepairs;
@@ -721,7 +718,7 @@ namespace Barotrauma
else
{
replaceShuttlesButton.Enabled =
(Campaign.PurchasedLostShuttles || Campaign.Money >= CampaignMode.ShuttleReplaceCost) &&
(Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost)) &&
Campaign.AllowedToManageCampaign();
replaceShuttlesButton.GetChild<GUITickBox>().Selected = Campaign.PurchasedLostShuttles;
}
@@ -742,7 +739,7 @@ namespace Barotrauma
public static LocalizedString GetMoney()
{
return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.Money));
return TextManager.GetWithVariable("PlayerCredits", "[credits]", (GameMain.GameSession?.Campaign == null) ? "0" : string.Format(CultureInfo.InvariantCulture, "{0:N0}", GameMain.GameSession.Campaign.Wallet.Balance));
}
private void UpdateMaxMissions(Location location)
@@ -1668,11 +1668,7 @@ namespace Barotrauma.CharacterEditor
if (contentPackage == null)
{
#if DEBUG
contentPackage = ContentPackageManager.EnabledPackages.All.LastOrDefault();
#else
contentPackage = ContentPackageManager.EnabledPackages.All.LastOrDefault(cp => cp != vanilla);
#endif
}
if (contentPackage == null)
{
@@ -1680,13 +1676,11 @@ namespace Barotrauma.CharacterEditor
DebugConsole.ThrowError(GetCharacterEditorTranslation("NoContentPackageSelected"));
return false;
}
#if !DEBUG
if (vanilla != null && contentPackage == vanilla)
{
GUI.AddMessage(GetCharacterEditorTranslation("CannotEditVanillaCharacters"), GUIStyle.Red, font: GUIStyle.LargeFont);
return false;
}
#endif
// Content package
if (contentPackage is RegularPackage regular && !ContentPackageManager.EnabledPackages.Regular.Contains(regular))
{
@@ -1721,9 +1715,9 @@ namespace Barotrauma.CharacterEditor
}
else
{
config.SetAttributeValue("speciesname", name);
config.SetAttributeValue("humanoid", isHumanoid);
var ragdollElement = config.Element("ragdolls");
config.SetAttributeValue("speciesname", name, StringComparison.OrdinalIgnoreCase);
config.SetAttributeValue("humanoid", isHumanoid, StringComparison.OrdinalIgnoreCase);
var ragdollElement = config.GetChildElement("ragdolls");
if (ragdollElement == null)
{
config.Add(new XElement("ragdolls", CreateRagdollPath()));
@@ -1736,7 +1730,7 @@ namespace Barotrauma.CharacterEditor
ragdollElement.ReplaceWith(new XElement("ragdolls", CreateRagdollPath()));
}
}
var animationElement = config.Element("animations");
var animationElement = config.GetChildElement("animations");
if (animationElement == null)
{
config.Add(new XElement("animations", CreateAnimationPath()));
@@ -160,8 +160,7 @@ namespace Barotrauma.CharacterEditor
bool isTextureSelected = false;
void UpdatePaths()
{
string pathBase = ContentPackage == GameMain.VanillaContent ? $"Content/Characters/{Name}/{Name}"
: $"{ContentPath.ModDirStr}/Characters/{Name}/{Name}";
string pathBase = $"{ContentPath.ModDirStr}/Characters/{Name}/{Name}";
XMLPath = $"{pathBase}.xml";
xmlPathElement.Text = XMLPath;
if (updateTexturePath)
@@ -307,10 +306,10 @@ namespace Barotrauma.CharacterEditor
contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
foreach (ContentPackage contentPackage in ContentPackageManager.EnabledPackages.All)
{
#if !DEBUG
if (contentPackage == GameMain.VanillaContent) { continue; }
#endif
contentPackageDropDown.AddItem(contentPackage.Name, userData: contentPackage, toolTip: contentPackage.Path);
if (contentPackage != GameMain.VanillaContent)
{
contentPackageDropDown.AddItem(contentPackage.Name, userData: contentPackage, toolTip: contentPackage.Path);
}
}
contentPackageDropDown.OnSelected = (obj, userdata) =>
{
@@ -1329,6 +1329,11 @@ namespace Barotrauma
}
SeedBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
levelDifficultyScrollBar.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
levelDifficultyScrollBar.ToolTip = string.Empty;
if (!levelDifficultyScrollBar.Enabled)
{
levelDifficultyScrollBar.ToolTip = TextManager.Get("campaigndifficultydisabled");
}
traitorProbabilityButtons[0].Enabled = traitorProbabilityButtons[1].Enabled = traitorProbabilityText.Enabled =
!CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
@@ -32,7 +32,10 @@ namespace Barotrauma
private GUIScrollBar zoomBar;
private readonly List<Sprite> selectedSprites = new List<Sprite>();
private readonly List<Sprite> dirtySprites = new List<Sprite>();
private Sprite selectedTexture;
private Texture2D SelectedTexture => lastSprite?.Texture;
private Sprite lastSprite;
private string selectedTexturePath;
private Rectangle textureRect;
private float zoom = 1;
private const float MinZoom = 0.25f, MaxZoom = 10.0f;
@@ -82,12 +85,11 @@ namespace Barotrauma
{
OnClicked = (button, userData) =>
{
if (!(textureList.SelectedData is Texture2D selectedTexture)) { return false; }
var selected = selectedSprites;
Sprite firstSelected = selected.First();
selected.ForEach(s => s.ReloadTexture());
RefreshLists();
textureList.Select(firstSelected.Texture, autoScroll: false);
textureList.Select(firstSelected.FullPath, autoScroll: false);
selected.ForEachMod(s => spriteList.Select(s, autoScroll: false));
texturePathText.Text = TextManager.GetWithVariable("spriteeditor.texturesreloaded", "[filepath]", firstSelected.FilePath.Value);
texturePathText.TextColor = GUIStyle.Green;
@@ -101,10 +103,10 @@ namespace Barotrauma
{
OnClicked = (button, userData) =>
{
if (selectedTexture == null) { return false; }
if (SelectedTexture == null) { return false; }
foreach (Sprite sprite in loadedSprites)
{
if (sprite.FullPath != selectedTexture.FullPath) { continue; }
if (sprite.FullPath != selectedTexturePath) { continue; }
var element = sprite.SourceElement;
if (element == null) { continue; }
// Not all sprites have a sourcerect defined, in which case we'll want to use the current source rect instead of an empty rect.
@@ -206,23 +208,20 @@ namespace Barotrauma
{
OnSelected = (listBox, userData) =>
{
var previousSprite = selectedTexture;
selectedTexture = userData as Sprite;
if (previousSprite != selectedTexture)
var newTexturePath = userData as string;
if (selectedTexturePath == null || selectedTexturePath != newTexturePath)
{
selectedTexturePath = newTexturePath;
ResetZoom();
spriteList.Select(loadedSprites.First(s => s.FilePath == selectedTexturePath), autoScroll: false);
UpdateScrollBar(spriteList);
}
foreach (GUIComponent child in spriteList.Content.Children)
{
var textBlock = (GUITextBlock)child;
var sprite = (Sprite)textBlock.UserData;
textBlock.TextColor = new Color(textBlock.TextColor, sprite.FilePath == selectedTexture.FilePath ? 1.0f : 0.4f);
if (sprite.FilePath == selectedTexture.FilePath) { textBlock.Visible = true; }
}
if (selectedSprites.None(s => s.FilePath == selectedTexture.FilePath))
{
spriteList.Select(loadedSprites.First(s => s.FilePath == selectedTexture.FilePath), autoScroll: false);
UpdateScrollBar(spriteList);
textBlock.TextColor = new Color(textBlock.TextColor, sprite.FilePath == selectedTexturePath ? 1.0f : 0.4f);
if (sprite.FilePath == selectedTexturePath) { textBlock.Visible = true; }
}
texturePathText.TextColor = Color.LightGray;
topPanelContents.Visible = true;
@@ -251,9 +250,12 @@ namespace Barotrauma
{
OnSelected = (listBox, userData) =>
{
if (!(userData is Sprite sprite)) return false;
SelectSprite(sprite);
return true;
if (userData is Sprite sprite)
{
SelectSprite(sprite);
return true;
}
return false;
}
};
@@ -410,12 +412,12 @@ namespace Barotrauma
private bool SaveSprites(IEnumerable<Sprite> sprites)
{
if (selectedTexture == null) { return false; }
if (SelectedTexture == null) { return false; }
if (sprites.None()) { return false; }
HashSet<XDocument> docsToSave = new HashSet<XDocument>();
foreach (Sprite sprite in sprites)
{
if (sprite.FullPath != selectedTexture.FullPath) { continue; }
if (sprite.FullPath != selectedTexturePath) { continue; }
var element = sprite.SourceElement;
if (element == null) { continue; }
element.SetAttributeValue("sourcerect", XMLExtensions.RectToString(sprite.SourceRect));
@@ -469,11 +471,11 @@ namespace Barotrauma
// Select rects with the mouse
if (Widget.selectedWidgets.None() || Widget.EnableMultiSelect)
{
if (selectedTexture != null && GUI.MouseOn == null)
if (SelectedTexture != null && GUI.MouseOn == null)
{
foreach (Sprite sprite in loadedSprites)
{
if (sprite.FullPath != selectedTexture.FullPath) { continue; }
if (sprite.FullPath != selectedTexturePath) { continue; }
if (PlayerInput.PrimaryMouseButtonClicked())
{
var scaledRect = new Rectangle(textureRect.Location + sprite.SourceRect.Location.Multiply(zoom), sprite.SourceRect.Size.Multiply(zoom));
@@ -637,20 +639,20 @@ namespace Barotrauma
var viewArea = GetViewArea;
if (selectedTexture != null)
if (SelectedTexture != null)
{
textureRect = new Rectangle(
(int)(viewArea.Center.X - selectedTexture.Texture.Bounds.Width / 2f * zoom),
(int)(viewArea.Center.Y - selectedTexture.Texture.Bounds.Height / 2f * zoom),
(int)(selectedTexture.Texture.Bounds.Width * zoom),
(int)(selectedTexture.Texture.Bounds.Height * zoom));
(int)(viewArea.Center.X - SelectedTexture.Bounds.Width / 2f * zoom),
(int)(viewArea.Center.Y - SelectedTexture.Bounds.Height / 2f * zoom),
(int)(SelectedTexture.Bounds.Width * zoom),
(int)(SelectedTexture.Bounds.Height * zoom));
spriteBatch.Draw(selectedTexture.Texture,
spriteBatch.Draw(SelectedTexture,
viewArea.Center.ToVector2(),
sourceRectangle: null,
color: Color.White,
rotation: 0.0f,
origin: new Vector2(selectedTexture.Texture.Bounds.Width / 2.0f, selectedTexture.Texture.Bounds.Height / 2.0f),
origin: new Vector2(SelectedTexture.Bounds.Width / 2.0f, SelectedTexture.Bounds.Height / 2.0f),
scale: zoom,
effects: SpriteEffects.None,
layerDepth: 0);
@@ -666,7 +668,7 @@ namespace Barotrauma
foreach (GUIComponent element in spriteList.Content.Children)
{
if (!(element.UserData is Sprite sprite)) { continue; }
if (sprite.FullPath != selectedTexture.FullPath) { continue; }
if (sprite.FullPath != selectedTexturePath) { continue; }
Rectangle sourceRect = new Rectangle(
textureRect.X + (int)(sprite.SourceRect.X * zoom),
@@ -874,13 +876,13 @@ namespace Barotrauma
public void SelectSprite(Sprite sprite)
{
lastSprite = sprite;
if (!loadedSprites.Contains(sprite))
{
loadedSprites.Add(sprite);
RefreshLists();
}
if (selectedSprites.Any(s => s.FullPath != selectedTexture.FullPath))
if (selectedSprites.Any(s => s.FullPath != selectedTexturePath))
{
ResetWidgets();
}
@@ -902,9 +904,9 @@ namespace Barotrauma
selectedSprites.Add(sprite);
dirtySprites.Add(sprite);
}
if (selectedTexture?.FullPath != sprite.FullPath)
if (sprite.FullPath != selectedTexturePath)
{
textureList.Select(sprite.Texture, autoScroll: false);
textureList.Select(sprite.FullPath, autoScroll: false);
UpdateScrollBar(textureList);
}
xmlPathText.Text = string.Empty;
@@ -926,7 +928,6 @@ namespace Barotrauma
public void RefreshLists()
{
//selectedTexture = null;
selectedSprites.Clear();
textureList.ClearChildren();
spriteList.ClearChildren();
@@ -936,7 +937,7 @@ namespace Barotrauma
foreach (Sprite sprite in loadedSprites.OrderBy(s => Path.GetFileNameWithoutExtension(s.FilePath.Value)))
{
//ignore sprites that don't have a file path (e.g. submarine pics)
if (sprite.FilePath.IsNullOrEmpty()) continue;
if (sprite.FilePath.IsNullOrEmpty()) { continue; }
string normalizedFilePath = sprite.FilePath.FullPath;
if (!textures.Contains(normalizedFilePath))
{
@@ -944,7 +945,7 @@ namespace Barotrauma
Path.GetFileName(sprite.FilePath.Value))
{
ToolTip = sprite.FilePath.Value,
UserData = sprite
UserData = sprite.FullPath
};
textures.Add(normalizedFilePath);
}
@@ -965,10 +966,10 @@ namespace Barotrauma
public void ResetZoom()
{
if (selectedTexture == null) { return; }
if (SelectedTexture == null) { return; }
var viewArea = GetViewArea;
float width = viewArea.Width / (float)selectedTexture.Texture.Width;
float height = viewArea.Height / (float)selectedTexture.Texture.Height;
float width = viewArea.Width / (float)SelectedTexture.Width;
float height = viewArea.Height / (float)SelectedTexture.Height;
zoom = Math.Min(1, Math.Min(width, height));
zoomBar.BarScroll = GetBarScrollValue();
viewAreaOffset = Point.Zero;
@@ -5,12 +5,10 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Input;
using System.Threading.Tasks;
#if DEBUG
using System.IO;
#else
@@ -21,6 +19,12 @@ namespace Barotrauma
{
class SubEditorScreen : EditorScreen
{
public const int MaxStructures = 2000;
public const int MaxWalls = 500;
public const int MaxItems = 5000;
public const int MaxLights = 300;
public const int MaxShadowCastingLights = 60;
private static Submarine MainSub
{
get => Submarine.MainSub;
@@ -83,7 +87,11 @@ namespace Barotrauma
NoCargoSpawnpoints,
NoBallastTag,
NonLinkedGaps,
TooManyLights
StructureCount,
WallCount,
ItemCount,
LightCount,
ShadowCastingLightCount
}
public static Vector2 MouseDragStart = Vector2.Zero;
@@ -102,7 +110,7 @@ namespace Barotrauma
private bool wasSelectedBefore;
public GUIComponent TopPanel;
private GUIComponent showEntitiesPanel, entityCountPanel;
public GUIComponent showEntitiesPanel, entityCountPanel;
private readonly List<GUITickBox> showEntitiesTickBoxes = new List<GUITickBox>();
private readonly Dictionary<string, bool> hiddenSubCategories = new Dictionary<string, bool>();
@@ -809,7 +817,7 @@ namespace Barotrauma
var itemCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), itemCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight);
itemCount.TextGetter = () =>
{
itemCount.TextColor = ToolBox.GradientLerp(Item.ItemList.Count / 5000.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
itemCount.TextColor = Item.ItemList.Count > MaxItems ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, Item.ItemList.Count / (float)MaxItems);
return Item.ItemList.Count.ToString();
};
@@ -818,8 +826,8 @@ namespace Barotrauma
var structureCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), structureCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight);
structureCount.TextGetter = () =>
{
int count = (MapEntity.mapEntityList.Count - Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count);
structureCount.TextColor = ToolBox.GradientLerp(count / 1000.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
int count = MapEntity.mapEntityList.Count - Item.ItemList.Count - Hull.HullList.Count - WayPoint.WayPointList.Count - Gap.GapList.Count;
structureCount.TextColor = count > MaxStructures ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, count / (float)MaxStructures);
return count.ToString();
};
@@ -828,7 +836,7 @@ namespace Barotrauma
var wallCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), wallCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight);
wallCount.TextGetter = () =>
{
wallCount.TextColor = ToolBox.GradientLerp(Structure.WallList.Count / 500.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
wallCount.TextColor = Structure.WallList.Count > MaxWalls ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, Structure.WallList.Count / (float)MaxWalls);
return Structure.WallList.Count.ToString();
};
@@ -843,7 +851,7 @@ namespace Barotrauma
if (item.ParentInventory != null) { continue; }
lightCount += item.GetComponents<LightComponent>().Count();
}
lightCountText.TextColor = ToolBox.GradientLerp(lightCount / 250.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
lightCountText.TextColor = lightCount > MaxLights ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, lightCount / (float)MaxLights);
return lightCount.ToString();
};
var shadowCastingLightCountLabel = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("SubEditorShadowCastingLights"),
@@ -857,7 +865,7 @@ namespace Barotrauma
if (item.ParentInventory != null) { continue; }
lightCount += item.GetComponents<LightComponent>().Count(l => l.CastShadows);
}
shadowCastingLightCountText.TextColor = ToolBox.GradientLerp(lightCount / 60.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
shadowCastingLightCountText.TextColor = lightCount > MaxShadowCastingLights ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, lightCount / (float)MaxShadowCastingLights);
return lightCount.ToString();
};
entityCountPanel.RectTransform.NonScaledSize =
@@ -1448,7 +1456,7 @@ namespace Barotrauma
case ".jpeg":
if (saveFrame == null) { break; }
Texture2D texture = Sprite.LoadTexture(filePath);
Texture2D texture = Sprite.LoadTexture(filePath, compress: false);
previewImage.Sprite = new Sprite(texture, null, null);
if (MainSub != null)
{
@@ -1548,7 +1556,7 @@ namespace Barotrauma
{
foreach (GUIColorPicker colorPicker in msgBox.GetAllChildren<GUIColorPicker>())
{
colorPicker.DisposeTextures();
colorPicker.Dispose();
}
msgBox.Close();
@@ -1827,6 +1835,21 @@ namespace Barotrauma
modProject.Save(packagePath);
}
if (!GameMain.DebugDraw)
{
if (Submarine.GetLightCount() > MaxLights)
{
new GUIMessageBox(TextManager.Get("error"), TextManager.GetWithVariable("subeditor.lightcounterror", "[max]", MaxShadowCastingLights.ToString()));
return false;
}
if (Submarine.GetShadowCastingLightCount() > MaxShadowCastingLights)
{
new GUIMessageBox(TextManager.Get("error"), TextManager.GetWithVariable("subeditor.shadowcastinglightcounterror", "[max]", MaxShadowCastingLights.ToString()));
return false;
}
}
if (string.IsNullOrWhiteSpace(name))
{
GUI.AddMessage(TextManager.Get("SubNameMissingWarning"), GUIStyle.Red);
@@ -3634,7 +3657,7 @@ namespace Barotrauma
closeButton.OnClicked = (button, o) =>
{
colorPicker.DisposeTextures();
colorPicker.Dispose();
msgBox.Close();
Color newColor = SetColor(null);
@@ -3678,7 +3701,7 @@ namespace Barotrauma
cancelButton.OnClicked = (button, o) =>
{
colorPicker.DisposeTextures();
colorPicker.Dispose();
msgBox.Close();
foreach (var (e, color, prop) in entities)
@@ -21,16 +21,16 @@ namespace Barotrauma
private Item? miniMapItem;
private Submarine? submarine;
private Character? dummyCharacter;
public static Effect BlueprintEffect = null!;
private GUIFrame container = null!;
public static Character? dummyCharacter;
public static Effect? BlueprintEffect;
private GUIFrame? container;
private TabMenu? tabMenu;
public TestScreen()
{
Cam = new Camera();
BlueprintEffect = GameMain.GameScreen.BlueprintEffect!;
BlueprintEffect = GameMain.GameScreen.BlueprintEffect;
new GUIButton(new RectTransform(new Point(256, 256), Frame.RectTransform), "Reload shader")
{
@@ -38,7 +38,7 @@ namespace Barotrauma
{
BlueprintEffect.Dispose();
GameMain.Instance.Content.Unload();
BlueprintEffect = GameMain.Instance.Content.Load<Effect>("Effects/blueprintshader_opengl")!;
BlueprintEffect = GameMain.Instance.Content.Load<Effect>("Effects/blueprintshader_opengl");
GameMain.GameScreen.BlueprintEffect = BlueprintEffect;
return true;
}
@@ -47,21 +47,19 @@ namespace Barotrauma
}
public override void Select()
{
{
base.Select();
container = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
var tab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
MedicalClinicUI clinic = new MedicalClinicUI(new MedicalClinic(null!), tab);
clinic.RequestLatestPending();
if (dummyCharacter is { Removed: false })
{
dummyCharacter?.Remove();
}
// dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false);
// dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom());
// dummyCharacter.Info.Name = "Galldren";
// dummyCharacter.Inventory.CreateSlots();
dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false);
dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom(Rand.RandSync.Unsynced));
dummyCharacter.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots();
Character.Controlled = dummyCharacter;
GameMain.World.ProcessChanges();
@@ -71,7 +69,8 @@ namespace Barotrauma
public override void AddToGUIUpdateList()
{
Frame.AddToGUIUpdateList();
container.AddToGUIUpdateList();
container?.AddToGUIUpdateList();
tabMenu?.AddToGUIUpdateList();
// CharacterHUD.AddToGUIUpdateList(dummyCharacter);
// dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList();
}
@@ -79,15 +78,13 @@ namespace Barotrauma
public override void Update(double deltaTime)
{
base.Update(deltaTime);
tabMenu!.Update();
if (dummyCharacter is { } dummy)
{
dummy.ControlLocalPlayer((float)deltaTime, Cam, false);
dummy.Control((float)deltaTime, Cam);
}
GUI.Update((float)deltaTime);
tabMenu?.Update((float)deltaTime);
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
@@ -342,9 +342,9 @@ namespace Barotrauma
if (property.PropertyType == typeof(string) && value == null)
{
value = "";
}
}
Identifier propertyTag = $"{entity.GetType().Name}.{property.PropertyInfo.Name}".ToIdentifier();
Identifier propertyTag = $"{property.PropertyInfo.DeclaringType.Name}.{property.PropertyInfo.Name}".ToIdentifier();
Identifier fallbackTag = property.PropertyInfo.Name.ToIdentifier();
LocalizedString displayName =
TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
@@ -365,7 +365,7 @@ namespace Barotrauma
{
displayName = property.Name.FormatCamelCaseWithSpaces();
#if DEBUG
Editable editable = property.GetAttribute<Editable>();
InGameEditable editable = property.GetAttribute<InGameEditable>();
if (editable != null)
{
if (!MissingLocalizations.Contains($"sp.{propertyTag}.name|{displayName}"))
@@ -378,7 +378,11 @@ namespace Barotrauma
#endif
}
LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description", $"sp.{fallbackTag}.description");
LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description");
if (toolTip.IsNullOrEmpty())
{
toolTip = TextManager.Get($"{propertyTag}.description", $"sp.{fallbackTag}.description");
}
if (toolTip == null)
{
@@ -1312,12 +1316,9 @@ namespace Barotrauma
entity = e.Item;
}
if (GameMain.Client != null)
if (GameMain.Client != null && entity is Item item)
{
if (entity is IClientSerializable clientSerializable)
{
GameMain.Client.CreateEntityEvent(clientSerializable, new object[] { NetEntityEvent.Type.ChangeProperty, property });
}
GameMain.Client.CreateEntityEvent(item, new Item.ChangePropertyEventData(property));
}
}
@@ -21,8 +21,8 @@ namespace Barotrauma.SpriteDeformations
private set;
}
[Serialize("", IsPropertySaveable.Yes)]
public string TypeName
[Serialize("", IsPropertySaveable.No)]
public string Type
{
get;
set;
@@ -35,7 +35,7 @@ namespace Barotrauma.SpriteDeformations
set;
}
public string Name => $"Deformation ({TypeName})";
public string Name => $"Deformation ({Type})";
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0, MaxValueFloat = 10, DecimalCount = 2, ValueStep = 0.01f)]
public float Strength { get; private set; }
@@ -85,11 +85,11 @@ namespace Barotrauma.SpriteDeformations
public SpriteDeformationParams(XElement element)
{
if (element != null)
{
TypeName = element.GetAttributeString("type", "").ToLowerInvariant();
}
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
if (element != null && string.IsNullOrEmpty(Type))
{
Type = element.GetAttributeString("typename", string.Empty);
}
}
}
@@ -120,7 +120,7 @@ namespace Barotrauma.SpriteDeformations
set { SetResolution(value); }
}
public string TypeName => Params.TypeName;
public string TypeName => Params.Type;
public int Sync => Params.Sync;
@@ -177,7 +177,7 @@ namespace Barotrauma.SpriteDeformations
if (newDeformation != null)
{
newDeformation.Params.TypeName = typeName;
newDeformation.Params.Type = typeName;
}
return newDeformation;
}
@@ -522,7 +522,7 @@ namespace Barotrauma.Steam
UserData = tag
};
tagBtn.RectTransform.NonScaledSize
= tagBtn.Font.MeasureString(tagBtn.Text).ToPoint() + new Point(GUI.IntScale(5));
= tagBtn.Font.MeasureString(tagBtn.Text).ToPoint() + new Point(GUI.IntScale(15), GUI.IntScale(5));
tagBtn.RectTransform.IsFixedSize = true;
tagBtn.ClampMouseRectToParent = false;
}
@@ -625,7 +625,7 @@ namespace Barotrauma.Steam
#region Stats box
var statsHorizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, statsBox.RectTransform), isHorizontal: true);
var statsVertical0
= new GUILayoutGroup(new RectTransform((1.0f, 1.0f), statsHorizontalLayout.RectTransform));
= new GUILayoutGroup(new RectTransform((1.0f, 1.0f), statsHorizontalLayout.RectTransform), childAnchor: Anchor.TopCenter);
statFrame("", ""); //padding
@@ -680,7 +680,7 @@ namespace Barotrauma.Steam
var tagsLabel = new GUITextBlock(new RectTransform((1.0f, 0.12f), statsVertical0.RectTransform),
TextManager.Get("WorkshopItemTags"), font: GUIStyle.SubHeadingFont);
CreateTagsList(workshopItem.Tags.ToIdentifiers(), new RectTransform((1.0f, 0.3f), statsVertical0.RectTransform), canBeFocused: false);
CreateTagsList(workshopItem.Tags.ToIdentifiers(), new RectTransform((0.97f, 0.3f), statsVertical0.RectTransform), canBeFocused: false);
#endregion
var descriptionListBox = new GUIListBox(new RectTransform((1.0f, 0.38f), verticalLayout.RectTransform));
@@ -71,6 +71,7 @@ namespace Barotrauma.Steam
if (GameMain.Client == null)
{
LeaveLobby();
return;
}
if (lobbyState == LobbyState.NotConnected)
@@ -83,7 +84,7 @@ namespace Barotrauma.Steam
return;
}
var contentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerIncompatibleContent);
var contentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent);
currentLobby?.SetData("name", serverSettings.ServerName);
currentLobby?.SetData("playercount", (GameMain.Client?.ConnectedClients?.Count ?? 0).ToString());
@@ -357,21 +357,31 @@ namespace Barotrauma.Steam
private IEnumerable<CoroutineStatus> MessageBoxCoroutine(Func<GUITextBlock, GUIMessageBox, IEnumerable<CoroutineStatus>> subcoroutine)
{
var messageBox = new GUIMessageBox("", "", relativeSize: (0.4f, 0.4f), buttons: new [] { TextManager.Get("Cancel") });
var messageBox = new GUIMessageBox("", "...", buttons: new [] { TextManager.Get("Cancel") });
messageBox.Buttons[0].OnClicked = (button, o) =>
{
messageBox.Close();
return false;
};
var currentStepText = new GUITextBlock(new RectTransform((1.0f, 0.8f), messageBox.InnerFrame.RectTransform),
"...", font: GUIStyle.Font)
{
CanBeFocused = false
};
foreach (var status in subcoroutine(currentStepText, messageBox))
var coroutineEval = subcoroutine(messageBox.Text, messageBox);
while (true)
{
bool moveNext = true;
try
{
moveNext = coroutineEval.GetEnumerator().MoveNext();
}
catch (Exception e)
{
DebugConsole.ThrowError($"{e.Message} {e.StackTrace.CleanupStackTrace()}");
messageBox.Close();
}
if (!moveNext)
{
messageBox.Close();
}
var status = coroutineEval.GetEnumerator().Current;
if (messageBox.Closed)
{
yield return CoroutineStatus.Success;
@@ -410,7 +420,7 @@ namespace Barotrauma.Steam
{
SteamManager.Workshop.ForceRedownload(workshopItem);
}
currentStepText.Text = $"Downloading {Percentage(workshopItem.DownloadAmount)}";
currentStepText.Text = TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(workshopItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
}
@@ -426,7 +436,7 @@ namespace Barotrauma.Steam
});
while (!ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
{
currentStepText.Text = $"Installing";
currentStepText.Text = TextManager.Get("PublishPopupInstall");
yield return new WaitForSeconds(0.5f);
}
@@ -444,7 +454,7 @@ namespace Barotrauma.Steam
});
while (!localCopyMade)
{
currentStepText.Text = $"Creating local copy";
currentStepText.Text = TextManager.Get("PublishPopupCreateLocal");
yield return new WaitForSeconds(0.5f);
}
@@ -457,47 +467,62 @@ namespace Barotrauma.Steam
GUITextBlock currentStepText, GUIMessageBox messageBox,
string modVersion, Steamworks.Ugc.Editor editor, ContentPackage localPackage)
{
if (!SteamManager.IsInitialized)
{
yield return CoroutineStatus.Failure;
}
bool stagingReady = false;
Exception? stagingException = null;
TaskPool.Add("CreatePublishStagingCopy",
SteamManager.Workshop.CreatePublishStagingCopy(modVersion, localPackage),
(t) =>
{
Exception? exception = t.Exception?.InnerException ?? t.Exception;
if (exception != null)
{
throw new Exception($"Failed to create staging copy: {exception.Message} {exception.StackTrace}");
}
stagingReady = true;
stagingException = t.Exception?.GetInnermost();
});
currentStepText.Text = "Copying item to staging folder...";
currentStepText.Text = TextManager.Get("PublishPopupStaging");
while (!stagingReady) { yield return new WaitForSeconds(0.5f); }
if (stagingException != null)
{
throw new Exception($"Failed to create staging copy: {stagingException.Message} {stagingException.StackTrace.CleanupStackTrace()}");
}
editor = editor
.WithContent(SteamManager.Workshop.PublishStagingDir)
.ForAppId(SteamManager.AppID);
messageBox.Buttons[0].Enabled = false;
Steamworks.Ugc.PublishResult? result = null;
Exception? resultException = null;
TaskPool.Add($"Publishing {localPackage.Name} ({localPackage.SteamWorkshopId})",
editor.SubmitAsync(),
(t) =>
t =>
{
result = ((Task<Steamworks.Ugc.PublishResult>)t).Result;
t.TryGetResult(out result);
resultException = t.Exception?.GetInnermost();
});
currentStepText.Text = "Submitting item to the Workshop...";
while (!result.HasValue) { yield return new WaitForSeconds(0.5f); }
currentStepText.Text = TextManager.Get("PublishPopupSubmit");
while (!result.HasValue && resultException is null) { yield return new WaitForSeconds(0.5f); }
if (result.Value.Success)
if (result is { Success: true })
{
var resultId = result.Value.FileId;
Steamworks.Ugc.Item resultItem = new Steamworks.Ugc.Item(resultId);
SteamManager.Workshop.ForceRedownload(resultItem);
while (!resultItem.IsInstalled)
Task downloadTask = SteamManager.Workshop.ForceRedownload(resultItem);
while (!resultItem.IsInstalled && !downloadTask.IsCompleted)
{
currentStepText.Text = $"Downloading {Percentage(resultItem.DownloadAmount)}";
currentStepText.Text = TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(resultItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
if (!resultItem.IsInstalled)
{
throw new Exception($"Failed to install item: download task ended with status {downloadTask.Status}, " +
$"exception was {downloadTask.Exception?.GetInnermost()?.ToString().CleanupStackTrace() ?? "[NULL]"}");
}
bool installed = false;
TaskPool.Add(
"InstallNewlyPublished",
@@ -508,7 +533,7 @@ namespace Barotrauma.Steam
});
while (!installed)
{
currentStepText.Text = $"Installing";
currentStepText.Text = TextManager.Get("PublishPopupInstall");
yield return new WaitForSeconds(0.5f);
}
@@ -524,8 +549,19 @@ namespace Barotrauma.Steam
{
SteamManager.OverlayCustomURL(resultItem.Url);
}
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("workshopitempublished", "[itemname]", localPackage.Name));
}
else if (resultException != null)
{
throw new Exception($"Failed to publish item: {resultException.Message} {resultException.StackTrace.CleanupStackTrace()}");
}
else
{
new GUIMessageBox(TextManager.Get("error"), TextManager.GetWithVariable("workshopitempublishfailed", "[itemname]", localPackage.Name));
}
SteamManager.Workshop.DeletePublishStagingCopy();
messageBox.Close();
}
}
}
@@ -89,7 +89,7 @@ namespace Barotrauma.Steam
}
private static int Round(float v) => (int)MathF.Round(v);
private static string Percentage(float v) => $"{Round(v * 100)}%";
private static string Percentage(float v) => $"{Round(v * 100)}";
private struct ActionCarrier
{
@@ -1,14 +1,13 @@
#nullable enable
using Barotrauma.IO;
using Microsoft.Xna.Framework.Graphics;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Barotrauma.IO;
namespace Barotrauma.Steam
{
@@ -180,8 +179,10 @@ namespace Barotrauma.Steam
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir);
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
ModProject modProject = new ModProject(contentPackage);
modProject.ModVersion = modVersion;
ModProject modProject = new ModProject(contentPackage)
{
ModVersion = modVersion
};
modProject.Save(Path.Combine(PublishStagingDir, ContentPackage.FileListFileName));
}
@@ -1,16 +1,11 @@
#nullable enable
using System;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
using System.Xml.Linq;
using Barotrauma.IO;
using Microsoft.Xna.Framework.Graphics;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
namespace Barotrauma.Steam
@@ -25,12 +20,12 @@ namespace Barotrauma.Steam
Publish
}
private GUILayoutGroup tabber;
private Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
private readonly GUILayoutGroup tabber;
private readonly Dictionary<Tab, (GUIButton Button, GUIFrame Content)> tabContents;
private GUIFrame contentFrame;
private readonly GUIFrame contentFrame;
private CorePackage enabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected");
private readonly GUIDropDown enabledCoreDropdown;
private readonly GUIListBox enabledRegularModsList;
@@ -173,7 +168,7 @@ namespace Barotrauma.Steam
to.DraggedElement = draggedElement;
to.BarScroll = to.BarScroll * (oldCount / newCount);
to.BarScroll *= (oldCount / newCount);
}
}
@@ -367,7 +362,8 @@ namespace Barotrauma.Steam
{
ToolBox.OpenFileWithShell(mod.Dir);
return false;
}
},
ToolTip = TextManager.Get("OpenLocalModInExplorer")
};
}
else if (ContentPackageManager.WorkshopPackages.Contains(mod))
@@ -386,8 +382,13 @@ namespace Barotrauma.Steam
onInstalledInfoButtonHit(item.Value);
});
return false;
}
},
ToolTip = TextManager.Get("ViewModDetails")
};
if (!SteamManager.IsInitialized)
{
infoButton.Enabled = false;
}
TaskPool.Add(
$"DetermineUpdateRequired{mod.SteamWorkshopId}",
mod.IsUpToDate(),
@@ -398,6 +399,7 @@ namespace Barotrauma.Steam
if (!isUpToDate)
{
infoButton.ApplyStyle(GUIStyle.ComponentStyles["WorkshopMenu.InfoButtonUpdate"]);
infoButton.ToolTip = TextManager.Get("ViewModDetailsUpdateAvailable");
}
});
}
@@ -421,20 +423,26 @@ namespace Barotrauma.Steam
private void CreatePopularModsTab(out GUIListBox popularModsList)
{
GUIFrame content = CreateNewContentFrame(Tab.PopularMods);
if (!SteamManager.IsInitialized)
{
tabContents[Tab.PopularMods].Button.Enabled = false;
}
CreateWorkshopItemList(content, out _, out popularModsList, onSelected: PopulateFrameWithItemInfo);
}
private void CreatePublishTab(out GUIListBox selfModsList)
{
GUIFrame content = CreateNewContentFrame(Tab.Publish);
if (!SteamManager.IsInitialized)
{
tabContents[Tab.Publish].Button.Enabled = false;
}
CreateWorkshopItemOrPackageList(content, out _, out selfModsList, onSelected: PopulatePublishTab);
}
public void Apply()
{
ContentPackageManager.EnabledPackages.SetCore(enabledCorePackage);
ContentPackageManager.EnabledPackages.SetCore(EnabledCorePackage);
ContentPackageManager.EnabledPackages.SetRegular(enabledRegularModsList.Content.Children
.Where(c => c.UserData is RegularPackage).Select(c => (RegularPackage)c.UserData).ToArray());
}

Some files were not shown because too many files have changed in this diff Show More