Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Map/MapEntity.cs
T
Joonas Rikkonen 81fafaf3f5 633e54b...7cc231b
commit 7cc231bc51890e7fde50bbac3413328dd7bc5189
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 14:23:06 2019 +0200

    Fixed server creating network events in Item.Load due to CreateServerEvent calls in some of the ItemComponent properties (e.g. LightComponent.IsOn). Caused syncing problems because the entity spawn events aren't created until the item has been loaded, leading to situations where clients fail to read events because the entity doesn't exist at their end yet (see #1293). TODO: get rid of console errors when attempting to create events during component initialization in the Item constructor.

commit ac1bf32edf125d95d56775e1e8ce54ac7671328c
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:49:55 2019 +0200

    Fix Hammerhead attack causing warping. Adjust the targeting priorities.

commit e3e032d7c3a753397269bb4236c1494ab0809004
Author: itchyOwl <lauri.harkanen@gmail.com>
Date:   Wed Mar 20 13:48:47 2019 +0200

    Enemy AI fixes:
    - Fix enemies "fleeing" after they have been shot. There was a steering issue when they targeted characters that were inside the sub when they were outside of it
    - Fix the previous target resetting too often
    - Fix the wall target resetting too often
    - Use world positions instead of sim positions where possible, because the sub positions are then taken into account

commit 847cf5ffd9212a542000dbf12332b2c08756579a
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Wed Mar 20 12:00:15 2019 +0200

    Remora: added power connection between sub and drone, removed non-scaling hull parts

commit a9c2e8cc124713e90dd44a9adf2fcbb3204b2c4d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Wed Mar 20 11:24:37 2019 +0200

    Server sets clients' last received campaign save/update IDs to one before the current up-to-date IDs (instead of zero) when a campaign changes. Zero would get interpreted as a more up-to-date ID if the IDs of the current campaign are close to ushort.MaxValue where the IDs wrap around.

commit 07d82b64e6990eacaf8905aed1a5d7c61224e47d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Tue Mar 19 18:24:01 2019 +0200

    Log level inequality error messages & client error reports handled by the server to GameAnalytics
2019-03-20 14:25:03 +02:00

535 lines
17 KiB
C#

using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
namespace Barotrauma
{
abstract partial class MapEntity : Entity
{
public static List<MapEntity> mapEntityList = new List<MapEntity>();
public readonly MapEntityPrefab prefab;
protected List<ushort> linkedToID;
//observable collection because some entities may need to be notified when the collection is modified
public ObservableCollection<MapEntity> linkedTo;
private bool flippedX, flippedY;
public bool FlippedX { get { return flippedX; } }
public bool FlippedY { get { return flippedY; } }
public bool ShouldBeSaved = true;
//the position and dimensions of the entity
protected Rectangle rect;
//is the mouse inside the rect
protected bool isHighlighted;
public bool IsHighlighted
{
get { return isHighlighted; }
set { isHighlighted = value; }
}
public virtual Rectangle Rect
{
get { return rect; }
set { rect = value; }
}
public Rectangle WorldRect
{
get { return Submarine == null ? rect : new Rectangle((int)(Submarine.Position.X + rect.X), (int)(Submarine.Position.Y + rect.Y), rect.Width, rect.Height); }
}
public virtual Sprite Sprite
{
get { return null; }
}
public virtual bool DrawBelowWater
{
get
{
return Sprite != null && SpriteDepth > 0.5f;
}
}
public virtual bool DrawOverWater
{
get
{
return !DrawBelowWater;
}
}
public virtual bool DrawDamageEffect
{
get
{
return false;
}
}
public virtual bool Linkable
{
get { return false; }
}
public List<string> AllowedLinks => prefab == null ? new List<string>() : prefab.AllowedLinks;
public bool ResizeHorizontal
{
get { return prefab != null && prefab.ResizeHorizontal; }
}
public bool ResizeVertical
{
get { return prefab != null && prefab.ResizeVertical; }
}
public override Vector2 Position
{
get
{
Vector2 rectPos = new Vector2(
rect.X + rect.Width / 2.0f,
rect.Y - rect.Height / 2.0f);
//if (MoveWithLevel) rectPos += Level.Loaded.Position;
return rectPos;
}
}
public override Vector2 SimPosition
{
get
{
return ConvertUnits.ToSimUnits(Position);
}
}
public float SoundRange
{
get
{
if (aiTarget == null) return 0.0f;
return aiTarget.SoundRange;
}
set
{
if (aiTarget == null) return;
aiTarget.SoundRange = value;
}
}
public float SightRange
{
get
{
if (aiTarget == null) return 0.0f;
return aiTarget.SightRange;
}
set
{
if (aiTarget == null) return;
aiTarget.SightRange = value;
}
}
public RuinGeneration.Ruin ParentRuin
{
get;
set;
}
public virtual string Name
{
get { return ""; }
}
// Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation.
private Memento<Rectangle> rectMemento;
public MapEntity(MapEntityPrefab prefab, Submarine submarine) : base(submarine)
{
this.prefab = prefab;
Scale = prefab != null ? prefab.Scale : 1;
}
public virtual void Move(Vector2 amount)
{
rect.X += (int)amount.X;
rect.Y += (int)amount.Y;
}
public virtual bool IsMouseOn(Vector2 position)
{
return (Submarine.RectContains(WorldRect, position));
}
public abstract MapEntity Clone();
public static List<MapEntity> Clone(List<MapEntity> entitiesToClone)
{
List<MapEntity> clones = new List<MapEntity>();
foreach (MapEntity e in entitiesToClone)
{
Debug.Assert(e != null);
try
{
clones.Add(e.Clone());
}
catch (Exception ex)
{
DebugConsole.ThrowError("Cloning entity \"" + e.Name + "\" failed.", ex);
GameAnalyticsManager.AddErrorEventOnce(
"MapEntity.Clone:" + e.Name,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Cloning entity \"" + e.Name + "\" failed (" + ex.Message + ").\n" + ex.StackTrace);
return clones;
}
Debug.Assert(clones.Last() != null);
}
Debug.Assert(clones.Count == entitiesToClone.Count);
//clone links between the entities
for (int i = 0; i < clones.Count; i++)
{
if (entitiesToClone[i].linkedTo == null) continue;
foreach (MapEntity linked in entitiesToClone[i].linkedTo)
{
if (!entitiesToClone.Contains(linked)) continue;
clones[i].linkedTo.Add(clones[entitiesToClone.IndexOf(linked)]);
}
}
//connect clone wires to the clone items
for (int i = 0; i < clones.Count; i++)
{
var cloneItem = clones[i] as Item;
if (cloneItem == null) continue;
var cloneWire = cloneItem.GetComponent<Wire>();
if (cloneWire == null) continue;
var originalWire = ((Item)entitiesToClone[i]).GetComponent<Wire>();
cloneWire.SetNodes(originalWire.GetNodes());
for (int n = 0; n < 2; n++)
{
if (originalWire.Connections[n] == null) continue;
var connectedItem = originalWire.Connections[n].Item;
if (connectedItem == null) continue;
//index of the item the wire is connected to
int itemIndex = entitiesToClone.IndexOf(connectedItem);
if (itemIndex < 0)
{
DebugConsole.ThrowError("Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectedNotFound" + connectedItem.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
continue;
}
//index of the connection in the connectionpanel of the target item
int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]);
if (connectionIndex < 0)
{
DebugConsole.ThrowError("Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectionNotFound" + connectedItem.ID,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
continue;
}
(clones[itemIndex] as Item).Connections[connectionIndex].TryAddLink(cloneWire);
cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], false);
}
}
return clones;
}
protected void InsertToList()
{
int i = 0;
if (Sprite==null)
{
mapEntityList.Add(this);
return;
}
while (i<mapEntityList.Count)
{
i++;
Sprite existingSprite = mapEntityList[i-1].Sprite;
if (existingSprite == null) continue;
#if CLIENT
if (existingSprite.Texture == this.Sprite.Texture) break;
#endif
}
mapEntityList.Insert(i, this);
}
/// <summary>
/// Remove the entity from the entity list without removing links to other entities
/// </summary>
public virtual void ShallowRemove()
{
base.Remove();
mapEntityList.Remove(this);
if (aiTarget != null) aiTarget.Remove();
}
public override void Remove()
{
base.Remove();
mapEntityList.Remove(this);
#if CLIENT
if (selectedList.Contains(this))
{
selectedList = selectedList.FindAll(e => e != this);
}
#endif
if (aiTarget != null) aiTarget.Remove();
if (linkedTo != null)
{
for (int i = linkedTo.Count - 1; i >= 0; i-- )
{
linkedTo[i].RemoveLinked(this);
}
linkedTo.Clear();
}
}
/// <summary>
/// Call Update() on every object in Entity.list
/// </summary>
public static void UpdateAll(float deltaTime, Camera cam)
{
foreach (Hull hull in Hull.hullList)
{
hull.Update(deltaTime, cam);
}
foreach (Gap gap in Gap.GapList)
{
gap.Update(deltaTime, cam);
}
foreach (Item item in Item.ItemList)
{
item.Update(deltaTime, cam);
}
UpdateAllProjSpecific(deltaTime);
Spawner?.Update();
}
static partial void UpdateAllProjSpecific(float deltaTime);
public virtual void Update(float deltaTime, Camera cam) { }
/// <summary>
/// Flip the entity horizontally
/// </summary>
/// <param name="relativeToSub">Should the entity be flipped across the y-axis of the sub it's inside</param>
public virtual void FlipX(bool relativeToSub)
{
flippedX = !flippedX;
if (!relativeToSub || Submarine == null) return;
Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.Y = 0.0f;
Move(-relative * 2.0f);
}
/// <summary>
/// Flip the entity vertically
/// </summary>
/// <param name="relativeToSub">Should the entity be flipped across the x-axis of the sub it's inside</param>
public virtual void FlipY(bool relativeToSub)
{
flippedY = !flippedY;
if (!relativeToSub || Submarine == null) return;
Vector2 relative = WorldPosition - Submarine.WorldPosition;
relative.X = 0.0f;
Move(-relative * 2.0f);
}
public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath)
{
List<MapEntity> entities = new List<MapEntity>();
foreach (XElement element in parentElement.Elements())
{
string typeName = element.Name.ToString();
Type t;
try
{
t = Type.GetType("Barotrauma." + typeName, true, true);
if (t == null)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".");
continue;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".", e);
continue;
}
try
{
MethodInfo loadMethod = t.GetMethod("Load", new [] { typeof(XElement), typeof(Submarine) });
if (loadMethod == null)
{
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
}
else if (!loadMethod.ReturnType.IsSubclassOf(typeof(MapEntity)))
{
DebugConsole.ThrowError("Error loading entity of the type \"" + t.ToString() + "\" - load method does not return a valid map entity.");
}
else
{
object newEntity = loadMethod.Invoke(t, new object[] { element, submarine });
if (newEntity != null) entities.Add((MapEntity)newEntity);
}
}
catch (TargetInvocationException e)
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e);
}
}
return entities;
}
/// <summary>
/// Update the linkedTo-lists of the entities based on the linkedToID-lists
/// Has to be done after all the entities have been loaded (an entity can't
/// be linked to some other entity that hasn't been loaded yet)
/// </summary>
private bool mapLoadedCalled;
public static void MapLoaded(List<MapEntity> entities, bool updateHulls)
{
foreach (MapEntity e in entities)
{
if (e.mapLoadedCalled) continue;
if (e.linkedToID == null) continue;
if (e.linkedToID.Count == 0) continue;
e.linkedTo.Clear();
foreach (ushort i in e.linkedToID)
{
if (FindEntityByID(i) is MapEntity linked) e.linkedTo.Add(linked);
}
}
List<LinkedSubmarine> linkedSubs = new List<LinkedSubmarine>();
for (int i = 0; i < entities.Count; i++)
{
if (entities[i].mapLoadedCalled) continue;
if (entities[i] is LinkedSubmarine)
{
linkedSubs.Add((LinkedSubmarine)entities[i]);
continue;
}
entities[i].OnMapLoaded();
}
if (updateHulls)
{
Item.UpdateHulls();
Gap.UpdateHulls();
}
entities.ForEach(e => e.mapLoadedCalled = true);
foreach (LinkedSubmarine linkedSub in linkedSubs)
{
linkedSub.OnMapLoaded();
}
}
public virtual void OnMapLoaded() { }
public virtual XElement Save(XElement parentElement)
{
DebugConsole.ThrowError("Saving entity " + GetType() + " failed.");
return null;
}
public void RemoveLinked(MapEntity e)
{
if (linkedTo == null) return;
if (linkedTo.Contains(e)) linkedTo.Remove(e);
}
#region Serialized properties
// We could use NaN or nullables, but in this case the first is not preferable, because it needs to be checked every time the value is used.
// Nullable on the other requires boxing that we don't want to do too often, since it generates garbage.
public bool SpriteDepthOverrideIsSet { get; private set; }
public float SpriteOverrideDepth => SpriteDepth;
private float _spriteOverrideDepth = float.NaN;
[Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, true)]
public float SpriteDepth
{
get
{
if (SpriteDepthOverrideIsSet) { return _spriteOverrideDepth; }
return Sprite != null ? Sprite.Depth : 0;
}
set
{
if (!float.IsNaN(value))
{
_spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999f);
SpriteDepthOverrideIsSet = true;
}
}
}
// The value should always be copied from the prefab. Editing is enabled only for testing the scale in the sub editor (changes are not saved).
#if DEBUG
[Serialize(1f, false), Editable(0.1f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
#else
[Serialize(1f, false)]
#endif
public float Scale { get; set; } = 1;
#endregion
}
}