Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs
2023-01-31 18:08:26 +02:00

397 lines
18 KiB
C#

using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
{
private CoroutineHandle logPropertyChangeCoroutine;
public Inventory PreviousParentInventory;
public override Sprite Sprite
{
get { return base.Prefab?.Sprite; }
}
partial void AssignCampaignInteractionTypeProjSpecific(CampaignMode.InteractionType interactionType)
{
GameMain.NetworkMember.CreateEntityEvent(this, new AssignCampaignInteractionEventData());
}
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
Exception error(string reason)
{
string errorMsg = $"Failed to write a network event for the item \"{Name}\" - {reason}";
GameAnalyticsManager.AddErrorEventOnce($"Item.ServerWrite:{Name}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return new Exception(errorMsg);
}
if (extraData is null) { throw error("event data was null"); }
if (!(extraData is IEventData itemEventData)) { throw error($"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
msg.WriteRangedInteger((int)itemEventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (itemEventData)
{
case ComponentStateEventData componentStateEventData:
int componentIndex = components.IndexOf(componentStateEventData.Component);
if (componentIndex < 0)
{
throw error($"component index out of range ({componentIndex})");
}
if (!(components[componentIndex] is IServerSerializable serializableComponent))
{
throw error($"component \"{components[componentIndex]}\" is not server serializable");
}
msg.WriteRangedInteger(componentIndex, 0, components.Count - 1);
serializableComponent.ServerEventWrite(msg, c, extraData);
break;
case InventoryStateEventData inventoryStateEventData:
int containerIndex = components.IndexOf(inventoryStateEventData.Component);
if (containerIndex < 0)
{
throw error($"container index out of range ({containerIndex})");
}
if (!(components[containerIndex] is ItemContainer itemContainer))
{
throw error("component \"" + components[containerIndex] + "\" is not server serializable");
}
msg.WriteRangedInteger(containerIndex, 0, components.Count - 1);
msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
itemContainer.Inventory.ServerEventWrite(msg, c);
break;
case ItemStatusEventData _:
msg.WriteSingle(condition);
break;
case AssignCampaignInteractionEventData _:
msg.WriteByte((byte)CampaignInteractionType);
break;
case ApplyStatusEffectEventData applyStatusEffectEventData:
{
ActionType actionType = applyStatusEffectEventData.ActionType;
ItemComponent targetComponent = applyStatusEffectEventData.TargetItemComponent;
Limb targetLimb = applyStatusEffectEventData.TargetLimb;
Vector2? worldPosition = applyStatusEffectEventData.WorldPosition;
Character targetCharacter = applyStatusEffectEventData.TargetCharacter;
if (targetCharacter != null && targetCharacter.Removed) { targetCharacter = null; }
byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255;
msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1);
msg.WriteByte((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent)));
msg.WriteUInt16(applyStatusEffectEventData.TargetCharacter?.ID ?? (ushort)0);
msg.WriteByte(targetLimbIndex);
msg.WriteUInt16(applyStatusEffectEventData.UseTarget?.ID ?? (ushort)0);
msg.WriteBoolean(worldPosition.HasValue);
if (worldPosition.HasValue)
{
msg.WriteSingle(worldPosition.Value.X);
msg.WriteSingle(worldPosition.Value.Y);
}
}
break;
case ChangePropertyEventData changePropertyEventData:
try
{
WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: !GameMain.NetworkMember.IsServer);
}
catch (Exception e)
{
throw new Exception(
$"Failed to write a ChangeProperty network event for the item \"{Name}\" ({e.Message})");
}
break;
case SetItemStatEventData setItemStatEventData:
msg.WriteByte((byte)setItemStatEventData.Stats.Count);
foreach (var (key, value) in setItemStatEventData.Stats)
{
msg.WriteNetSerializableStruct(key);
msg.WriteSingle(value);
}
break;
case UpgradeEventData upgradeEventData:
var upgrade = upgradeEventData.Upgrade;
var upgradeTargets = upgrade.TargetComponents;
msg.WriteIdentifier(upgrade.Identifier);
msg.WriteByte((byte)upgrade.Level);
msg.WriteByte((byte)upgradeTargets.Count);
foreach (var (_, value) in upgrade.TargetComponents)
{
msg.WriteByte((byte)value.Length);
foreach (var propertyReference in value)
{
object originalValue = propertyReference.OriginalValue;
msg.WriteSingle((float)(originalValue ?? -1));
}
}
break;
default:
throw error($"Unsupported event type {itemEventData.GetType().Name}");
}
}
public void ServerEventRead(IReadMessage msg, Client c)
{
EventType eventType =
(EventType)msg.ReadRangedInteger((int)EventType.MinValue, (int)EventType.MaxValue);
c.KickAFKTimer = 0.0f;
switch (eventType)
{
case EventType.ComponentState:
int componentIndex = msg.ReadRangedInteger(0, components.Count - 1);
(components[componentIndex] as IClientSerializable).ServerEventRead(msg, c);
break;
case EventType.InventoryState:
int containerIndex = msg.ReadRangedInteger(0, components.Count - 1);
(components[containerIndex] as ItemContainer).Inventory.ServerEventRead(msg, c);
break;
case EventType.Treatment:
if (c.Character == null || !c.Character.CanInteractWith(this)) { return; }
UInt16 characterID = msg.ReadUInt16();
byte limbIndex = msg.ReadByte();
if (HealingCooldown.IsOnCooldown(c)) { return; }
if (FindEntityByID(characterID) is not Character targetCharacter) { break; }
if (targetCharacter != c.Character && c.Character.SelectedCharacter != targetCharacter) { break; }
HealingCooldown.SetCooldown(c);
Limb targetLimb = limbIndex < targetCharacter.AnimController.Limbs.Length ? targetCharacter.AnimController.Limbs[limbIndex] : null;
if (ContainedItems == null || ContainedItems.All(static i => i == null))
{
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} used item {Name}", ServerLog.MessageType.ItemInteraction);
}
else
{
GameServer.Log(
$"{GameServer.CharacterLogName(c.Character)} used item {Name} (contained items: {string.Join(", ", ContainedItems.Select(i => i.Name))})",
ServerLog.MessageType.ItemInteraction);
}
ApplyTreatment(c.Character, targetCharacter, targetLimb);
break;
case EventType.ChangeProperty:
ReadPropertyChange(msg, inGameEditableOnly: GameMain.NetworkMember.IsServer, sender: c);
break;
case EventType.Combine:
UInt16 combineTargetID = msg.ReadUInt16();
Item combineTarget = FindEntityByID(combineTargetID) as Item;
if (combineTarget == null || !c.Character.CanInteractWith(this) || !c.Character.CanInteractWith(combineTarget))
{
return;
}
Combine(combineTarget, c.Character);
break;
}
}
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID, UInt16 originalInventoryID, byte originalItemContainerIndex, int originalSlotIndex)
{
if (GameMain.Server == null) { return; }
msg.WriteString(Prefab.OriginalName);
msg.WriteIdentifier(Prefab.Identifier);
msg.WriteBoolean(Description != base.Prefab.Description);
if (Description != base.Prefab.Description)
{
msg.WriteString(Description);
}
msg.WriteUInt16(entityID);
if (ParentInventory == null || ParentInventory.Owner == null || originalInventoryID == 0)
{
msg.WriteUInt16((ushort)0);
msg.WriteSingle(Position.X);
msg.WriteSingle(Position.Y);
msg.WriteRangedSingle(body == null ? 0.0f : MathUtils.WrapAngleTwoPi(body.Rotation), 0.0f, MathHelper.TwoPi, 8);
msg.WriteUInt16(Submarine != null ? Submarine.ID : (ushort)0);
}
else
{
msg.WriteUInt16(originalInventoryID);
msg.WriteByte(originalItemContainerIndex);
msg.WriteByte(originalSlotIndex < 0 ? (byte)255 : (byte)originalSlotIndex);
}
msg.WriteByte(body == null ? (byte)0 : (byte)body.BodyType);
msg.WriteBoolean(SpawnedInCurrentOutpost);
msg.WriteBoolean(AllowStealing);
msg.WriteRangedInteger(Quality, 0, Items.Components.Quality.MaxQuality);
byte teamID = 0;
IdCard idCardComponent = null;
foreach (WifiComponent wifiComponent in GetComponents<WifiComponent>())
{
teamID = (byte)wifiComponent.TeamID;
break;
}
if (teamID == 0)
{
foreach (IdCard idCard in GetComponents<IdCard>())
{
teamID = (byte)idCard.TeamID;
idCardComponent = idCard;
break;
}
}
msg.WriteByte(teamID);
bool hasIdCard = idCardComponent != null;
msg.WriteBoolean(hasIdCard);
if (hasIdCard)
{
msg.WriteString(idCardComponent.OwnerName);
msg.WriteString(idCardComponent.OwnerTags);
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerBeardIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerHairIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerMoustacheIndex+1));
msg.WriteByte((byte)Math.Max(0, idCardComponent.OwnerFaceAttachmentIndex+1));
msg.WriteColorR8G8B8(idCardComponent.OwnerHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerFacialHairColor);
msg.WriteColorR8G8B8(idCardComponent.OwnerSkinColor);
msg.WriteIdentifier(idCardComponent.OwnerJobId);
msg.WriteByte((byte)idCardComponent.OwnerSheetIndex.X);
msg.WriteByte((byte)idCardComponent.OwnerSheetIndex.Y);
}
bool tagsChanged = tags.Count != base.Prefab.Tags.Count || !tags.All(t => base.Prefab.Tags.Contains(t));
msg.WriteBoolean(tagsChanged);
if (tagsChanged)
{
IEnumerable<Identifier> splitTags = Tags.Split(',').ToIdentifiers();
msg.WriteString(string.Join(',', splitTags.Where(t => !base.Prefab.Tags.Contains(t))));
msg.WriteString(string.Join(',', base.Prefab.Tags.Where(t => !splitTags.Contains(t))));
}
var nameTag = GetComponent<NameTag>();
msg.WriteBoolean(nameTag != null);
if (nameTag != null)
{
msg.WriteString(nameTag.WrittenName ?? "");
}
}
partial void UpdateNetPosition(float deltaTime)
{
if (parentInventory != null || body == null || !body.Enabled || Removed)
{
PositionUpdateInterval = float.PositiveInfinity;
return;
}
//gradually increase the interval of position updates
PositionUpdateInterval += deltaTime;
float maxInterval = 30.0f;
float velSqr = body.LinearVelocity.LengthSquared();
if (velSqr > 10.0f * 10.0f)
{
//over 10 m/s (projectile, thrown item or similar) -> send updates very frequently
maxInterval = 0.1f;
}
else if (velSqr > 1.0f)
{
//over 1 m/s
maxInterval = 0.25f;
}
else if (velSqr > 0.05f * 0.05f)
{
//over 0.05 m/s
maxInterval = 1.0f;
}
PositionUpdateInterval = Math.Min(PositionUpdateInterval, maxInterval);
}
public float GetPositionUpdateInterval(Client recipient)
{
if (PositionUpdateInterval == float.PositiveInfinity || body == null || parentInventory != null)
{
return float.PositiveInfinity;
}
if (recipient.Character == null || recipient.Character.IsDead)
{
//less frequent updates for clients who aren't controlling a character (max 2 updates/sec)
return Math.Max(PositionUpdateInterval, 0.5f);
}
else
{
float distSqr = Vector2.DistanceSquared(recipient.Character.WorldPosition, WorldPosition);
if (distSqr > 20000.0f * 20000.0f)
{
//don't send position updates at all if >20 000 units away
return float.PositiveInfinity;
}
else if (distSqr > 10000.0f * 10000.0f)
{
//drop the update rate to 10% if too far to see the item
return PositionUpdateInterval * 10;
}
else if (distSqr > 1000.0f * 1000.0f)
{
//halve the update rate if the client is far away (but still close enough to possibly see the item)
return PositionUpdateInterval * 2;
}
return PositionUpdateInterval;
}
}
public void ServerWritePosition(ReadWriteMessage tempBuffer, Client c)
{
body.ServerWrite(tempBuffer);
}
public void CreateServerEvent<T>(T ic) where T : ItemComponent, IServerSerializable
=> CreateServerEvent(ic, ic.ServerGetEventData());
public void CreateServerEvent<T>(T ic, ItemComponent.IEventData extraData) where T : ItemComponent, IServerSerializable
{
if (GameMain.Server == null) { return; }
if (!ItemList.Contains(this))
{
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.\n" + Environment.StackTrace.CleanupStackTrace();
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
#warning TODO: this should throw an exception
if (!components.Contains(ic)) { return; }
var eventData = new ComponentStateEventData(ic, extraData);
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false."); }
GameMain.Server.CreateEntityEvent(this, eventData);
}
#if DEBUG
public void TryCreateServerEventSpam()
{
if (GameMain.Server == null) { return; }
foreach (ItemComponent ic in components)
{
if (!(ic is IServerSerializable)) { continue; }
var eventData = new ComponentStateEventData(ic, ic.ServerGetEventData());
if (!ic.ValidateEventData(eventData)) { continue; }
GameMain.Server.CreateEntityEvent(this, eventData);
}
}
#endif
}
}