using Barotrauma.Items.Components; using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Xml.Linq; namespace Barotrauma { partial class Item : MapEntity, IDamageable, IPropertyObject, IServerSerializable, IClientSerializable { public override Sprite Sprite { get { return prefab.sprite; } } public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) { if (!Visible) return; Color color = (IsSelected && editing) ? color = Color.Red : spriteColor; if (isHighlighted) color = Color.Orange; SpriteEffects oldEffects = prefab.sprite.effects; prefab.sprite.effects ^= SpriteEffects; if (prefab.sprite != null) { float depth = Sprite.Depth; depth += (ID % 255) * 0.000001f; if (body == null) { if (prefab.ResizeHorizontal || prefab.ResizeVertical || SpriteEffects.HasFlag(SpriteEffects.FlipHorizontally) || SpriteEffects.HasFlag(SpriteEffects.FlipVertically)) { prefab.sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), color); } else { prefab.sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y), color, 0.0f, 1.0f, SpriteEffects.None, depth); } } else if (body.Enabled) { var holdable = GetComponent(); if (holdable != null && holdable.Picker?.AnimController != null) { if (holdable.Picker.SelectedItems[0] == this) { depth = holdable.Picker.AnimController.GetLimb(LimbType.RightHand).sprite.Depth + 0.000001f; } else if (holdable.Picker.SelectedItems[1] == this) { depth = holdable.Picker.AnimController.GetLimb(LimbType.LeftArm).sprite.Depth - 0.000001f; } body.Draw(spriteBatch, prefab.sprite, color, depth); } else { body.Draw(spriteBatch, prefab.sprite, color, depth); } } } prefab.sprite.effects = oldEffects; List staticDrawableComponents = new List(drawableComponents); //static list to compensate for drawable toggling for (int i = 0; i < staticDrawableComponents.Count; i++) { staticDrawableComponents[i].Draw(spriteBatch, editing); } if (GameMain.DebugDraw && aiTarget != null) aiTarget.Draw(spriteBatch); if (!editing || (body != null && !body.Enabled)) { return; } if (IsSelected || isHighlighted) { GUI.DrawRectangle(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)), new Vector2(rect.Width, rect.Height), Color.Green, false, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); foreach (Rectangle t in prefab.Triggers) { Rectangle transformedTrigger = TransformTrigger(t); Vector2 rectWorldPos = new Vector2(transformedTrigger.X, transformedTrigger.Y); if (Submarine != null) rectWorldPos += Submarine.Position; rectWorldPos.Y = -rectWorldPos.Y; GUI.DrawRectangle(spriteBatch, rectWorldPos, new Vector2(transformedTrigger.Width, transformedTrigger.Height), Color.Green, false, 0, (int)Math.Max((1.5f / GameScreen.Selected.Cam.Zoom), 1.0f)); } } if (!ShowLinks) return; foreach (MapEntity e in linkedTo) { GUI.DrawLine(spriteBatch, new Vector2(WorldPosition.X, -WorldPosition.Y), new Vector2(e.WorldPosition.X, -e.WorldPosition.Y), Color.Red * 0.3f); } } public override void UpdateEditing(Camera cam) { if (editingHUD == null || editingHUD.UserData as Item != this) { editingHUD = CreateEditingHUD(Screen.Selected != GameMain.EditMapScreen); } editingHUD.Update((float)Timing.Step); if (Screen.Selected != GameMain.EditMapScreen) return; if (!prefab.IsLinkable) return; if (!PlayerInput.LeftButtonClicked() || !PlayerInput.KeyDown(Keys.Space)) return; Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); foreach (MapEntity entity in mapEntityList) { if (entity == this || !entity.IsHighlighted) continue; if (linkedTo.Contains(entity)) continue; if (!entity.IsMouseOn(position)) continue; linkedTo.Add(entity); if (entity.IsLinkable && entity.linkedTo != null) entity.linkedTo.Add(this); } } public override void DrawEditing(SpriteBatch spriteBatch, Camera cam) { if (editingHUD != null) editingHUD.Draw(spriteBatch); } private GUIComponent CreateEditingHUD(bool inGame = false) { List editableProperties = inGame ? GetProperties() : GetProperties(); int requiredItemCount = 0; if (!inGame) { foreach (ItemComponent ic in components) { requiredItemCount += ic.requiredItems.Count; } } int width = 450; int height = 80 + requiredItemCount * 20; int x = GameMain.GraphicsWidth / 2 - width / 2, y = 10; foreach (var objectProperty in editableProperties) { var editable = objectProperty.Attributes.OfType().FirstOrDefault(); if (editable != null) height += (int)(Math.Ceiling(editable.MaxLength / 40.0f) * 18.0f) + 5; } editingHUD = new GUIFrame(new Rectangle(x, y, width, height), ""); editingHUD.Padding = new Vector4(10, 10, 0, 0); editingHUD.UserData = this; new GUITextBlock(new Rectangle(0, 0, 100, 20), prefab.Name, "", Alignment.TopLeft, Alignment.TopLeft, editingHUD, false, GUI.LargeFont); y += 25; if (!inGame) { if (prefab.IsLinkable) { new GUITextBlock(new Rectangle(0, 5, 0, 20), "Hold space to link to another item", "", Alignment.TopRight, Alignment.TopRight, editingHUD).Font = GUI.SmallFont; } foreach (ItemComponent ic in components) { foreach (RelatedItem relatedItem in ic.requiredItems) { new GUITextBlock(new Rectangle(0, y, 100, 15), ic.Name + ": " + relatedItem.Type.ToString() + " required", "", Alignment.TopLeft, Alignment.CenterLeft, editingHUD, false, GUI.SmallFont); GUITextBox namesBox = new GUITextBox(new Rectangle(-10, y, 160, 15), Alignment.Right, "", editingHUD); namesBox.Font = GUI.SmallFont; PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(relatedItem); PropertyDescriptor property = properties.Find("JoinedNames", false); namesBox.Text = relatedItem.JoinedNames; namesBox.UserData = new ObjectProperty(property, relatedItem); namesBox.OnEnterPressed = EnterProperty; namesBox.OnTextChanged = PropertyChanged; y += 20; } } if (requiredItemCount > 0) y += 10; } foreach (var objectProperty in editableProperties) { int boxHeight = 18; var editable = objectProperty.Attributes.OfType().FirstOrDefault(); if (editable != null) boxHeight = (int)(Math.Ceiling(editable.MaxLength / 40.0f) * 18.0f); object value = objectProperty.GetValue(); if (value is bool) { GUITickBox propertyTickBox = new GUITickBox(new Rectangle(10, y, 18, 18), objectProperty.Name, Alignment.Left, editingHUD); propertyTickBox.Font = GUI.SmallFont; propertyTickBox.Selected = (bool)value; propertyTickBox.UserData = objectProperty; propertyTickBox.OnSelected = EnterProperty; } else { new GUITextBlock(new Rectangle(0, y, 100, 18), objectProperty.Name, "", Alignment.TopLeft, Alignment.Left, editingHUD, false, GUI.SmallFont); GUITextBox propertyBox = new GUITextBox(new Rectangle(180, y, 250, boxHeight), "", editingHUD); propertyBox.Font = GUI.SmallFont; if (boxHeight > 18) propertyBox.Wrap = true; if (value != null) { if (value is float) { propertyBox.Text = ((float)value).ToString("G", System.Globalization.CultureInfo.InvariantCulture); } else { propertyBox.Text = value.ToString(); } } propertyBox.UserData = objectProperty; propertyBox.OnEnterPressed = EnterProperty; propertyBox.OnTextChanged = PropertyChanged; } y = y + boxHeight + 5; } return editingHUD; } public virtual void UpdateHUD(Camera cam, Character character) { if (condition <= 0.0f) { FixRequirement.UpdateHud(this, character); return; } if (HasInGameEditableProperties) { UpdateEditing(cam); } foreach (ItemComponent ic in components) { if (ic.CanBeSelected) ic.UpdateHUD(character); } } public virtual void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character) { if (condition <= 0.0f) { FixRequirement.DrawHud(spriteBatch, this, character); return; } if (HasInGameEditableProperties) { DrawEditing(spriteBatch, cam); } foreach (ItemComponent ic in components) { if (ic.CanBeSelected) ic.DrawHUD(spriteBatch, character); } } public override void AddToGUIUpdateList() { if (Screen.Selected is EditMapScreen) { if (editingHUD != null) editingHUD.AddToGUIUpdateList(); } else { if (HasInGameEditableProperties) { if (editingHUD != null) editingHUD.AddToGUIUpdateList(); } } if (Character.Controlled != null && Character.Controlled.SelectedConstruction == this) { if (condition <= 0.0f) { FixRequirement.AddToGUIUpdateList(); return; } foreach (ItemComponent ic in components) { if (ic.CanBeSelected) ic.AddToGUIUpdateList(); } } } private bool EnterProperty(GUITickBox tickBox) { var objectProperty = tickBox.UserData as ObjectProperty; if (objectProperty == null) return false; objectProperty.TrySetValue(tickBox.Selected); return true; } private bool EnterProperty(GUITextBox textBox, string text) { textBox.Color = Color.DarkGreen; var objectProperty = textBox.UserData as ObjectProperty; if (objectProperty == null) return false; object prevValue = objectProperty.GetValue(); textBox.Deselect(); if (objectProperty.TrySetValue(text)) { textBox.Text = text; if (GameMain.Server != null) { GameMain.Server.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ChangeProperty, objectProperty }); } else if (GameMain.Client != null) { GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ChangeProperty, objectProperty }); } return true; } else { if (prevValue != null) { textBox.Text = prevValue.ToString(); } return false; } } private bool PropertyChanged(GUITextBox textBox, string text) { textBox.Color = Color.Red; return true; } public override XElement Save(XElement parentElement) { XElement element = new XElement("Item"); element.Add(new XAttribute("name", prefab.Name), new XAttribute("ID", ID)); System.Diagnostics.Debug.Assert(Submarine != null); if (ResizeHorizontal || ResizeVertical) { element.Add(new XAttribute("rect", (int)(rect.X - Submarine.HiddenSubPosition.X) + "," + (int)(rect.Y - Submarine.HiddenSubPosition.Y) + "," + rect.Width + "," + rect.Height)); } else { element.Add(new XAttribute("rect", (int)(rect.X - Submarine.HiddenSubPosition.X) + "," + (int)(rect.Y - Submarine.HiddenSubPosition.Y))); } if (linkedTo != null && linkedTo.Count > 0) { string[] linkedToIDs = new string[linkedTo.Count]; for (int i = 0; i < linkedTo.Count; i++) { linkedToIDs[i] = linkedTo[i].ID.ToString(); } element.Add(new XAttribute("linked", string.Join(",", linkedToIDs))); } ObjectProperty.SaveProperties(this, element); foreach (ItemComponent ic in components) { ic.Save(element); } parentElement.Add(element); return element; } public void ClientRead(ServerNetObject type, NetBuffer 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); switch (eventType) { case NetEntityEvent.Type.ComponentState: int componentIndex = msg.ReadRangedInteger(0, components.Count - 1); (components[componentIndex] as IServerSerializable).ClientRead(type, msg, sendingTime); break; case NetEntityEvent.Type.InventoryState: ownInventory.ClientRead(type, msg, sendingTime); break; case NetEntityEvent.Type.Status: condition = msg.ReadRangedSingle(0.0f, 100.0f, 8); if (FixRequirements.Count > 0) { if (Condition <= 0.0f) { for (int i = 0; i < FixRequirements.Count; i++) FixRequirements[i].Fixed = msg.ReadBoolean(); } else { for (int i = 0; i < FixRequirements.Count; i++) FixRequirements[i].Fixed = true; } } break; case NetEntityEvent.Type.ApplyStatusEffect: ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1); ushort targetID = msg.ReadUInt16(); Character target = FindEntityByID(targetID) as Character; ApplyStatusEffects(actionType, (float)Timing.Step, target, true); break; case NetEntityEvent.Type.ChangeProperty: ReadPropertyChange(msg); break; } } public void ClientWrite(NetBuffer msg, object[] extraData = null) { if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) { return; } NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType); switch (eventType) { case NetEntityEvent.Type.ComponentState: int componentIndex = (int)extraData[1]; msg.WriteRangedInteger(0, components.Count - 1, componentIndex); (components[componentIndex] as IClientSerializable).ClientWrite(msg, extraData); break; case NetEntityEvent.Type.InventoryState: ownInventory.ClientWrite(msg, extraData); break; case NetEntityEvent.Type.Repair: if (FixRequirements.Count > 0) { int requirementIndex = (int)extraData[1]; msg.WriteRangedInteger(0, FixRequirements.Count - 1, requirementIndex); } break; case NetEntityEvent.Type.ApplyStatusEffect: //no further data needed, the server applies the effect //on the character of the client who sent the message break; case NetEntityEvent.Type.ChangeProperty: WritePropertyChange(msg, extraData); break; } msg.WritePadBits(); } public void ClientReadPosition(ServerNetObject type, NetBuffer msg, float sendingTime) { Vector2 newPosition = new Vector2(msg.ReadFloat(), msg.ReadFloat()); float newRotation = msg.ReadRangedSingle(0.0f, MathHelper.TwoPi, 7); bool awake = msg.ReadBoolean(); Vector2 newVelocity = Vector2.Zero; if (awake) { newVelocity = new Vector2( msg.ReadRangedSingle(-MaxVel, MaxVel, 12), msg.ReadRangedSingle(-MaxVel, MaxVel, 12)); } if (body == null) { DebugConsole.ThrowError("Received a position update for an item with no physics body (" + Name + ")"); return; } body.FarseerBody.Awake = awake; if (body.FarseerBody.Awake) { if ((newVelocity - body.LinearVelocity).Length() > 8.0f) body.LinearVelocity = newVelocity; } else { body.FarseerBody.Enabled = false; } if ((newPosition - SimPosition).Length() > body.LinearVelocity.Length() * 2.0f) { body.SetTransform(newPosition, newRotation); Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition); rect.X = (int)(displayPos.X - rect.Width / 2.0f); rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); } } public void CreateClientEvent(T ic) where T : ItemComponent, IClientSerializable { if (GameMain.Client == null) return; int index = components.IndexOf(ic); if (index == -1) return; GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ComponentState, index }); } } }