Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Characters/CharacterInfo.cs
3 using System;
4 using System.Linq;
5 using System.Collections.Generic;
6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 using System.Xml.Linq;
9 using Barotrauma.IO;
11 using System.Collections.Immutable;
12 
13 namespace Barotrauma
14 {
15  partial class CharacterInfo
16  {
17  private static Sprite infoAreaPortraitBG;
18 
19  public bool LastControlled;
20  public int CrewListIndex { get; set; } = -1;
21 
22  private Sprite disguisedPortrait;
23  private List<WearableSprite> disguisedAttachmentSprites;
24  private Vector2? disguisedSheetIndex;
25  private Sprite disguisedJobIcon;
26  private Color disguisedJobColor;
27  private Color disguisedHairColor;
28  private Color disguisedFacialHairColor;
29  private Color disguisedSkinColor;
30 
31  private Sprite tintMask;
32  private float tintHighlightThreshold;
33  private float tintHighlightMultiplier;
34 
35  public static void Init()
36  {
37  infoAreaPortraitBG = GUIStyle.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
38  new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
39  }
40 
41  partial void LoadHeadSpriteProjectSpecific(ContentXElement limbElement)
42  {
43  ContentXElement maskElement = limbElement.GetChildElement("tintmask");
44  if (maskElement != null)
45  {
46  ContentPath tintMaskPath = maskElement.GetAttributeContentPath("texture");
47  if (!tintMaskPath.IsNullOrEmpty())
48  {
50  tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this));
51  tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f);
52  tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f);
53  }
54  }
55  }
56 
57  public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null)
58  {
59  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.874f, 0.58f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) })
60  {
61  RelativeSpacing = 0.05f
62  //Stretch = true
63  };
64 
65  var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform), isHorizontal: true);
66 
67  new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
68  onDraw: (sb, component) => DrawInfoFrameCharacterIcon(sb, component.Rect));
69 
70  GUIFont font = paddedFrame.Rect.Width < 280 ? GUIStyle.SmallFont : GUIStyle.Font;
71 
72  var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.575f, 1.0f), headerArea.RectTransform))
73  {
74  RelativeSpacing = 0.02f,
75  Stretch = true
76  };
77 
78  Color? nameColor = null;
79  if (Job != null) { nameColor = Job.Prefab.UIColor; }
80 
81  GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUIStyle.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUIStyle.Font)
82  {
84  Padding = Vector4.Zero
85  };
86 
87  if (permissionIcon != null)
88  {
89  Point iconSize = permissionIcon.SourceRect.Size;
90  int iconWidth = (int)((float)characterNameBlock.Rect.Height / iconSize.Y * iconSize.X);
91  new GUIImage(new RectTransform(new Point(iconWidth, characterNameBlock.Rect.Height), characterNameBlock.RectTransform) { AbsoluteOffset = new Point(-iconWidth - 2, 0) }, permissionIcon) { IgnoreLayoutGroups = true };
92  }
93 
94  if (Job != null)
95  {
96  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font)
97  {
98  Padding = Vector4.Zero
99  };
100  }
101 
102  if (PersonalityTrait != null)
103  {
104  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
105  TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName),
106  font: font)
107  {
108  Padding = Vector4.Zero
109  };
110  }
111 
112  GUIButton manageTalentButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
113  text: TextManager.Get("ClientPermission.ManageBotTalents"), style: "GUIButtonSmall")
114  {
115  Enabled = false,
116  UserData = TalentMenu.ManageBotTalentsButtonUserData,
117  TextBlock =
118  {
119  AutoScaleHorizontal = true
120  }
121  };
122 
123  if (TalentMenu.CanManageTalents(this))
124  {
125  manageTalentButton.Enabled = true;
126  }
127 
128  if (Job != null && Character is not { IsDead: true })
129  {
130  var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
131  {
132  Stretch = true
133  };
134 
135  var skills = Job.GetSkills().ToList();
136  skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
137 
138  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero };
139 
140  foreach (Skill skill in skills)
141  {
142  Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
143 
144  var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), skill.DisplayName, textColor: textColor, font: font) { Padding = Vector4.Zero };
145 
146  float modifiedSkillLevel = skill.Level;
147  if (Character != null)
148  {
149  modifiedSkillLevel = Character.GetSkillLevel(skill.Identifier);
150  }
151  if (!MathUtils.NearlyEqual(MathF.Round(modifiedSkillLevel), MathF.Round(skill.Level)))
152  {
153  int skillChange = (int)MathF.Round(modifiedSkillLevel - skill.Level);
154  string changeText = $"{(skillChange > 0 ? "+" : "") + skillChange}";
155  new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), $"{(int)skill.Level} ({changeText})", textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
156  }
157  else
158  {
159  new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), ((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
160  }
161  }
162  }
163  else if (Character is { IsDead: true })
164  {
165  var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
166  {
167  Stretch = true
168  };
169 
170  LocalizedString deadDescription =
171  TextManager.Get("deceased") + "\n" +
172  (Character.CauseOfDeath.Affliction?.CauseOfDeathDescription ?? TextManager.Get("CauseOfDeath." + Character.CauseOfDeath.Type.ToString()));
173 
174  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), deadArea.RectTransform), deadDescription, textColor: GUIStyle.Red, font: font, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
175  }
176 
177  if (returnParent)
178  {
179  return frame;
180  }
181  else
182  {
183  return paddedFrame;
184  }
185  }
186 
187  private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
188  {
189  if (HeadSprite == null) { return; }
190  Vector2 targetAreaSize = componentRect.Size.ToVector2();
191  float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y);
192  DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize);
193  }
194 
195  public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
196  {
197  GUIFrame frame = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, 40), parent.RectTransform) { IsFixedSize = false }, "ListBoxElement")
198  {
199  UserData = userData
200  };
201 
202  Color? textColor = null;
203  if (Job != null) { textColor = Job.Prefab.UIColor; }
204 
205  GUITextBlock textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(40, 0) }, text, textColor: textColor, font: GUIStyle.SmallFont);
206  new GUICustomComponent(new RectTransform(new Point(frame.Rect.Height, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft) { IsFixedSize = false },
207  onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
208  return frame;
209  }
210 
211  partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
212  {
213  if (TeamID == CharacterTeamType.FriendlyNPC) { return; }
214  if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
215 
216  // if we increased by more than 1 in one increase, then display special color (for talents)
217  bool specialIncrease = Math.Abs(newLevel - prevLevel) >= 1.0f;
218 
219  if ((int)newLevel > (int)prevLevel)
220  {
222  int increase = Math.Max((int)newLevel - (int)prevLevel, 1);
223 
225  "+[value] "+ TextManager.Get("SkillName." + skillIdentifier).Value,
226  specialIncrease ? GUIStyle.Orange : GUIStyle.Green,
227  playSound: Character == Character.Controlled, skillIdentifier, increase);
228  }
229  }
230 
231  partial void OnExperienceChanged(int prevAmount, int newAmount)
232  {
233  if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
234 
235  GameSession.TabMenuInstance?.OnExperienceChanged(Character);
236 
237  if (newAmount > prevAmount)
238  {
239  int increase = newAmount - prevAmount;
241  "+[value] " + TextManager.Get("experienceshort").Value,
242  GUIStyle.Blue, playSound: Character == Character.Controlled, "exp".ToIdentifier(), increase);
243  }
244  }
245 
246  private void GetDisguisedSprites(IdCard idCard)
247  {
248  if (idCard.Item.Tags == string.Empty) return;
249 
250  if (idCard.StoredOwnerAppearance.JobPrefab == null || idCard.StoredOwnerAppearance.Portrait == null)
251  {
252  var readTags = idCard.Item.Tags.Split(',')
253  .Where(s => s.Contains(':'))
254  .Select(s => s.Split(':'))
255  .Select(s => (s[0].ToIdentifier(),s[1]))
256  .ToImmutableDictionary();
257 
258  if (readTags.None()) { return; }
259 
260  if (idCard.StoredOwnerAppearance.JobPrefab == null)
261  {
262  idCard.StoredOwnerAppearance.ExtractJobPrefab(readTags);
263  }
264 
265  if (idCard.StoredOwnerAppearance.Portrait == null)
266  {
267  idCard.StoredOwnerAppearance.ExtractAppearance(this, idCard);
268  }
269  }
270 
271  if (idCard.StoredOwnerAppearance.JobPrefab != null)
272  {
273  disguisedJobIcon = idCard.StoredOwnerAppearance.JobPrefab.Icon;
274  disguisedJobColor = idCard.StoredOwnerAppearance.JobPrefab.UIColor;
275  }
276 
277  disguisedPortrait = idCard.StoredOwnerAppearance.Portrait;
278  disguisedSheetIndex = idCard.StoredOwnerAppearance.SheetIndex;
279  disguisedAttachmentSprites = idCard.StoredOwnerAppearance.Attachments;
280 
281  disguisedHairColor = idCard.StoredOwnerAppearance.HairColor;
282  disguisedFacialHairColor = idCard.StoredOwnerAppearance.FacialHairColor;
283  disguisedSkinColor = idCard.StoredOwnerAppearance.SkinColor;
284  }
285 
286  partial void LoadAttachmentSprites()
287  {
288  if (attachmentSprites == null)
289  {
290  attachmentSprites = new List<WearableSprite>();
291  }
292  if (!IsAttachmentsLoaded)
293  {
295  }
296  Head.FaceAttachment?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.FaceAttachment)));
297  Head.BeardElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Beard)));
298  Head.MoustacheElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Moustache)));
299  Head.HairElement?.GetChildElements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Hair)));
300  }
301 
302  // Doesn't work if the head's source rect does not start at 0,0.
303  public static Point CalculateOffset(Sprite sprite, Point offset) => sprite.SourceRect.Size * offset;
304 
305  public void CalculateHeadPosition(Sprite sprite)
306  {
307  if (sprite == null) { return; }
308  if (Head.SheetIndex == null) { return; }
309  Point location = CalculateOffset(sprite, Head.SheetIndex.ToPoint());
310  sprite.SourceRect = new Rectangle(location, sprite.SourceRect.Size);
311  }
312 
313  public void DrawBackground(SpriteBatch spriteBatch)
314  {
315  if (infoAreaPortraitBG == null) { return; }
316  infoAreaPortraitBG.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea.Location.ToVector2(), Color.White, Vector2.Zero, 0.0f,
317  scale: new Vector2(
318  HUDLayoutSettings.BottomRightInfoArea.Width / (float)infoAreaPortraitBG.SourceRect.Width,
319  HUDLayoutSettings.BottomRightInfoArea.Height / (float)infoAreaPortraitBG.SourceRect.Height));
320  }
321 
322  public void DrawForeground(SpriteBatch spriteBatch)
323  {
324  if (Character is null || !(GameMain.GameSession?.Campaign is MultiPlayerCampaign)) { return; }
325  const int million = 1000000;
326  int xfraction = (int)(HUDLayoutSettings.BottomRightInfoArea.Width * 0.2f);
327  int yoffset = GUI.IntScale(6);
328 
329  int walletAmount = Character.Wallet.Balance;
330 
331  LocalizedString str = walletAmount >= million ? TextManager.Get("crewwallet.balance.toomuchtoshow") : TextManager.FormatCurrency(walletAmount);
332  Vector2 size = GUIStyle.Font.MeasureString(str);
333  int barHeight = GUI.IntScale(18);
334 
335  Rectangle barRect = new Rectangle((int)(HUDLayoutSettings.BottomRightInfoArea.X + xfraction / 2.5f), HUDLayoutSettings.BottomRightInfoArea.Bottom - barHeight - yoffset, HUDLayoutSettings.BottomRightInfoArea.Width - xfraction, barHeight);
336  float textScale = Math.Max(0.1f, Math.Min(barRect.Width / size.X, barRect.Height / size.Y)) - 0.01f;
337 
338  GUIStyle.WalletPortraitBG.Draw(spriteBatch, barRect, Color.White);
339 
340  int iconSize = GUI.IntScale(28);
341  int iconXOffset = iconSize / 2;
342  Rectangle iconRect = new Rectangle(barRect.Right - iconXOffset, barRect.Top - iconSize / 4, iconSize, iconSize);
343  GUIStyle.CrewWalletIconSmall.Draw(spriteBatch, iconRect, Color.White);
344  var (scaledTextSizeX, scaledTextSizeY) = size * textScale;
345  GUIStyle.Font.DrawString(spriteBatch, str, new Vector2(barRect.Right - iconXOffset - scaledTextSizeX - GUI.IntScale(4), barRect.Center.Y - scaledTextSizeY / 2), GUIStyle.TextColorNormal, 0f, Vector2.Zero, textScale, SpriteEffects.None, 0f);
346  }
347 
348  public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false, bool evaluateDisguise = false)
349  {
350  if (evaluateDisguise && IsDisguised) { return; }
351 
352  Vector2? sheetIndex;
353  Sprite portraitToDraw;
354  List<WearableSprite> attachmentsToDraw;
355 
356  Color hairColor;
357  Color facialHairColor;
358  Color skinColor;
359 
360  if (!IsDisguisedAsAnother || !evaluateDisguise)
361  {
362  sheetIndex = Head.SheetIndex;
363  portraitToDraw = Portrait;
364  attachmentsToDraw = AttachmentSprites;
365 
366  hairColor = Head.HairColor;
367  facialHairColor = Head.FacialHairColor;
368  skinColor = Head.SkinColor;
369  }
370  else
371  {
372  sheetIndex = disguisedSheetIndex;
373  portraitToDraw = disguisedPortrait;
374  attachmentsToDraw = disguisedAttachmentSprites;
375 
376  hairColor = disguisedHairColor;
377  facialHairColor = disguisedFacialHairColor;
378  skinColor = disguisedSkinColor;
379  }
380 
381  if (portraitToDraw != null)
382  {
383  var currEffect = spriteBatch.GetCurrentEffect();
384  // Scale down the head sprite 10%
385  float scale = targetWidth * 0.9f / Portrait.size.X;
386  if (sheetIndex.HasValue)
387  {
388  SetHeadEffect(spriteBatch);
389  portraitToDraw.SourceRect = new Rectangle(CalculateOffset(portraitToDraw, sheetIndex.Value.ToPoint()), portraitToDraw.SourceRect.Size);
390  }
391  portraitToDraw.Draw(spriteBatch, screenPos + offset, skinColor, portraitToDraw.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
392  if (attachmentsToDraw != null)
393  {
394  float depthStep = 0.000001f;
395  foreach (var attachment in attachmentsToDraw)
396  {
397  SetAttachmentEffect(spriteBatch, attachment);
398  DrawAttachmentSprite(spriteBatch, attachment, portraitToDraw, sheetIndex, screenPos + offset, scale, depthStep, GetAttachmentColor(attachment, hairColor, facialHairColor), flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
399  depthStep += depthStep;
400  }
401  }
402  spriteBatch.SwapEffect(currEffect);
403  }
404  }
405 
406  //TODO: I hate this so much :(
407  private SpriteBatch.EffectWithParams headEffectParameters;
408  private Dictionary<WearableType, SpriteBatch.EffectWithParams> attachmentEffectParameters
409  = new Dictionary<WearableType, SpriteBatch.EffectWithParams>();
410 
411  private void SetHeadEffect(SpriteBatch spriteBatch)
412  {
413  headEffectParameters.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
414  headEffectParameters.Params ??= new Dictionary<string, object>();
415  headEffectParameters.Params["xBaseTexture"] = HeadSprite.Texture;
416  headEffectParameters.Params["xTintMaskTexture"] = tintMask?.Texture ?? GUI.WhiteTexture;
417  headEffectParameters.Params["xCutoffTexture"] = GUI.WhiteTexture;
418  headEffectParameters.Params["baseToCutoffSizeRatio"] = 1.0f;
419  headEffectParameters.Params["highlightThreshold"] = tintHighlightThreshold;
420  headEffectParameters.Params["highlightMultiplier"] = tintHighlightMultiplier;
421  spriteBatch.SwapEffect(headEffectParameters);
422  }
423 
424  private void SetAttachmentEffect(SpriteBatch spriteBatch, WearableSprite attachment)
425  {
426  if (!attachmentEffectParameters.ContainsKey(attachment.Type))
427  {
428  attachmentEffectParameters.Add(attachment.Type, new SpriteBatch.EffectWithParams(GameMain.GameScreen.ThresholdTintEffect, new Dictionary<string, object>()));
429  }
430  var parameters = attachmentEffectParameters[attachment.Type].Params;
431  parameters["xBaseTexture"] = attachment.Sprite.Texture;
432  parameters["xTintMaskTexture"] = GUI.WhiteTexture;
433  parameters["xCutoffTexture"] = GUI.WhiteTexture;
434  parameters["baseToCutoffSizeRatio"] = 1.0f;
435  parameters["highlightThreshold"] = tintHighlightThreshold;
436  parameters["highlightMultiplier"] = tintHighlightMultiplier;
437  spriteBatch.SwapEffect(attachmentEffectParameters[attachment.Type]);
438  }
439 
440  private Color GetAttachmentColor(WearableSprite attachment, Color hairColor, Color facialHairColor)
441  {
442  switch (attachment.Type)
443  {
444  case WearableType.Hair:
445  return hairColor;
446  case WearableType.Beard:
447  case WearableType.Moustache:
448  return facialHairColor;
449  default:
450  return Color.White;
451  }
452  }
453 
454  public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
455  {
456  var headSprite = HeadSprite;
457  if (headSprite != null)
458  {
459  var currEffect = spriteBatch.GetCurrentEffect();
460  float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
461  headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size);
462  SetHeadEffect(spriteBatch);
463  headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor);
464  if (AttachmentSprites != null)
465  {
466  float depthStep = 0.000001f;
467  foreach (var attachment in AttachmentSprites)
468  {
469  SetAttachmentEffect(spriteBatch, attachment);
470  DrawAttachmentSprite(spriteBatch, attachment, headSprite, Head.SheetIndex, screenPos, scale, depthStep, GetAttachmentColor(attachment, Head.HairColor, Head.FacialHairColor));
471  depthStep += depthStep;
472  }
473  }
474  spriteBatch.SwapEffect(currEffect);
475  }
476  }
477 
478  public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise = false)
479  {
480  if (evaluateDisguise && IsDisguised) return;
481  var icon = !IsDisguisedAsAnother || !evaluateDisguise ? Job?.Prefab?.Icon : disguisedJobIcon;
482  if (icon == null) { return; }
483  Color iconColor = !IsDisguisedAsAnother || !evaluateDisguise ? Job.Prefab.UIColor : disguisedJobColor;
484 
485  icon.Draw(spriteBatch, area.Center.ToVector2(), iconColor, scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height));
486  }
487 
488  private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2? sheetIndex, Vector2 drawPos, float scale, float depthStep, Color? color = null, SpriteEffects spriteEffects = SpriteEffects.None)
489  {
490  if (attachment.InheritSourceRect)
491  {
492  if (attachment.SheetIndex.HasValue)
493  {
494  attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, attachment.SheetIndex.Value), head.SourceRect.Size);
495  }
496  else if (sheetIndex.HasValue)
497  {
498  attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, sheetIndex.Value.ToPoint()), head.SourceRect.Size);
499  }
500  else
501  {
502  attachment.Sprite.SourceRect = head.SourceRect;
503  }
504  }
505  Vector2 origin;
506  if (attachment.InheritOrigin)
507  {
508  origin = head.Origin;
509  attachment.Sprite.Origin = origin;
510  }
511  else
512  {
513  origin = attachment.Sprite.Origin;
514  }
515  float depth = attachment.Sprite.Depth;
516  if (attachment.InheritLimbDepth)
517  {
518  depth = head.Depth - depthStep;
519  }
520  attachment.Sprite.Draw(spriteBatch, drawPos, color ?? Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
521  }
522 
523  public static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound = true)
524  {
525  ushort infoID = inc.ReadUInt16();
526  string newName = inc.ReadString();
527  string originalName = inc.ReadString();
528  bool renamingEnabled = inc.ReadBoolean();
529  int tagCount = inc.ReadByte();
530  HashSet<Identifier> tagSet = new HashSet<Identifier>();
531  for (int i = 0; i < tagCount; i++)
532  {
533  tagSet.Add(inc.ReadIdentifier());
534  }
535  int hairIndex = inc.ReadByte();
536  int beardIndex = inc.ReadByte();
537  int moustacheIndex = inc.ReadByte();
538  int faceAttachmentIndex = inc.ReadByte();
539  Color skinColor = inc.ReadColorR8G8B8();
540  Color hairColor = inc.ReadColorR8G8B8();
541  Color facialHairColor = inc.ReadColorR8G8B8();
542 
543 
544  Identifier npcId = inc.ReadIdentifier();
545 
546  Identifier factionId = inc.ReadIdentifier();
547  float minReputationToHire = 0.0f;
548  if (!factionId.IsEmpty)
549  {
550  minReputationToHire = inc.ReadSingle();
551  }
552 
553  uint jobIdentifier = inc.ReadUInt32();
554  int variant = inc.ReadByte();
555  JobPrefab jobPrefab = null;
556  Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>();
557  if (jobIdentifier > 0)
558  {
559  jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier);
560  if (jobPrefab == null && requireJobPrefabFound)
561  {
562  throw new Exception($"Error while reading {nameof(CharacterInfo)} received from the server: could not find a job prefab with the identifier \"{jobIdentifier}\".");
563  }
564  byte skillCount = inc.ReadByte();
565  for (int i = 0; i < skillCount; i++)
566  {
567  Identifier skillIdentifier = inc.ReadIdentifier();
568  float skillLevel = inc.ReadSingle();
569  skillLevels.Add(skillIdentifier, skillLevel);
570  }
571  }
572 
573  CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, variant, npcIdentifier: npcId)
574  {
575  ID = infoID,
576  MinReputationToHire = (factionId, minReputationToHire),
577  RenamingEnabled = renamingEnabled
578  };
579  ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
580  ch.Head.SkinColor = skinColor;
581  ch.Head.HairColor = hairColor;
582  ch.Head.FacialHairColor = facialHairColor;
583  ch.SetPersonalityTrait();
584  ch.Job?.OverrideSkills(skillLevels);
585 
586  ch.ExperiencePoints = inc.ReadInt32();
587  ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
588  ch.PermanentlyDead = inc.ReadBoolean();
589  return ch;
590  }
591 
592  public void CreateIcon(RectTransform rectT)
593  {
595  new GUICustomComponent(rectT,
596  onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
597  }
598 
599  public class AppearanceCustomizationMenu : IDisposable
600  {
601  public readonly CharacterInfo CharacterInfo;
603  public bool HasIcon = true;
604 
607  public Action<AppearanceCustomizationMenu> OnHeadSwitch = null;
608 
609  private readonly GUIComponent parentComponent;
610  private readonly List<Sprite> characterSprites = new List<Sprite>();
612 
613  public AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon = true)
614  {
615  CharacterInfo = info;
616  parentComponent = parent;
617  HasIcon = hasIcon;
619  }
620 
621  public void RecreateFrameContents()
622  {
623  var info = CharacterInfo;
624 
625  HeadSelectionList = null;
626  parentComponent.ClearChildren();
627  ClearSprites();
628 
629  float contentWidth = HasIcon ? 0.75f : 1.0f;
630  var listBox = new GUIListBox(
631  new RectTransform(new Vector2(contentWidth, 1.0f), parentComponent.RectTransform,
632  Anchor.CenterLeft))
633  { CanBeFocused = false, CanTakeKeyBoardFocus = false };
634  var content = listBox.Content;
635 
636  info.LoadHeadAttachments();
637  if (HasIcon)
638  {
639  info.CreateIcon(
640  new RectTransform(new Vector2(0.25f, 1.0f), parentComponent.RectTransform, Anchor.CenterRight)
641  { RelativeOffset = new Vector2(-0.01f, 0.0f) });
642  }
643 
644  RectTransform createItemRectTransform(Identifier labelTag, float width = 0.6f)
645  {
646  var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.166f), content.RectTransform));
647 
648  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
649  TextManager.Get(labelTag), font: GUIStyle.SubHeadingFont);
650 
651  var bottomItem = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), layoutGroup.RectTransform),
652  style: null);
653 
654  return new RectTransform(new Vector2(width, 1.0f), bottomItem.RectTransform, Anchor.Center);
655  }
656 
657  RectTransform menuCategoryRT = createItemRectTransform(info.Prefab.MenuCategoryVar, 1.0f);
658 
659  GUILayoutGroup menuCategoryContainer =
660  new GUILayoutGroup(menuCategoryRT, isHorizontal: true)
661  {
662  Stretch = true,
663  RelativeSpacing = 0.05f
664  };
665 
666  void createMenuCategoryButton(Identifier tag)
667  {
668  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), menuCategoryContainer.RectTransform),
669  TextManager.Get(tag), style: "ListBoxElement")
670  {
671  UserData = tag,
672  OnClicked = OpenHeadSelection,
673  Selected = info.Head.Preset.TagSet.Contains(tag)
674  };
675  }
676 
677  foreach (var tag in info.Prefab.VarTags[info.Prefab.MenuCategoryVar].OrderBy(t => t.Value).Reverse())
678  {
679  createMenuCategoryButton(tag);
680  }
681 
682  List<GUIScrollBar> attachmentSliders = new List<GUIScrollBar>();
683  void createAttachmentSlider(int initialValue, WearableType wearableType)
684  {
685  int attachmentCount = info.CountValidAttachmentsOfType(wearableType);
686  if (attachmentCount > 0)
687  {
688  var labelTag = wearableType == WearableType.FaceAttachment
689  ? "FaceAttachment.Accessories".ToIdentifier()
690  : $"FaceAttachment.{wearableType}".ToIdentifier();
691  var sliderItemRT = createItemRectTransform(labelTag);
692  var slider =
693  new GUIScrollBar(sliderItemRT, style: "GUISlider")
694  {
695  Range = new Vector2(0, attachmentCount),
696  StepValue = 1,
697  OnMoved = (bar, scroll) => SwitchAttachment(bar, wearableType),
698  OnReleased = OnSliderReleased,
699  BarSize = 1.0f / (float)(attachmentCount + 1)
700  };
701  slider.BarScrollValue = initialValue;
702  attachmentSliders.Add(slider);
703  }
704  }
705 
706  createAttachmentSlider(info.Head.HairIndex, WearableType.Hair);
707  createAttachmentSlider(info.Head.BeardIndex, WearableType.Beard);
708  createAttachmentSlider(info.Head.MoustacheIndex, WearableType.Moustache);
709  createAttachmentSlider(info.Head.FaceAttachmentIndex, WearableType.FaceAttachment);
710 
711  void createColorSelector(Identifier labelTag, IEnumerable<(Color Color, float Commonness)> options, Func<Color> getter,
712  Action<Color> setter)
713  {
714  var selectorItemRT = createItemRectTransform(labelTag, 0.4f);
715  var dropdown =
716  new GUIDropDown(selectorItemRT)
717  { AllowNonText = true };
718 
719  var listBoxSize = dropdown.ListBox.RectTransform.RelativeSize;
720  dropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.75f, listBoxSize.Y);
721  var dropdownButton = dropdown.GetChild<GUIButton>();
722  var buttonFrame =
723  new GUIFrame(
724  new RectTransform(Vector2.One * 0.7f, dropdownButton.RectTransform, Anchor.CenterLeft)
725  { RelativeOffset = new Vector2(0.05f, 0.0f) }, style: null);
726  Color? previewingColor = null;
727  dropdown.OnSelected = (component, color) =>
728  {
729  previewingColor = null;
730  setter((Color)color);
731  buttonFrame.Color = getter();
732  buttonFrame.HoverColor = getter();
733  return true;
734  };
735  buttonFrame.Color = getter();
736  buttonFrame.HoverColor = getter();
737 
738  dropdown.ListBox.UseGridLayout = true;
739  foreach (var option in options)
740  {
741  var optionElement =
742  new GUIFrame(
743  new RectTransform(new Vector2(0.25f, 1.0f / 3.0f),
744  dropdown.ListBox.Content.RectTransform),
745  style: "ListBoxElement")
746  {
747  UserData = option.Color,
748  CanBeFocused = true
749  };
750  var colorElement =
751  new GUIFrame(
752  new RectTransform(Vector2.One * 0.75f, optionElement.RectTransform, Anchor.Center,
753  scaleBasis: ScaleBasis.Smallest),
754  style: null)
755  {
756  Color = option.Color,
757  HoverColor = option.Color,
758  OutlineColor = Color.Lerp(Color.Black, option.Color, 0.5f),
759  CanBeFocused = false
760  };
761  }
762 
763  var childToSelect = dropdown.ListBox.Content.FindChild(c => (Color)c.UserData == getter());
764  dropdown.Select(dropdown.ListBox.Content.GetChildIndex(childToSelect));
765 
766  //The following exists to track mouseover to preview colors before selecting them
767  new GUICustomComponent(new RectTransform(Vector2.One, buttonFrame.RectTransform),
768  onUpdate: (deltaTime, component) =>
769  {
770  if (GUI.MouseOn is GUIFrame { Parent: { } p } hoveredFrame && dropdown.ListBox.Content.IsParentOf(hoveredFrame))
771  {
772  previewingColor ??= getter();
773  Color color = (Color)(dropdown.ListBox.Content.FindChild(c =>
774  c == hoveredFrame || c.IsParentOf(hoveredFrame))?.UserData ?? dropdown.SelectedData ?? getter());
775  setter(color);
776  buttonFrame.Color = getter();
777  buttonFrame.HoverColor = getter();
778  }
779  else if (previewingColor.HasValue)
780  {
781  setter(previewingColor.Value);
782  buttonFrame.Color = getter();
783  buttonFrame.HoverColor = getter();
784  previewingColor = null;
785  }
786  }, onDraw: null)
787  {
788  CanBeFocused = false,
789  Visible = true
790  };
791  }
792 
793  if (info.CountValidAttachmentsOfType(WearableType.Hair) > 0)
794  {
795  createColorSelector($"Customization.{nameof(info.Head.HairColor)}".ToIdentifier(), info.HairColors,
796  () => info.Head.HairColor, (color) => info.Head.HairColor = color);
797  }
798 
799  if (info.CountValidAttachmentsOfType(WearableType.Moustache) > 0 ||
800  info.CountValidAttachmentsOfType(WearableType.Beard) > 0)
801  {
802  createColorSelector($"Customization.{nameof(info.Head.FacialHairColor)}".ToIdentifier(), info.FacialHairColors,
803  () => info.Head.FacialHairColor, (color) => info.Head.FacialHairColor = color);
804  }
805 
806  createColorSelector($"Customization.{nameof(info.Head.SkinColor)}".ToIdentifier(), info.SkinColors, () => info.Head.SkinColor,
807  (color) => info.Head.SkinColor = color);
808 #if DEBUG
809  new GUIButton(new RectTransform(Vector2.One * 0.12f,
810  parentComponent.RectTransform,
811  anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
812  { RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "SaveButton", color: Color.Magenta)
813  {
814  ToolTip = "DEBUG ONLY: copy the character info XML to clipboard",
815  OnClicked = (button, o) =>
816  {
817  XElement element = info.Save(null);
818  Clipboard.SetText(element.ToString());
819  return false;
820  }
821  };
822 #endif
823  RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f,
824  parentComponent.RectTransform,
825  anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
826  { RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "RandomizeButton")
827  {
828  OnClicked = (button, o) =>
829  {
830  var headPreset = info.Prefab.Heads.GetRandom(Rand.RandSync.Unsynced);
831  info.Head = new HeadInfo(info, headPreset);
832  info.SetAttachments(Rand.RandSync.Unsynced);
833  info.SetColors(Rand.RandSync.Unsynced);
834 
836  info.RefreshHead();
837  OnHeadSwitch?.Invoke(this);
838  attachmentSliders.ForEach(s => OnSliderMoved?.Invoke(s, s.BarScroll));
839 
840  return false;
841  }
842  };
843  listBox.ForceLayoutRecalculation();
844  foreach (var childLayoutGroup in listBox.Content.GetAllChildren<GUILayoutGroup>())
845  {
846  childLayoutGroup.Recalculate();
847  }
848  }
849 
850  private bool OpenHeadSelection(GUIButton button, object userData)
851  {
852  Identifier selectedCategory = (Identifier)userData;
853 
854  var info = CharacterInfo;
855 
856  if (info.HeadSprite == null)
857  {
858  DebugConsole.ThrowError($"Head Selection: the head sprite is null! Failed to open the head selection.");
859  return false;
860  }
861 
862  float characterHeightWidthRatio = info.HeadSprite.size.Y / info.HeadSprite.size.X;
863  HeadSelectionList ??= new GUIListBox(
864  new RectTransform(
865  new Point(parentComponent.Rect.Width,
866  (int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)), GUI.Canvas)
867  {
868  AbsoluteOffset = new Point(parentComponent.Rect.Right - parentComponent.Rect.Width,
869  button.Rect.Bottom)
870  });
871  HeadSelectionList.Visible = true;
872  HeadSelectionList.Content.ClearChildren();
873  ClearSprites();
874 
875  parentComponent.RectTransform.SizeChanged += () =>
876  {
877  if (parentComponent == null || HeadSelectionList?.RectTransform == null || button == null)
878  {
879  return;
880  }
881 
882  HeadSelectionList.RectTransform.Resize(new Point(parentComponent.Rect.Width,
883  (int)(parentComponent.Rect.Width * characterHeightWidthRatio * 0.6f)));
884  HeadSelectionList.RectTransform.AbsoluteOffset =
885  new Point(parentComponent.Rect.Right - parentComponent.Rect.Width, button.Rect.Bottom);
886  };
887 
888  new GUIFrame(
889  new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.ContentBackground.RectTransform, Anchor.Center),
890  style: "OuterGlow", color: Color.Black)
891  {
892  UserData = "outerglow",
893  CanBeFocused = false
894  };
895 
896  GUILayoutGroup row = null;
897  int itemsInRow = 0;
898 
899  ContentXElement headElement = info.Ragdoll.MainElement?.Elements().FirstOrDefault(e =>
900  e.GetAttributeString("type", "").Equals("head", StringComparison.OrdinalIgnoreCase));
901  if (headElement == null)
902  {
903  DebugConsole.ThrowError($"Head Selection: the head element is null in {info.ragdoll.FileName}! Failed to open the head selection.");
904  return false;
905  }
906  ContentXElement headSpriteElement = headElement.GetChildElement("sprite");
907  ContentPath spritePathWithTags = headSpriteElement.GetAttributeContentPath("texture");
908 
909  var characterConfigElement = info.CharacterConfigElement;
910 
911  var heads = info.Prefab.Heads;
912  if (heads != null)
913  {
914  row = null;
915  itemsInRow = 0;
916  foreach (var head in heads.Where(h => h.TagSet.Contains(selectedCategory)))
917  {
918  string spritePath = info.Prefab.ReplaceVars(spritePathWithTags.Value, head);
919 
920  if (!File.Exists(spritePath)) { continue; }
921 
922  Sprite headSprite = new Sprite(headSpriteElement, "", spritePath);
923  headSprite.SourceRect =
924  new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.SheetIndex.ToPoint()),
925  headSprite.SourceRect.Size);
926  characterSprites.Add(headSprite);
927 
928  if (itemsInRow >= 4 || row == null)
929  {
930  row = new GUILayoutGroup(
931  new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform),
932  true)
933  {
934  UserData = head.MenuCategory,
935  Visible = true
936  };
937  itemsInRow = 0;
938  }
939 
940  var btn = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), row.RectTransform),
941  style: "ListBoxElementSquare")
942  {
943  OutlineColor = Color.White * 0.5f,
944  PressedColor = Color.White * 0.5f,
945  UserData = head,
946  OnClicked = SwitchHead,
947  Selected = info.Head.Preset == head,
948  Visible = true
949  };
950 
951  new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), headSprite, scaleToFit: true);
952  itemsInRow++;
953  }
954  }
955 
956  return false;
957  }
958 
959  private bool SwitchHead(GUIButton button, object obj)
960  {
961  var info = CharacterInfo;
962  var headPreset = obj as HeadPreset;
963  if (info.Head.Preset != headPreset)
964  {
965  info.Head = new HeadInfo(info, headPreset, info.Head.HairIndex, info.Head.BeardIndex, info.Head.MoustacheIndex, info.Head.FaceAttachmentIndex)
966  {
967  SkinColor = info.Head.SkinColor,
968  HairColor = info.Head.HairColor,
969  FacialHairColor = info.Head.FacialHairColor
970  };
971  info.ReloadHeadAttachments();
972  }
973 
974  RecreateFrameContents();
975  OnHeadSwitch?.Invoke(this);
976  return true;
977  }
978 
979  private bool SwitchAttachment(GUIScrollBar scrollBar, WearableType type)
980  {
981  var info = CharacterInfo;
982  int index = (int)Math.Round(scrollBar.BarScrollValue);
983  switch (type)
984  {
985  case WearableType.Beard:
986  info.Head.BeardIndex = index;
987  break;
988  case WearableType.FaceAttachment:
989  info.Head.FaceAttachmentIndex = index;
990  break;
991  case WearableType.Hair:
992  info.Head.HairIndex = index;
993  break;
994  case WearableType.Moustache:
995  info.Head.MoustacheIndex = index;
996  break;
997  default:
998  DebugConsole.ThrowError($"Wearable type not implemented: {type}");
999  return false;
1000  }
1001 
1002  info.RefreshHead();
1003  OnSliderMoved?.Invoke(scrollBar, scrollBar.BarScroll);
1004  return true;
1005  }
1006 
1007  public void Update()
1008  {
1009  if (HeadSelectionList != null && PlayerInput.PrimaryMouseButtonDown() &&
1010  !GUI.IsMouseOn(HeadSelectionList))
1011  {
1012  HeadSelectionList.Visible = false;
1013  }
1014  }
1015 
1016  public void AddToGUIUpdateList()
1017  {
1018  HeadSelectionList?.AddToGUIUpdateList();
1019  }
1020 
1021  private void ClearSprites()
1022  {
1023  foreach (Sprite sprite in characterSprites) { sprite.Remove(); }
1024  characterSprites.Clear();
1025  }
1026 
1027  public void Dispose()
1028  {
1029  ClearSprites();
1030  if (HeadSelectionList != null)
1031  {
1032  HeadSelectionList.RectTransform.Parent = null;
1033  HeadSelectionList = null;
1034  }
1035  }
1036  }
1037  }
1038 }
readonly LocalizedString CauseOfDeathDescription
readonly AfflictionPrefab Affliction
Definition: CauseOfDeath.cs:13
readonly CauseOfDeathType Type
Definition: CauseOfDeath.cs:12
float GetSkillLevel(string skillIdentifier)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
void AddMessage(string rawText, Color color, bool playSound, Identifier identifier=default, int? value=null, float lifetime=3.0f)
AppearanceCustomizationMenu(CharacterInfo info, GUIComponent parent, bool hasIcon=true)
Stores information about the Character that is needed between rounds in the menu etc....
static CharacterInfo ClientRead(Identifier speciesName, IReadMessage inc, bool requireJobPrefabFound=true)
CharacterInfo(Identifier speciesName, string name="", string originalName="", Either< Job, JobPrefab > jobOrJobPrefab=null, int variant=0, Rand.RandSync randSync=Rand.RandSync.Unsynced, Identifier npcIdentifier=default)
void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon=null)
ushort ID
Unique ID given to character infos in MP. Non-persistent. Used by clients to identify which infos are...
GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip=false, bool evaluateDisguise=false)
void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area, bool evaluateDisguise=false)
static Point CalculateOffset(Sprite sprite, Point offset)
float GetAttributeFloat(string key, float def)
IEnumerable< ContentXElement > GetChildElements(string name)
ContentPath? GetAttributeContentPath(string key)
ContentXElement? GetChildElement(string name)
override bool Enabled
Definition: GUIButton.cs:27
virtual void ClearChildren()
virtual Rectangle Rect
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
override void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
Definition: GUIListBox.cs:814
GUIFrame ContentBackground
A frame drawn behind the content of the listbox
Definition: GUIListBox.cs:28
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
override void ClearChildren()
Definition: GUIListBox.cs:1243
delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll)
static GameSession?? GameSession
Definition: GameMain.cs:88
static GameScreen GameScreen
Definition: GameMain.cs:52
IEnumerable< Skill > GetSkills()
Definition: Job.cs:84
LocalizedString Name
Definition: Job.cs:14
JobPrefab Prefab
Definition: Job.cs:18
static readonly PrefabCollection< JobPrefab > Prefabs
readonly LocalizedString DisplayName
bool IsParentOf(RectTransform rectT, bool recursive=true)
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
Vector2 RelativeSize
Relative to the parent rect.
void Resize(Point absoluteSize, bool resizeChildren=true)
RectTransform?? Parent
LocalizedString DisplayName
Definition: Skill.cs:28
readonly Identifier Identifier
Definition: Skill.cs:7
float Level
Definition: Skill.cs:19
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
int ReadRangedInteger(int min, int max)
Microsoft.Xna.Framework.Color ReadColorR8G8B8()