Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/StructurePrefab.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Linq;
5 using System.Collections.Generic;
6 using System.Xml.Linq;
7 using Barotrauma.IO;
8 using System.Collections.Immutable;
9 using System.ComponentModel;
10 #if CLIENT
11 using Microsoft.Xna.Framework.Graphics;
12 #endif
13 
14 namespace Barotrauma
15 {
17  {
19 
20  public override LocalizedString Name { get; }
21 
22  public readonly ContentXElement ConfigElement;
23 
24  public override bool CanSpriteFlipX { get; }
25  public override bool CanSpriteFlipY { get; }
26 
30  public readonly bool? IsHorizontal;
31 
32  public Vector2 ScaledSize => Size * Scale;
33 
34  public readonly Sprite BackgroundSprite;
35 
36  public override Sprite Sprite { get; }
37 
38  public override string OriginalName { get; }
39 
40  public override ImmutableHashSet<Identifier> Tags { get; }
41 
42  public override ImmutableHashSet<Identifier> AllowedLinks { get; }
43 
44  public override MapEntityCategory Category { get; }
45 
46  public override ImmutableHashSet<string> Aliases { get; }
47 
48  [Serialize(false, IsPropertySaveable.No, description: "Does the structure have a physics body?")]
49  public bool Body { get; private set; }
50 
51  [Serialize(0.0f, IsPropertySaveable.No, description: "Rotation of the physics body in degrees.")]
52  public float BodyRotation { get; private set; }
53 
54  [Serialize(0.0f, IsPropertySaveable.No, description: "Width of the physics body in pixels.")]
55  public float BodyWidth { get; private set; }
56 
57  [Serialize(0.0f, IsPropertySaveable.No, description: "Height of the physics body in pixels.")]
58  public float BodyHeight { get; private set; }
59 
60  //in display units
61  [Serialize("0.0,0.0", IsPropertySaveable.No, description: "Offset of the physics body from the center of the structure in pixels.")]
62  public Vector2 BodyOffset { get; private set; }
63 
64  [Serialize(false, IsPropertySaveable.No, description: "Is the structure a platform (i.e. a \"floor\" the players can pass through)? Only relevant if the structure has a physics body.")]
65  public bool Platform { get; private set; }
66 
67  [Serialize(false, IsPropertySaveable.No, description: "Can items like signal components be attached on this structure? Should be enabled on structures like decorative background walls.")]
68  public bool AllowAttachItems { get; private set; }
69 
70  [Serialize(true, IsPropertySaveable.No, description: "Can the structure be rotated in the submarine editor?")]
71  public bool AllowRotatingInEditor { get; set; }
72 
73  [Serialize(0.0f, IsPropertySaveable.No)]
74  public float MinHealth { get; private set; }
75 
76  private float health;
77  [Serialize(100.0f, IsPropertySaveable.No)]
78  public float Health
79  {
80  get { return health; }
81  private set { health = Math.Max(value, MinHealth); }
82  }
83 
84  [Serialize(true, IsPropertySaveable.No, description: "Should the structure be indestructible when used in an outpost?")]
85  public bool IndestructibleInOutposts { get; private set; }
86 
87  [Serialize(false, IsPropertySaveable.No, description: "Should the structure cast shadows and obstruct visibility when LOS is enabled?")]
88  public bool CastShadow { get; private set; }
89 
90  [Serialize(Direction.None, IsPropertySaveable.No, description: "Makes the structure function as a staircase.")]
91  public Direction StairDirection { get; private set; }
92 
93  [Serialize(45.0f, IsPropertySaveable.No, description: "Angle of the stairs in degrees. Only relevant if StairDirection is something else than None.")]
94  public float StairAngle { get; private set; }
95 
96  [Serialize(false, IsPropertySaveable.No, description: "If enabled, monsters will not be able to target this structure.")]
97  public bool NoAITarget { get; private set; }
98 
99  [Serialize("0,0", IsPropertySaveable.Yes, description: "Size of the structure in pixels. If not set, the size is determined, based on the attributes width and height, and if those aren't defined either, based on the size of the structure's sprite.")]
100  public Vector2 Size { get; private set; }
101 
102  [Serialize("", IsPropertySaveable.Yes, description: "Tag of the sound that plays when something damages the wall.")]
103  public string DamageSound { get; private set; }
104 
105  [Serialize("shrapnel", IsPropertySaveable.Yes, description: "Identifier of the particles emitted when something damages the wall.")]
106  public string DamageParticle { get; private set; }
107 
108  protected Vector2 textureScale = Vector2.One;
109  [Editable(DecimalCount = 3), Serialize("1.0, 1.0", IsPropertySaveable.Yes)]
110  public Vector2 TextureScale
111  {
112  get { return textureScale; }
113  private set
114  {
115  textureScale = new Vector2(
116  MathHelper.Clamp(value.X, 0.01f, 10),
117  MathHelper.Clamp(value.Y, 0.01f, 10));
118  }
119  }
120 
121  protected override Identifier DetermineIdentifier(XElement element)
122  {
123  Identifier identifier = base.DetermineIdentifier(element);
124  string originalName = element.GetAttributeString("name", "");
125  if (identifier.IsEmpty && !string.IsNullOrEmpty(originalName))
126  {
127  string categoryStr = element.GetAttributeString("category", "Misc");
128  if (Enum.TryParse(categoryStr, true, out MapEntityCategory category) && category.HasFlag(MapEntityCategory.Legacy))
129  {
130  identifier = $"legacystructure_{originalName.Replace(" ", "")}".ToIdentifier();
131  }
132  }
133  return identifier;
134  }
135 
136  public StructurePrefab(ContentXElement element, StructureFile file) : base(element, file)
137  {
138  OriginalName = element.GetAttributeString("name", "");
139  ConfigElement = element;
140 
141  var parentType = element.Parent?.GetAttributeIdentifier("prefabtype", Identifier.Empty) ?? Identifier.Empty;
142 
143  Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", "");
144 
145  //only used if the item doesn't have a name/description defined in the currently selected language
146  Identifier fallbackNameIdentifier = element.GetAttributeIdentifier("fallbacknameidentifier", "");
147 
148  Name = TextManager.Get(nameIdentifier.IsEmpty
149  ? $"EntityName.{Identifier}"
150  : $"EntityName.{nameIdentifier}",
151  $"EntityName.{fallbackNameIdentifier}");
152 
153  if (parentType == "wrecked")
154  {
155  Name = TextManager.GetWithVariable("wreckeditemformat", "[name]", Name);
156  }
157  if (!string.IsNullOrEmpty(OriginalName))
158  {
160  }
161 
162  var tags = new HashSet<Identifier>();
163  string joinedTags = element.GetAttributeString("tags", "");
164  if (string.IsNullOrEmpty(joinedTags)) joinedTags = element.GetAttributeString("Tags", "");
165  foreach (string tag in joinedTags.Split(','))
166  {
167  tags.Add(tag.Trim().ToIdentifier());
168  }
169 
170  if (element.GetAttribute("ishorizontal") != null)
171  {
172  IsHorizontal = element.GetAttributeBool("ishorizontal", false);
173  }
174 
175 #if CLIENT
176  var decorativeSprites = new List<DecorativeSprite>();
177  var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
178 #endif
179  foreach (var subElement in element.Elements())
180  {
181  switch (subElement.Name.ToString().ToLowerInvariant())
182  {
183  case "sprite":
184  Sprite = new Sprite(subElement, lazyLoad: true);
185  if (subElement.GetAttribute("sourcerect") == null &&
186  subElement.GetAttribute("sheetindex") == null)
187  {
188  DebugConsole.ThrowErrorLocalized("Warning - sprite sourcerect not configured for structure \"" + Name + "\"!");
189  }
190 #if CLIENT
191  if (subElement.GetAttributeBool("fliphorizontal", false))
192  Sprite.effects = SpriteEffects.FlipHorizontally;
193  if (subElement.GetAttributeBool("flipvertical", false))
194  Sprite.effects = SpriteEffects.FlipVertically;
195 #endif
196  CanSpriteFlipX = subElement.GetAttributeBool("canflipx", true);
197  CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
198 
199  if (subElement.GetAttribute("name") == null && !Name.IsNullOrWhiteSpace())
200  {
201  Sprite.Name = Name.Value;
202  }
204  break;
205  case "backgroundsprite":
206  BackgroundSprite = new Sprite(subElement, lazyLoad: true);
207  if (subElement.GetAttribute("sourcerect") == null && Sprite != null)
208  {
213  BackgroundSprite.RelativeOrigin = subElement.GetAttributeVector2("origin", new Vector2(0.5f, 0.5f));
214  }
215 #if CLIENT
216  if (subElement.GetAttributeBool("fliphorizontal", false)) { BackgroundSprite.effects = SpriteEffects.FlipHorizontally; }
217  if (subElement.GetAttributeBool("flipvertical", false)) { BackgroundSprite.effects = SpriteEffects.FlipVertically; }
218  BackgroundSpriteColor = subElement.GetAttributeColor("color", Color.White);
219 #endif
220  break;
221  case "decorativesprite":
222 #if CLIENT
223  string decorativeSpriteFolder = "";
224  if (subElement.DoesAttributeReferenceFileNameAlone("texture"))
225  {
226  decorativeSpriteFolder = Path.GetDirectoryName(file.Path);
227  }
228 
229  int groupID = 0;
230  DecorativeSprite decorativeSprite = null;
231  if (subElement.GetAttribute("texture") == null)
232  {
233  groupID = subElement.GetAttributeInt("randomgroupid", 0);
234  }
235  else
236  {
237  decorativeSprite = new DecorativeSprite(subElement, decorativeSpriteFolder, lazyLoad: true);
238  decorativeSprites.Add(decorativeSprite);
239  groupID = decorativeSprite.RandomGroupID;
240  }
241  if (!decorativeSpriteGroups.ContainsKey(groupID))
242  {
243  decorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
244  }
245  decorativeSpriteGroups[groupID].Add(decorativeSprite);
246 #endif
247  break;
248  }
249  }
250 #if CLIENT
251  DecorativeSprites = decorativeSprites.ToImmutableArray();
252  DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
253 #endif
254 
255  string categoryStr = element.GetAttributeString("category", "Structure");
256  if (!Enum.TryParse(categoryStr, true, out MapEntityCategory category))
257  {
258  category = MapEntityCategory.Structure;
259  }
260  Category = category;
261 
262  Aliases =
263  (element.GetAttributeStringArray("aliases", null, convertToLowerInvariant: true) ??
264  element.GetAttributeStringArray("Aliases", Array.Empty<string>(), convertToLowerInvariant: true)).ToImmutableHashSet();
265 
266  string nonTranslatedName = element.GetAttributeString("name", null) ?? element.Name.ToString();
267  Aliases.Add(nonTranslatedName.ToLowerInvariant());
268 
270  if (Body)
271  {
272  tags.Add("wall".ToIdentifier());
273  }
274 
275  LoadDescription(element);
276 
277  //backwards compatibility
278  if (element.GetAttribute("size") == null)
279  {
280  Size = Vector2.Zero;
281  if (element.GetAttribute("width") == null && element.GetAttribute("height") == null)
282  {
283  Size = Sprite.SourceRect.Size.ToVector2();
284  }
285  else
286  {
287  Size = new Vector2(
288  element.GetAttributeFloat("width", 0.0f),
289  element.GetAttributeFloat("height", 0.0f));
290  }
291  }
292 
293  //backwards compatibility
294  if (categoryStr.Equals("Thalamus", StringComparison.OrdinalIgnoreCase))
295  {
296  Category = MapEntityCategory.Wrecked;
297  Subcategory = "Thalamus";
298  }
299 
300  if (Identifier == Identifier.Empty)
301  {
302  DebugConsole.ThrowError(
303  "Structure prefab \"" + Name.Value + "\" has no identifier. All structure prefabs have a unique identifier string that's used to differentiate between items during saving and loading.",
304  contentPackage: ContentPackage);
305  }
306 #if DEBUG
307  if (!Category.HasFlag(MapEntityCategory.Legacy) && !HideInMenus)
308  {
309  if (!string.IsNullOrEmpty(OriginalName))
310  {
311  DebugConsole.AddWarning($"Structure \"{(Identifier == Identifier.Empty ? Name : Identifier.Value)}\" has a hard-coded name, and won't be localized to other languages.",
313  }
314  }
315 #endif
316 
317  Tags = tags.ToImmutableHashSet();
318  AllowedLinks = ImmutableHashSet<Identifier>.Empty;
319  }
320 
321  protected override void CreateInstance(Rectangle rect)
322  {
323  throw new NotImplementedException();
324  }
325 
326  public override void Dispose() { }
327  }
328 }
string? GetAttributeString(string key, string? def)
Identifier GetAttributeIdentifier(string key, string def)
int RandomGroupID
If > 0, only one sprite of the same group is used (chosen randomly)
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
readonly Identifier Identifier
Definition: Prefab.cs:34
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
Identifier EntityIdentifier
Identifier of the Map Entity so that we can link the sprite to its owner.
override ImmutableHashSet< Identifier > AllowedLinks
readonly ImmutableDictionary< int, ImmutableArray< DecorativeSprite > > DecorativeSpriteGroups
static readonly PrefabCollection< StructurePrefab > Prefabs
readonly ImmutableArray< DecorativeSprite > DecorativeSprites
StructurePrefab(ContentXElement element, StructureFile file)
override Identifier DetermineIdentifier(XElement element)
readonly? bool IsHorizontal
If null, the orientation is determined automatically based on the dimensions of the structure instanc...