6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 using Microsoft.Xna.Framework.Input;
10 using System.Collections.Generic;
11 using System.Collections.Immutable;
12 using System.Diagnostics.CodeAnalysis;
13 using System.Globalization;
21 public static bool ShowItems =
true, ShowWires =
true;
23 private readonly List<PosInfo> positionBuffer =
new List<PosInfo>();
25 private readonly List<ItemComponent> activeHUDs =
new List<ItemComponent>();
27 private readonly List<SerializableEntityEditor> activeEditors =
new List<SerializableEntityEditor>();
33 get {
return iconStyle; }
44 partial
void AssignCampaignInteractionTypeProjSpecific(
CampaignMode.
InteractionType interactionType, IEnumerable<Client> targetClients)
52 IconStyle = GUIStyle.GetComponentStyle($
"CampaignInteractionIcon.{interactionType}");
56 public IEnumerable<ItemComponent>
ActiveHUDs => activeHUDs;
61 private float editingHUDRefreshTimer;
69 private bool fakeBroken;
72 get {
return fakeBroken; }
75 if (value != fakeBroken)
83 private Sprite activeSprite;
86 get {
return activeSprite; }
91 get {
return base.Rect; }
94 cachedVisibleExtents =
null;
108 if (itemInUseWarning ==
null)
111 textColor: GUIStyle.Orange, color: Color.Black,
112 textAlignment: Alignment.Center, style:
"OuterGlow");
114 return itemInUseWarning;
138 public Color
GetSpriteColor(Color? defaultColor =
null,
bool withHighlight =
false)
141 if (
Prefab.UseContainedSpriteColor && ownInventory !=
null)
145 color = item.ContainerColor;
153 color = GUIStyle.Orange * Math.Max(
GetSpriteColor().A / (
float)
byte.MaxValue, 0.1f);
157 color = Color.Lerp(color,
HighlightColor.Value, (MathF.Sin((
float)Timing.TotalTime * 3.0f) + 1.0f) / 2.0f);
178 if (
Prefab.UseContainedInventoryIconColor && ownInventory !=
null)
182 color = item.ContainerColor;
189 partial
void SetActiveSpriteProjSpecific()
191 activeSprite =
Prefab.Sprite;
192 activeContainedSprite =
null;
193 Holdable holdable = GetComponent<Holdable>();
194 if (holdable !=
null && holdable.
Attached)
200 activeContainedSprite = containedSprite;
201 activeSprite = containedSprite.
Sprite;
212 if (containedSprite.MatchesContainer(
Container))
214 activeContainedSprite = containedSprite;
215 activeSprite = containedSprite.
Sprite;
227 if (displayCondition <= minCondition || displayCondition <=
Prefab.
BrokenSprites[i].MaxConditionPercentage)
237 Prefab.Sprite?.EnsureLazyLoaded();
238 Prefab.InventoryIcon?.EnsureLazyLoaded();
243 foreach (var decorativeSprite
in Prefab.DecorativeSprites)
245 decorativeSprite.Sprite.EnsureLazyLoaded();
252 partial
void InitProjSpecific()
257 private Rectangle? cachedVisibleExtents;
261 cachedVisibleExtents =
null;
267 if (container !=
null)
284 if (cachedVisibleExtents.HasValue)
286 extents = cachedVisibleExtents.Value;
293 Vector2 min =
new Vector2(-boundingBox.Width / 2 - padding, -boundingBox.Height / 2 - padding);
298 min.X = Math.Min(min.X, -drawable.
DrawSize.X / 2);
299 min.Y = Math.Min(min.Y, -drawable.
DrawSize.Y / 2);
300 max.X = Math.Max(max.X, drawable.
DrawSize.X / 2);
301 max.Y = Math.Max(max.Y, drawable.
DrawSize.Y / 2);
305 float scale = decorativeSprite.
GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) *
Scale;
311 cachedVisibleExtents = extents =
new Rectangle(min.ToPoint(), max.ToPoint());
316 if (worldPosition.X + extents.X > worldView.Right || worldPosition.X + extents.Width < worldView.X) {
return false; }
317 if (worldPosition.Y + extents.Height < worldView.Y - worldView.Height || worldPosition.Y + extents.Y > worldView.Y) {
return false; }
322 public override void Draw(SpriteBatch spriteBatch,
bool editing,
bool back =
true)
324 Draw(spriteBatch, editing, back, overrideColor:
null);
327 public void Draw(SpriteBatch spriteBatch,
bool editing,
bool back =
true, Color? overrideColor =
null)
335 if (!ShowWires) {
return; }
343 bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() ==
null;
344 if (renderTransparent) { color *= 0.15f; }
349 foreach (var ic
in components)
352 if (interactionType == InteractionVisibility.MissingRequirement)
354 color = Color.Orange;
356 else if (interactionType == InteractionVisibility.Visible)
358 color = Color.LightGreen;
365 float fadeInBrokenSpriteAlpha = 0.0f;
369 drawOffset.Y = -drawOffset.Y;
373 for (
int i = 0; i <
Prefab.BrokenSprites.Length; i++)
375 if (
Prefab.BrokenSprites[i].FadeIn)
377 float min = i > 0 ?
Prefab.BrokenSprites[i - i].MaxConditionPercentage : 0.0f;
378 float max =
Prefab.BrokenSprites[i].MaxConditionPercentage;
379 fadeInBrokenSpriteAlpha = 1.0f - ((displayCondition - min) / (max - min));
380 if (fadeInBrokenSpriteAlpha > 0.0f && fadeInBrokenSpriteAlpha <= 1.0f)
382 fadeInBrokenSprite =
Prefab.BrokenSprites[i];
386 if (displayCondition <=
Prefab.BrokenSprites[i].MaxConditionPercentage)
388 activeSprite =
Prefab.BrokenSprites[i].Sprite;
389 drawOffset =
Prefab.BrokenSprites[i].Offset.ToVector2() *
Scale;
397 if (activeSprite !=
null)
402 if (fadeInBrokenSprite !=
null && fadeInBrokenSprite.
Sprite != activeSprite)
414 Vector2 size =
new Vector2(
rect.Width,
rect.Height);
417 textureScale: Vector2.One *
Scale,
420 if (fadeInBrokenSprite !=
null)
422 float d = Math.Min(depth + (fadeInBrokenSprite.
Sprite.
Depth - activeSprite.
Depth - 0.000001f), 0.999f);
424 textureScale: Vector2.One *
Scale,
432 Vector2 origin = GetSpriteOrigin(activeSprite);
436 if (fadeInBrokenSprite !=
null)
438 float d = Math.Min(depth + (fadeInBrokenSprite.
Sprite.
Depth - activeSprite.
Depth - 0.000001f), 0.999f);
453 var holdable = GetComponent<Holdable>();
454 if (holdable !=
null && holdable.Picker?.AnimController !=
null)
457 if (GetComponent<Wearable>() is { IsActive:
true }) {
return; }
458 if (!back) {
return; }
459 if (holdable.Picker.Inventory?.GetItemInLimbSlot(
InvSlotType.RightHand) ==
this)
461 depth = GetHeldItemDepth(
LimbType.RightHand, holdable, depth);
463 else if (holdable.Picker.Inventory?.GetItemInLimbSlot(
InvSlotType.LeftHand) ==
this)
465 depth = GetHeldItemDepth(
LimbType.LeftHand, holdable, depth);
468 static float GetHeldItemDepth(
LimbType limb,
Holdable holdable,
float depth)
472 float limbDepthOffset = 0.000001f;
476 if (holdLimb?.ActiveSprite !=
null)
481 + limbDepthOffset * 2 * (limb ==
LimbType.RightHand ? 1 : -1);
488 Math.Max(wearableSprite.
Sprite.
Depth + limbDepthOffset, depth) :
489 Math.Min(wearableSprite.
Sprite.
Depth - limbDepthOffset, depth);
498 Math.Min(head.Sprite.Depth + depthOffset - limbDepthOffset, depth) :
499 Math.Max(head.Sprite.Depth + depthOffset + limbDepthOffset, depth);
505 Vector2 origin = GetSpriteOrigin(activeSprite);
506 body.
Draw(spriteBatch, activeSprite, color, depth,
Scale, origin: origin);
507 if (fadeInBrokenSprite !=
null)
509 float d = Math.Min(depth + (fadeInBrokenSprite.
Sprite.
Depth - activeSprite.
Depth - 0.000001f), 0.999f);
510 body.
Draw(spriteBatch, fadeInBrokenSprite.
Sprite, color * fadeInBrokenSpriteAlpha, d,
Scale);
517 var upgradeSprites = GetUpgradeSprites(upgrade);
519 foreach (var decorativeSprite
in upgradeSprites)
521 if (!spriteAnimState[decorativeSprite].IsActive) {
continue; }
522 float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
523 Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, -
RotationRad) *
Scale;
525 if (flippedY &&
Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
526 decorativeSprite.Sprite.Draw(spriteBatch,
new Vector2(
DrawPosition.X + offset.X, -(
DrawPosition.Y + offset.Y)), color,
527 rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) *
Scale, activeSprite.
effects,
528 depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.
Depth));
532 activeSprite.
effects = oldEffects;
533 if (fadeInBrokenSprite !=
null && fadeInBrokenSprite.
Sprite != activeSprite)
541 for (
int i = drawableComponents.Count - 1; i >= 0; i--)
543 drawableComponents[i].Draw(spriteBatch, editing, depth, overrideColor);
551 triggerBody.UpdateDrawPosition();
552 triggerBody.DebugDraw(spriteBatch, Color.White);
558 if (GetComponent<ElectricalDischarger>() is { } discharger)
560 discharger.DrawElectricity(spriteBatch);
572 Vector2 drawSize =
new Vector2(MathF.Ceiling(
rect.Width + Math.Abs(drawPos.X - (
int)drawPos.X)), MathF.Ceiling(
rect.Height + Math.Abs(drawPos.Y - (
int)drawPos.Y)));
573 drawPos =
new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
574 GUI.DrawRectangle(sb: spriteBatch,
575 center: drawPos + drawSize * 0.5f,
587 Vector2 rectWorldPos =
new Vector2(transformedTrigger.X, transformedTrigger.Y);
589 rectWorldPos.Y = -rectWorldPos.Y;
591 GUI.DrawRectangle(spriteBatch,
593 new Vector2(transformedTrigger.Width, transformedTrigger.Height),
601 if (!
ShowLinks || GUI.DisableHUD) {
return; }
606 Color lineColor = GUIStyle.Red * 0.5f;
613 GUI.DrawLine(spriteBatch, from, to, lineColor * 0.25f, width: 3);
614 GUI.DrawLine(spriteBatch, from, to, lineColor, width: 1);
618 Vector2 GetSpriteOrigin(
Sprite sprite)
620 Vector2 origin = sprite.
Origin;
623 origin.X = sprite.
SourceRect.Width - origin.X;
627 origin.Y = sprite.
SourceRect.Height - origin.Y;
640 public void DrawDecorativeSprites(SpriteBatch spriteBatch, Vector2 drawPos,
bool flipX,
bool flipY,
float rotation,
float depth, Color? overrideColor =
null)
642 foreach (var decorativeSprite
in Prefab.DecorativeSprites)
645 if (!spriteAnimState[decorativeSprite].IsActive) {
continue; }
647 Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier,
648 flipX ^ flipY ? -rotation : rotation) *
Scale;
652 decorativeSprite.Sprite.DrawTiled(spriteBatch,
654 new Vector2(
rect.Width,
rect.Height), color: decorativeSpriteColor,
655 textureScale: Vector2.One *
Scale,
656 depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.
Depth), 0.999f));
660 float spriteRotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor);
662 Vector2 origin = decorativeSprite.Sprite.Origin;
664 if (flipX &&
Prefab.CanSpriteFlipX)
666 offset.X = -offset.X;
667 origin.X = -origin.X + decorativeSprite.Sprite.size.X;
670 if (flipY &&
Prefab.CanSpriteFlipY)
672 offset.Y = -offset.Y;
673 origin.Y = -origin.Y + decorativeSprite.Sprite.size.Y;
680 offset =
new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
682 decorativeSprite.Sprite.Draw(spriteBatch,
new Vector2(drawPos.X + offset.X, -(drawPos.Y + offset.Y)), decorativeSpriteColor, origin,
683 -rotation + spriteRotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) *
Scale, spriteEffects,
684 depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.
Depth));
689 partial
void OnCollisionProjSpecific(
float impact)
693 !
string.IsNullOrEmpty(
Prefab.ImpactSoundTag) &&
701 partial
void Splash()
705 float massFactor = MathHelper.Clamp(
body.
Mass, 0.5f, 20.0f);
706 for (
int i = 0; i < MathHelper.Clamp(Math.Abs(
body.
LinearVelocity.Y), 1.0f, 10.0f); i++)
708 var splash = GameMain.ParticleManager.CreateParticle(
"watersplash",
714 splash.Size *= MathHelper.Clamp(Math.Abs(
body.
LinearVelocity.Y) * 0.1f * massFactor, 1.0f, 4.0f);
717 GameMain.ParticleManager.CreateParticle(
"bubbles",
726 if (n >= 0 && n < currentHull.
WaveVel.Length)
738 if (!updateableComponents.Contains(ic))
740 updateableComponents.Add(ic);
748 if (activeContainedSprite !=
null)
754 var spriteState = spriteAnimState[decorativeSprite];
755 spriteState.IsActive =
false;
762 foreach (var containedSprite
in Prefab.ContainedSprites)
768 var spriteState = spriteAnimState[decorativeSprite];
769 spriteState.IsActive =
false;
776 if (
Prefab.DecorativeSpriteGroups.Count > 0)
783 var upgradeSprites = GetUpgradeSprites(upgrade);
784 foreach (var decorativeSprite
in upgradeSprites)
786 var spriteState = spriteAnimState[decorativeSprite];
787 spriteState.IsActive =
true;
788 foreach (var conditional
in decorativeSprite.IsActiveConditionals)
792 spriteState.IsActive =
false;
805 editingHUDRefreshTimer = 1.0f;
807 if (editingHUDRefreshTimer <= 0.0f)
809 activeEditors.ForEach(e => e?.RefreshValues());
810 editingHUDRefreshTimer = 1.0f;
815 if (GetComponent<ElectricalDischarger>() is { } discharger)
823 discharger.IsActive =
false;
839 if (!lClick && !rClick) {
return; }
843 if (otherEntity !=
null)
848 if (otherEntity.linkedTo !=
null && otherEntity.linkedTo.Contains(
this))
850 otherEntity.linkedTo.Remove(
this);
856 if (otherEntity.Linkable && otherEntity.linkedTo !=
null)
858 otherEntity.linkedTo.Add(
this);
866 Vector2 rectSize =
rect.Size.ToVector2();
870 Vector2 transformedMousePos = MathUtils.RotatePointAroundTarget(position, bodyPos,
RotationRad);
873 Math.Abs(transformedMousePos.X - bodyPos.X) < rectSize.X / 2.0f &&
874 Math.Abs(transformedMousePos.Y - bodyPos.Y) < rectSize.Y / 2.0f;
879 activeEditors.Clear();
881 int heightScaled = (int)(20 * GUI.Scale);
885 CanTakeKeyBoardFocus =
false,
886 Spacing = (int)(25 * GUI.Scale)
890 activeEditors.Add(itemEditor);
891 itemEditor.
Children.First().Color = Color.Black * 0.7f;
899 itemsText.Text = TextManager.AddPunctuation(
':', itemsText.Text, allowedItems);
900 itemEditor.AddCustomContent(linkText, 1);
901 itemEditor.AddCustomContent(itemsText, 2);
902 linkText.TextColor = GUIStyle.Orange;
903 itemsText.TextColor = GUIStyle.Orange;
907 var itemContainer = GetComponent<ItemContainer>();
908 if (itemContainer !=
null)
910 var tagBox = itemEditor.Fields[
"Tags".ToIdentifier()].First() as
GUITextBox;
911 var tagsField = tagBox?.
Parent;
914 var containerTagButtonLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.25f, 1), containerTagLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterRight);
915 new GUIButton(
new RectTransform(
new Vector2(0.95f, 1), containerTagButtonLayout.RectTransform), text: TextManager.Get(
"containertaguibutton"), style:
"GUIButtonSmall")
918 TextBlock = { AutoScaleHorizontal =
true }
920 var containerTagText =
new GUITextBlock(
new RectTransform(
new Vector2(0.8f, 1), containerTagLayout.RectTransform), TextManager.Get(
"containertaguibuttondescription"), font: GUIStyle.SmallFont)
922 TextColor = GUIStyle.Orange
924 var limitedString = ToolBox.LimitString(containerTagText.Text, containerTagText.Font, itemEditor.Rect.Width - containerTagButtonLayout.Rect.Width);
925 if (limitedString != containerTagText.Text)
927 containerTagText.
ToolTip = containerTagText.Text;
928 containerTagText.Text = limitedString;
930 itemEditor.AddCustomContent(containerTagLayout, 3);
936 RelativeSpacing = 0.02f,
941 itemEditor.Fields.TryGetValue(
"Rotation".ToIdentifier(), out var rotationFieldComponents)
942 ? rotationFieldComponents.OfType<
GUINumberInput>().FirstOrDefault()
945 var mirrorX =
new GUIButton(
new RectTransform(
new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get(
"MirrorEntityX"), style:
"GUIButtonSmall")
947 ToolTip = TextManager.Get(
"MirrorEntityXToolTip"),
948 Enabled =
Prefab.CanFlipX,
949 OnClicked = (button, data) =>
953 me.
FlipX(relativeToSub:
false);
962 var mirrorY =
new GUIButton(
new RectTransform(
new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get(
"MirrorEntityY"), style:
"GUIButtonSmall")
964 ToolTip = TextManager.Get(
"MirrorEntityYToolTip"),
965 Enabled =
Prefab.CanFlipY,
966 OnClicked = (button, data) =>
970 me.
FlipY(relativeToSub:
false);
981 var reloadTextureButton =
new GUIButton(
new RectTransform(
new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get(
"ReloadSprite"), style:
"GUIButtonSmall");
982 reloadTextureButton.OnClicked += (button, data) =>
989 new GUIButton(
new RectTransform(
new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get(
"ResetToPrefab"), style:
"GUIButtonSmall")
991 OnClicked = (button, data) =>
1003 buttonContainer.RectTransform.MinSize =
new Point(0, buttonContainer.RectTransform.Children.Max(c => c.MinSize.Y));
1004 buttonContainer.RectTransform.IsFixedSize =
true;
1005 itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount);
1012 Font = GUIStyle.SmallFont,
1014 ToolTip = TextManager.Get(
"sp.structure.removeiflinkedoutpostdoorinuse.description"),
1015 OnSelected = (tickBox) =>
1021 itemEditor.AddCustomContent(tickBox, 1);
1024 if (!
Layer.IsNullOrEmpty())
1026 var layerText =
new GUITextBlock(
new RectTransform(
new Point(listBox.
Content.
Rect.Width, heightScaled)) { MinSize = new Point(0, heightScaled) }, TextManager.AddPunctuation(
':', TextManager.Get(
"editor.layer"),
Layer));
1027 itemEditor.AddCustomContent(layerText, 1);
1047 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style:
"HorizontalLine");
1049 var componentEditor =
new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame, showName: !inGame, titleFont: GUIStyle.SubHeadingFont) { UserData = ic };
1050 componentEditor.Children.First().Color = Color.Black * 0.7f;
1051 activeEditors.Add(componentEditor);
1056 componentEditor.Recalculate();
1060 List<RelatedItem> requiredItems =
new List<RelatedItem>();
1065 requiredItems.Add(relatedItem);
1070 foreach (
RelatedItem relatedItem
in requiredItems)
1074 relatedItem.
Type.ToString() +
" required", font: GUIStyle.SmallFont)
1076 Padding =
new Vector4(10.0f, 0.0f, 10.0f, 0.0f)
1079 componentEditor.AddCustomContent(textBlock, 1);
1083 Font = GUIStyle.SmallFont,
1087 textBlock.RectTransform.Resize(
new Point(textBlock.Rect.Width, namesBox.
RectTransform.
MinSize.Y));
1104 componentEditor.Recalculate();
1113 private ImmutableArray<DecorativeSprite> GetUpgradeSprites(Upgrade upgrade)
1115 var upgradeSprites = upgrade.Prefab.DecorativeSprites;
1117 if (
Prefab.UpgradeOverrideSprites.ContainsKey(upgrade.Prefab.Identifier))
1119 upgradeSprites =
Prefab.UpgradeOverrideSprites[upgrade.Prefab.Identifier];
1122 return upgradeSprites;
1125 public override bool AddUpgrade(Upgrade upgrade,
bool createNetworkEvent =
false)
1127 if (upgrade.Prefab.IsWallUpgrade) {
return false; }
1128 bool result = base.AddUpgrade(upgrade, createNetworkEvent);
1129 if (result && !upgrade.Disposed)
1131 var upgradeSprites = GetUpgradeSprites(upgrade);
1133 if (upgradeSprites.Any())
1148 var msgBox =
new GUIMessageBox(
string.Empty,
string.Empty,
new[] { TextManager.Get(
"Ok") },
new Vector2(0.35f, 0.6f),
new Point(400, 400));
1149 msgBox.
Buttons[0].OnClicked = msgBox.Close;
1153 RelativeOffset = new Vector2(0.015f)
1154 }, style:
"GUIButtonInfo")
1156 ToolTip = TextManager.Get(
"containertagui.tutorial"),
1157 IgnoreLayoutGroups =
true
1164 const float NameSize = 0.4f;
1165 const float ItemSize = 0.5f;
1166 const float CountSize = 0.1f;
1168 var headerLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.075f), list.Content.RectTransform), isHorizontal:
true);
1174 ContainerTagPrefab.Prefabs
1175 .ToImmutableDictionary(
1177 ct => ct.GetItemsAndSpawnProbabilities());
1181 var tagCategories = ContainerTagPrefab.Prefabs
1182 .GroupBy(ct => ct.Category)
1183 .ToImmutableDictionary(
1185 g => g.Select(ct => ct.Identifier).ToImmutableArray());
1187 foreach (var (category, categoryTags) in tagCategories)
1189 var categoryButton =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.075f), list.Content.RectTransform), style:
"GUIButtonSmallFreeScale");
1190 categoryButton.Color *= 0.66f;
1191 var categoryLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, categoryButton.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft) { Stretch =
true };
1192 var categoryText =
new GUITextBlock(
new RectTransform(Vector2.One, categoryLayout.RectTransform), TextManager.Get($
"tagcategory.{category}"), font: GUIStyle.SubHeadingFont);
1193 var arrowImage =
new GUIImage(
new RectTransform(
new Vector2(1f, 0.5f), categoryLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
"GUIButtonVerticalArrowFreeScale");
1194 var arrowPadding =
new GUIFrame(
new RectTransform(
new Vector2(0.025f, 1f), categoryLayout.RectTransform), style:
null);
1196 bool hasHiddenCategories =
false;
1197 foreach (var categoryTag
in categoryTags.OrderBy(t => t.Value))
1199 var found = itemsByTag.FirstOrNull(kvp => kvp.Key.Identifier == categoryTag);
1202 DebugConsole.ThrowError($
"Failed to find tag with identifier {categoryTag} in itemsByTag");
1206 var (tag, prefabsAndProbabilities) = found.Value;
1208 bool isCorrectSubType = tag.IsRecommendedForSub(
Submarine);
1212 UserData = category,
1216 if (!isCorrectSubType)
1218 hasHiddenCategories =
true;
1222 var enabledCheckBox =
new GUITickBox(
new RectTransform(Vector2.One, checkBoxLayout.RectTransform,
Anchor.Center), tag.Name, font: GUIStyle.SmallFont)
1224 Selected = tags.Contains(tag.Identifier),
1225 ToolTip = tag.Description
1228 var tickBoxText = enabledCheckBox.TextBlock;
1229 tickBoxText.Text = ToolBox.LimitString(tickBoxText.Text, tickBoxText.Font, tickBoxText.Rect.Width);
1233 var itemLayoutButtonLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.2f, 1), itemLayout.RectTransform), isHorizontal:
true, childAnchor:
Anchor.Center);
1234 var itemLayoutButton =
new GUIButton(
new RectTransform(
new Vector2(0.8f), itemLayoutButtonLayout.RectTransform), text:
"...", style:
"GUICharacterInfoButton")
1237 ToolTip = TextManager.Get(
"containertagui.viewprobabilities")
1240 itemLayoutButtonLayout.Recalculate();
1243 float localScroll = 0f;
1244 int lastSkippedItems = 0;
1245 int skippedItems = 0;
1246 var itemLayoutDraw =
new GUICustomComponent(
new RectTransform(
new Vector2(1f, 0.9f), itemLayoutScissor.Content.RectTransform,
Anchor.CenterLeft), onDraw: (spriteBatch, component) =>
1248 component.ToolTip = string.Empty;
1250 const float padding = 8f;
1252 float size = component.Rect.Height;
1253 int start = (int)Math.Floor(scroll);
1254 int amountToDraw = (int)Math.Ceiling(component.Rect.Width / size) + 1;
1255 bool shouldIncrementOnSkip = true;
1256 float toDrawWidth = prefabsAndProbabilities.Length * (size + padding);
1259 if (toDrawWidth < component.Rect.Width)
1261 shouldIncrementOnSkip = false;
1262 amountToDraw = prefabsAndProbabilities.Length;
1265 for (
int i = start; i < start + amountToDraw; i++)
1267 var (ip, probability, _) = prefabsAndProbabilities[i % prefabsAndProbabilities.Length];
1268 var sprite = ip.InventoryIcon ?? ip.Sprite;
1273 if (shouldIncrementOnSkip)
1281 if (ShouldHideItemPrefab(ip, probability))
1283 if (shouldIncrementOnSkip)
1291 float partialScroll = localScroll * (size + padding);
1292 var drawRect =
new RectangleF(itemLayoutScissor.Rect.X + offset - partialScroll, component.Rect.Y, size, size);
1297 component.ToolTip = ip.CreateTooltipText();
1301 slotSprite?.
Draw(spriteBatch, drawRect.Location, Color.White, origin: Vector2.Zero, rotate: 0f, scale: size / slotSprite.size.X *
Inventory.
SlotSpriteSmallScale);
1303 float iconScale = Math.Min(drawRect.Width / sprite.size.X, drawRect.Height / sprite.size.Y) * 0.9f;
1305 Color drawColor = ip.InventoryIconColor;
1307 sprite.Draw(spriteBatch, drawRect.Center, drawColor, origin: sprite.Origin, scale: iconScale);
1308 offset += size + padding;
1312 if (skippedItems < lastSkippedItems)
1314 scroll += lastSkippedItems - skippedItems;
1317 lastSkippedItems = skippedItems;
1319 }, onUpdate: (deltaTime, component) =>
1321 if (GUI.MouseOn != component && MathUtils.NearlyEqual(localScroll, 0, deltaTime * 2))
1327 float totalWidth = prefabsAndProbabilities.Length * (component.Rect.Height + 8f);
1328 if (totalWidth < component.Rect.Width) {
return; }
1329 scroll += deltaTime;
1330 localScroll = scroll % 1f;
1334 AlwaysOverrideCursor =
true
1337 var tooltip = TextManager.Get(tag.WarnIfLess ?
"ContainerTagUI.RecommendedAmount" :
"ContainerTagUI.SuggestedAmount");
1339 var countBlock =
new GUITextBlock(
new RectTransform(
new Vector2(CountSize, 1f), tagLayout.RectTransform),
string.Empty, textAlignment: Alignment.Center)
1343 UpdateCountBlock(countBlock, tag);
1345 enabledCheckBox.OnSelected += tickBox =>
1347 if (tickBox.Selected)
1356 if (tagTextBox is not
null)
1358 tagTextBox.Text =
string.Join(
',', tags.Where(t => !
Prefab.Tags.Contains(t)));
1360 UpdateCountBlock(countBlock, tag);
1364 itemLayoutButton.OnClicked = (button, _) =>
1366 CreateContainerTagItemListPopup(tag, button.Rect.Center, layout, prefabsAndProbabilities);
1370 void UpdateCountBlock(
GUITextBlock textBlock, ContainerTagPrefab containerTag)
1372 if (textBlock is
null) {
return; }
1374 var tagCount =
Submarine.
GetItems(alsoFromConnectedSubs:
true).Count(i => i.HasTag(containerTag.Identifier));
1375 textBlock.
Text = $
"{tagCount} ({containerTag.RecommendedAmount})";
1377 if (!isCorrectSubType || !containerTag.WarnIfLess || containerTag.RecommendedAmount <= 0) {
return; }
1379 if (tagCount < containerTag.RecommendedAmount)
1382 textBlock.
Text +=
"*";
1383 textBlock.
ToolTip =
RichString.
Rich($
"{tooltip}\n\n‖color:gui.red‖{TextManager.Get("ContainerTagUI.RecommendedAmountWarning
")}‖color:end‖");
1385 else if (tagCount >= containerTag.RecommendedAmount)
1394 categoryButton.OnClicked = (_, _) =>
1398 foreach (var child
in list.Content.Children)
1400 if (child.UserData is Identifier
id &&
id == category)
1402 child.Visible = !child.Visible;
1410 private static void CreateContainerTagItemListPopup(ContainerTagPrefab tag, Point location, GUIComponent popupParent, ImmutableArray<ContainerTagPrefab.ItemAndProbability> prefabAndProbabilities)
1412 const string TooltipUserData =
"tooltip";
1413 const string ProbabilityUserData =
"probability";
1415 if (popupParent.GetChildByUserData(TooltipUserData) is { } existingTooltip)
1417 popupParent.RemoveChild(existingTooltip);
1420 var tooltip =
new GUIFrame(
new RectTransform(
new Point(popupParent.Rect.Height), popupParent.RectTransform)
1422 AbsoluteOffset = location - popupParent.Rect.Location
1425 UserData = TooltipUserData,
1426 IgnoreLayoutGroups =
true
1429 if (tooltip.Rect.Bottom > GameMain.GraphicsHeight)
1431 int diffY = tooltip.Rect.Bottom - GameMain.GraphicsHeight;
1432 tooltip.RectTransform.AbsoluteOffset -=
new Point(0, diffY);
1435 if (tooltip.Rect.Right > GameMain.GraphicsWidth)
1437 int diffX = tooltip.Rect.Right - GameMain.GraphicsWidth;
1438 tooltip.RectTransform.AbsoluteOffset -=
new Point(diffX, 0);
1441 var tooltipLayout =
new GUILayoutGroup(
new RectTransform(ToolBox.PaddingSizeParentRelative(tooltip.RectTransform, 0.9f), tooltip.RectTransform,
Anchor.Center));
1443 var tooltipHeader =
new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.1f), tooltipLayout.RectTransform), tag.Name, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
1444 var tooltipList =
new GUIListBox(
new RectTransform(
new Vector2(1f, 0.7f), tooltipLayout.RectTransform));
1446 var tooltipHeaderLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.1f), tooltipList.Content.RectTransform), isHorizontal:
true);
1447 new GUIButton(
new RectTransform(
new Vector2(0.66f, 1f), tooltipHeaderLayout.RectTransform), TextManager.Get(
"tagheader.item"), style:
"GUIButtonSmallFreeScale") {
ForceUpperCase =
ForceUpperCase.Yes, CanBeFocused =
false };
1448 new GUIButton(
new RectTransform(
new Vector2(0.33f, 1f), tooltipHeaderLayout.RectTransform), TextManager.Get(
"tagheader.probability"), style:
"GUIButtonSmallFreeScale") {
ForceUpperCase =
ForceUpperCase.Yes, CanBeFocused =
false };
1450 foreach (var itemAndProbability
in prefabAndProbabilities.OrderByDescending(p => p.Probability))
1452 var (ip, probability, campaignOnlyProbability) = itemAndProbability;
1453 if (ShouldHideItemPrefab(ip, probability)) {
continue; }
1455 var itemLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.1f), tooltipList.Content.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
1457 UserData = itemAndProbability
1460 var itemNameLayout =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.66f, 1f), itemLayout.RectTransform), childAnchor:
Anchor.CenterLeft, isHorizontal:
true)
1465 var itemIcon =
new GUIImage(
new RectTransform(Vector2.One, itemNameLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), ip.InventoryIcon ?? ip.Sprite, scaleToFit:
true)
1467 Color = ip.InventoryIconColor
1470 var itemName =
new GUITextBlock(
new RectTransform(Vector2.One, itemNameLayout.RectTransform), ip.Name);
1471 itemName.Text = ToolBox.LimitString(ip.Name, itemName.Font, itemName.Rect.Width);
1473 var toolTipContainer =
new GUIFrame(
new RectTransform(Vector2.One, itemNameLayout.RectTransform), style:
null)
1475 IgnoreLayoutGroups =
true,
1476 ToolTip = ip.CreateTooltipText()
1479 var probabilityText =
new GUITextBlock(
new RectTransform(
new Vector2(0.33f, 1f), itemLayout.RectTransform), ProbabilityToPercentage(campaignOnlyProbability), textAlignment: Alignment.Right)
1481 UserData = ProbabilityUserData
1483 if (MathUtils.NearlyEqual(campaignOnlyProbability, 0f)) { probabilityText.TextColor = GUIStyle.Red; }
1486 var campaignCheckbox =
new GUITickBox(
new RectTransform(
new Vector2(1f, 0.1f), tooltipLayout.RectTransform), label: TextManager.Get(
"containertagui.campaignonly"))
1488 ToolTip = TextManager.Get(
"containertagui.campaignonlytooltip"),
1492 foreach (var child
in tooltipList.Content.Children)
1494 if (child.UserData is not ContainerTagPrefab.ItemAndProbability data) {
continue; }
1496 if (child.GetChildByUserData(ProbabilityUserData) is not GUITextBlock text) {
continue; }
1498 float probability = box.Selected
1499 ? data.CampaignProbability
1501 text.Text = ProbabilityToPercentage(probability);
1503 text.TextColor = MathUtils.NearlyEqual(probability, 0f)
1505 : GUIStyle.TextColorNormal;
1512 var tooltipClose =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.1f), tooltipLayout.RectTransform), TextManager.Get(
"Close"))
1514 OnClicked = (_, _) =>
1516 popupParent.RemoveChild(tooltip);
1521 static LocalizedString ProbabilityToPercentage(
float probability)
1522 => TextManager.GetWithVariable(
"percentageformat",
"[value]", MathF.Round((probability * 100f), 1).ToString(CultureInfo.InvariantCulture));
1526 private static bool ShouldHideItemPrefab(ItemPrefab ip,
float probability)
1527 => ip.HideInMenus && MathUtils.NearlyEqual(probability, 0f);
1532 private void SetHUDLayout(
bool ignoreLocking =
false)
1535 List<GUIComponent> elementsToMove =
new List<GUIComponent>();
1537 if (editingHUD !=
null && editingHUD.UserData ==
this &&
1538 ((HasInGameEditableProperties &&
Character.Controlled?.SelectedItem ==
this) || Screen.Selected == GameMain.SubEditorScreen))
1540 elementsToMove.Add(editingHUD);
1543 debugInitialHudPositions.Clear();
1549 if (ic.
GuiFrame.
Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.
GuiFrame.
Rect.Height >= GameMain.GraphicsHeight * 0.9f) {
continue; }
1555 List<Rectangle> disallowedAreas =
new List<Rectangle>();
1556 if (GameMain.GameSession?.CrewManager !=
null && Screen.Selected == GameMain.GameScreen)
1558 int disallowedPadding = (int)(50 * GUI.Scale);
1559 disallowedAreas.Add(GameMain.GameSession.CrewManager.GetActiveCrewArea());
1561 HUDLayoutSettings.ChatBoxArea.X - disallowedPadding, HUDLayoutSettings.ChatBoxArea.Y,
1562 HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height));
1565 if (Screen.Selected is SubEditorScreen editor)
1567 disallowedAreas.Add(editor.EntityMenu.Rect);
1568 disallowedAreas.Add(editor.TopPanel.Rect);
1569 disallowedAreas.Add(editor.ToggleEntityMenuButton.Rect);
1572 GUI.PreventElementOverlap(elementsToMove, disallowedAreas, clampArea: HUDLayoutSettings.ItemHUDArea);
1577 if (ic.
GuiFrame ==
null) {
continue; }
1580 if (linkUIToComponent ==
null) {
continue; }
1586 private readonly List<Rectangle> debugInitialHudPositions =
new List<Rectangle>();
1588 private readonly List<ItemComponent> prevActiveHUDs =
new List<ItemComponent>();
1589 private readonly List<ItemComponent> activeComponents =
new List<ItemComponent>();
1590 private readonly List<ItemComponent> maxPriorityHUDs =
new List<ItemComponent>();
1594 bool editingHUDCreated =
false;
1595 if ((HasInGameEditableProperties && (character.
SelectedItem ==
this || EditableWhenEquipped)) ||
1599 UpdateEditing(cam, deltaTime);
1600 editingHUDCreated = editingHUD !=
null && editingHUD != prevEditingHUD;
1603 if (editingHUD ==
null ||
1604 !(GUI.KeyboardDispatcher.Subscriber is
GUITextBox textBox) ||
1605 !editingHUD.IsParentOf(textBox))
1607 editingHUDRefreshTimer -= deltaTime;
1610 prevActiveHUDs.Clear();
1611 prevActiveHUDs.AddRange(activeHUDs);
1612 activeComponents.Clear();
1613 activeComponents.AddRange(components);
1619 if (!i.DisplaySideBySideWhenLinked) {
continue; }
1620 activeComponents.AddRange(i.components);
1625 maxPriorityHUDs.Clear();
1629 if (character.HasEquippedItem(
this))
1642 if (ic.
HudPriority > 0 && DrawHud(ic) && (maxPriorityHUDs.Count == 0 || ic.
HudPriority >= maxPriorityHUDs[0].HudPriority))
1644 if (maxPriorityHUDs.Count > 0 && ic.
HudPriority > maxPriorityHUDs[0].HudPriority) { maxPriorityHUDs.Clear(); }
1645 maxPriorityHUDs.Add(ic);
1649 if (maxPriorityHUDs.Count > 0)
1651 activeHUDs.AddRange(maxPriorityHUDs);
1664 activeHUDs.Sort((h1, h2) => {
return h2.HudLayer.CompareTo(h1.HudLayer); });
1667 if (!prevActiveHUDs.SequenceEqual(activeHUDs) || editingHUDCreated)
1675 ic.
UpdateHUD(character, deltaTime, cam);
1678 mergedHUDRect = mergedHUDRect ==
Rectangle.Empty ?
1686 if (itemInUseWarning !=
null) { itemInUseWarning.
Visible =
false; }
1689 if (otherCharacter != character &&
1692 ItemInUseWarning.Visible =
true;
1696 if (itemInUseWarning.
UserData != otherCharacter)
1698 itemInUseWarning.
Text = TextManager.GetWithVariable(
"ItemInUse",
"[character]", otherCharacter.
Name);
1699 itemInUseWarning.
UserData = otherCharacter;
1709 if (HasInGameEditableProperties && (character.
SelectedItem ==
this || EditableWhenEquipped))
1711 DrawEditing(spriteBatch, cam);
1718 ic.
DrawHUD(spriteBatch, character);
1727 if (i >= debugInitialHudPositions.Count) {
break; }
1728 if (activeHUDs[i].GuiFrame ==
null) {
continue; }
1731 GUI.DrawRectangle(spriteBatch, debugInitialHudPositions[i], Color.Orange);
1732 GUI.DrawRectangle(spriteBatch, ic.
GuiFrame.
Rect, Color.LightGreen);
1733 GUI.DrawLine(spriteBatch, debugInitialHudPositions[i].
Location.ToVector2(), ic.
GuiFrame.
Rect.Location.ToVector2(), Color.Orange);
1740 readonly List<ColoredText> texts =
new();
1744 if (texts.Any() && !recreateHudTexts) {
return texts; }
1747 string nameText = Name;
1748 if (
Prefab.Tags.Contains(
"identitycard") || Tags.Contains(
"despawncontainer"))
1750 string[] readTags = Tags.Split(
',');
1751 string idName =
null;
1752 foreach (
string tag
in readTags)
1754 string[] s = tag.Split(
':');
1763 nameText += $
" ({idName})";
1766 if (DroppedStack.Any())
1768 nameText += $
" x{DroppedStack.Count()}";
1771 texts.Add(
new ColoredText(nameText, GUIStyle.TextColorNormal, isCommand:
false, isError:
false));
1775 texts.Add(
new ColoredText(TextManager.GetWithVariable($
"CampaignInteraction.{CampaignInteractionType}",
"[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(
InputType.Use)).Value, Color.Cyan, isCommand:
false, isError:
false));
1781 var interactionVisibility = GetComponentInteractionVisibility(character, itemComponent);
1782 if (interactionVisibility == InteractionVisibility.None) {
continue; }
1783 if (itemComponent.
DisplayMsg.IsNullOrEmpty()) {
continue; }
1785 Color color = interactionVisibility == InteractionVisibility.MissingRequirement ? Color.Gray : Color.Cyan;
1791 texts.Add(
new ColoredText(TextManager.ParseInputTypes(TextManager.Get(
"itemmsgcontextualorders")).Value, Color.Cyan, isCommand:
false, isError:
false));
1795 texts.Add(
new ColoredText(TextManager.Get(
"itemmsg.morreoptionsavailable").Value, Color.LightGray * 0.7f, isCommand:
false, isError:
false));
1800 private enum InteractionVisibility
1818 private static InteractionVisibility GetComponentInteractionVisibility(Character character,
ItemComponent itemComponent)
1822 if (itemComponent is
ConnectionPanel connectionPanel && !connectionPanel.
CanRewire()) {
return InteractionVisibility.None; }
1824 InteractionVisibility interactionVisibility = InteractionVisibility.MissingRequirement;
1829 if (repairable.IsBelowRepairThreshold)
1831 interactionVisibility = InteractionVisibility.Visible;
1836 interactionVisibility = InteractionVisibility.Visible;
1840 return interactionVisibility;
1845 foreach (var component
in components)
1847 if (GetComponentInteractionVisibility(character, component) == InteractionVisibility.Visible)
1860 if (ic.
GuiFrame ==
null) {
continue; }
1872 SetHUDLayout(ignoreLocking);
1879 if (editingHUD !=
null && editingHUD.UserData ==
this) { editingHUD.AddToGUIUpdateList(); }
1885 if (editingHUD !=
null && editingHUD.UserData ==
this) { editingHUD.AddToGUIUpdateList(); }
1891 if (character !=
null && selectedItem !=
this && GetComponent<RemoteController>() ==
null)
1893 bool insideCircuitBox =
1894 selectedItem?.GetComponent<CircuitBox>() !=
null &&
1896 if (!insideCircuitBox &&
1897 selectedItem?.GetComponent<RemoteController>()?.TargetItem !=
this &&
1904 bool needsLayoutUpdate =
false;
1909 bool useAlternativeLayout = activeHUDs.Count > 1;
1916 if (itemInUseWarning !=
null && itemInUseWarning.
Visible)
1921 if (needsLayoutUpdate)
1939 serverSerializable.ClientEventRead(msg, sendingTime);
1943 throw new Exception($
"Failed to read component state - {components[componentIndex].GetType()} in item \"{Prefab.Identifier}\" is not IServerSerializable.");
1952 container.Inventory.ClientEventRead(msg);
1956 throw new Exception($
"Failed to read inventory state - {components[containerIndex].GetType()} in item \"{Prefab.Identifier}\" is not an ItemContainer.");
1963 SetCondition(newCondition, isNetworkEvent:
true, executeEffects: !loadingRound);
1965 case EventType.AssignCampaignInteraction:
1970 AssignCampaignInteractionType(interactionType);
1976 byte componentIndex = msg.
ReadByte();
1978 byte targetLimbID = msg.
ReadByte();
1980 Vector2? worldPosition =
null;
1987 ItemComponent targetComponent = componentIndex < components.Count ? components[componentIndex] :
null;
1991 Entity useTarget = FindEntityByID(useTargetID);
1993 if (targetComponent ==
null)
1995 ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, isNetworkEvent:
true, worldPosition: worldPosition);
1999 targetComponent.
ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, worldPosition: worldPosition);
2004 ReadPropertyChange(msg,
false);
2008 for (
int i = 0; i < length; i++)
2010 var statIdentifier = INetSerializableStruct.Read<TalentStatIdentifier>(msg);
2012 StatManager.ApplyStatDirect(statIdentifier, statValue);
2018 if (UpgradePrefab.Find(identifier) is { } upgradePrefab)
2020 Upgrade upgrade =
new Upgrade(
this, upgradePrefab, level);
2023 for (
int i = 0; i < targetCount; i++)
2025 byte propertyCount = msg.
ReadByte();
2026 for (
int j = 0; j < propertyCount; j++)
2029 upgrade.TargetComponents.ElementAt(i).Value[j].SetOriginalValue(value);
2033 AddUpgrade(upgrade,
false);
2040 List<Item> droppedStack =
new List<Item>();
2041 for (
int i = 0; i < itemCount; i++)
2044 if (FindEntityByID(
id) is not
Item droppedItem)
2046 DebugConsole.ThrowError($
"Error while reading {EventType.DroppedStack} message: could not find an item with the ID {id}.");
2050 droppedStack.Add(droppedItem);
2053 CreateDroppedStack(droppedStack, allowClientExecute:
true);
2057 RemoveFromDroppedStack(allowClientExecute:
true);
2062 if (isTargetedForThisClient)
2065 ExternalHighlight = highlight;
2069 HighlightColor = highlightColor;
2073 HighlightColor =
null;
2078 throw new Exception($
"Malformed incoming item event: unsupported event type {eventType}");
2084 Exception error(
string reason)
2086 string errorMsg = $
"Failed to write a network event for the item \"{Name}\" - {reason}";
2087 GameAnalyticsManager.AddErrorEventOnce($
"Item.ClientWrite:{Name}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
2088 return new Exception(errorMsg);
2091 if (extraData is
null) {
throw error(
"event data was null"); }
2092 if (!(extraData is
IEventData eventData)) {
throw error($
"event data was of the wrong type (\"{extraData.GetType().Name}\")"); }
2094 EventType eventType = eventData.EventType;
2100 var component = componentStateEventData.
Component;
2101 if (component is
null) {
throw error(
"component was null"); }
2102 if (component is not
IClientSerializable clientSerializable) {
throw error($
"component was not {nameof(IClientSerializable)}"); }
2103 int componentIndex = components.IndexOf(component);
2104 if (componentIndex < 0) {
throw error(
"component did not belong to item"); }
2106 clientSerializable.ClientEventWrite(msg, extraData);
2111 var container = inventoryStateEventData.
Component;
2112 if (container is
null) {
throw error(
"container was null"); }
2113 int containerIndex = components.IndexOf(container);
2114 if (containerIndex < 0) {
throw error(
"container did not belong to item"); }
2116 container.Inventory.ClientEventWrite(msg, inventoryStateEventData);
2119 case TreatmentEventData treatmentEventData:
2120 Character targetCharacter = treatmentEventData.TargetCharacter;
2123 msg.
WriteByte(treatmentEventData.LimbIndex);
2126 WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly:
true);
2127 editingHUDRefreshTimer = 1.0f;
2129 case CombineEventData combineEventData:
2130 Item combineTarget = combineEventData.CombineTarget;
2134 throw error($
"Unsupported event type {eventData.GetType().Name}");
2138 partial
void UpdateNetPosition(
float deltaTime)
2142 if (parentInventory !=
null || body ==
null || !body.Enabled || Removed || (GetComponent<Projectile>() is { IsStuckToTarget: true }))
2144 positionBuffer.Clear();
2150 if (positionBuffer.Count > 0)
2152 transformDirty =
true;
2155 body.CorrectPosition(positionBuffer, out Vector2 newPosition, out Vector2 newVelocity, out
float newRotation, out
float newAngularVelocity);
2156 body.LinearVelocity = newVelocity;
2157 body.AngularVelocity = newAngularVelocity;
2158 float distSqr = Vector2.DistanceSquared(newPosition, body.SimPosition);
2160 if (distSqr > 0.0001f ||
2161 Math.Abs(newRotation - body.Rotation) > 0.01f)
2163 body.TargetPosition = newPosition;
2164 body.TargetRotation = newRotation;
2165 body.MoveToTargetPosition(lerp:
true);
2166 if (distSqr > 10.0f * 10.0f)
2174 Vector2 displayPos = ConvertUnits.ToDisplayUnits(body.SimPosition);
2175 rect.X = (int)(displayPos.X - rect.Width / 2.0f);
2176 rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
2183 string errorMsg =
"Received a position update for an item with no physics body (" + Name +
")";
2185 DebugConsole.ThrowError(errorMsg);
2187 if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
2189 GameAnalyticsManager.AddErrorEventOnce(
"Item.ClientReadPosition:nophysicsbody", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
2194 var posInfo = body.ClientRead(msg, sendingTime, parentDebugName: Name);
2197 if (GetComponent<Projectile>() is { IsStuckToTarget:
true }) {
return; }
2199 if (posInfo !=
null)
2202 while (index < positionBuffer.Count && sendingTime > positionBuffer[index].Timestamp)
2207 positionBuffer.Insert(index, posInfo);
2242 => CreateClientEvent(ic,
null);
2248 #warning TODO: this should throw an exception
2249 if (!components.Contains(ic)) {
return; }
2252 if (!ic.
ValidateEventData(eventData)) {
throw new Exception($
"Component event creation failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false"); }
2261 string itemDesc =
"";
2262 if (descriptionChanged)
2269 DebugConsole.Log($
"Received entity spawn message for item \"{itemName}\" (identifier: {itemIdentifier}, id: {itemId})");
2272 string.IsNullOrEmpty(itemIdentifier) ?
2276 Vector2 pos = Vector2.Zero;
2278 float rotation = 0.0f;
2279 int itemContainerIndex = -1;
2280 int inventorySlotIndex = -1;
2282 if (inventoryId > 0)
2284 itemContainerIndex = msg.
ReadByte();
2285 inventorySlotIndex = msg.
ReadByte();
2305 string ownerName =
"", ownerTags =
"";
2306 int ownerBeardIndex = -1, ownerHairIndex = -1, ownerMoustacheIndex = -1, ownerFaceAttachmentIndex = -1;
2307 Color ownerHairColor = Color.White,
2308 ownerFacialHairColor = Color.White,
2309 ownerSkinColor = Color.White;
2310 Identifier ownerJobId = Identifier.Empty;
2311 Vector2 ownerSheetIndex = Vector2.Zero;
2312 int submarineSpecificId = 0;
2318 ownerBeardIndex = msg.
ReadByte() - 1;
2319 ownerHairIndex = msg.
ReadByte() - 1;
2320 ownerMoustacheIndex = msg.
ReadByte() - 1;
2321 ownerFaceAttachmentIndex = msg.
ReadByte() - 1;
2329 ownerSheetIndex = (x, y);
2336 HashSet<Identifier> addedTags = msg.
ReadString().Split(
',').ToIdentifiers().ToHashSet();
2337 HashSet<Identifier> removedTags = msg.
ReadString().Split(
',').ToIdentifiers().ToHashSet();
2338 if (itemPrefab !=
null)
2340 tags =
string.Join(
',',itemPrefab.
Tags.Where(t => !removedTags.Contains(t)).Concat(addedTags));
2345 string writtenName =
"";
2351 if (!spawn) {
return null; }
2355 if (itemPrefab ==
null)
2357 string errorMsg =
"Failed to spawn item, prefab not found (name: " + (itemName ??
"null") +
", identifier: " + (itemIdentifier ??
"null") +
")";
2358 errorMsg +=
"\n" +
string.Join(
", ", ContentPackageManager.EnabledPackages.All.Select(cp => cp.Name));
2359 GameAnalyticsManager.AddErrorEventOnce(
"Item.ReadSpawnData:PrefabNotFound" + (itemName ??
"null") + (itemIdentifier ??
"null"),
2360 GameAnalyticsManager.ErrorSeverity.Critical,
2362 DebugConsole.ThrowError(errorMsg);
2367 if (inventoryId > 0)
2369 var inventoryOwner = FindEntityByID(inventoryId);
2370 if (inventoryOwner is
Character character)
2374 else if (inventoryOwner is
Item parentItem)
2376 if (itemContainerIndex < 0 || itemContainerIndex >= parentItem.components.Count)
2379 $
"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{parentItem.Prefab.Identifier} ({parentItem.ID})\" (component index out of range). Index: {itemContainerIndex}, components: {parentItem.components.Count}.";
2380 GameAnalyticsManager.AddErrorEventOnce(
"Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ??
"null") + (itemIdentifier ??
"null"),
2381 GameAnalyticsManager.ErrorSeverity.Error,
2383 DebugConsole.ThrowError(errorMsg);
2386 else if (parentItem.components[itemContainerIndex] is
ItemContainer container)
2391 else if (inventoryOwner ==
null)
2393 DebugConsole.ThrowError($
"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of an entity with the ID {inventoryId} (entity not found)");
2397 DebugConsole.ThrowError($
"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{inventoryOwner} ({inventoryOwner.ID})\" (invalid entity, should be an item or a character)");
2404 item =
new Item(itemPrefab, pos, sub,
id: itemId)
2406 SpawnedInCurrentOutpost = spawnedInOutpost,
2407 AllowStealing = allowStealing,
2413 DebugConsole.ThrowError($
"Failed to spawn item {itemPrefab.Name}", e);
2417 if (item.body !=
null)
2419 item.body.BodyType = (BodyType)bodyType;
2426 foreach (
IdCard idCard
in item.GetComponents<
IdCard>())
2442 if (descriptionChanged) { item.Description = itemDesc; }
2443 if (tagsChanged) { item.Tags = tags; }
2444 var nameTag = item.GetComponent<
NameTag>();
2445 if (nameTag !=
null)
2452 item.CurrentHull =
Hull.
FindHull(pos + sub.Position,
null,
true);
2456 if (inventory !=
null)
2458 if (inventorySlotIndex >= 0 && inventorySlotIndex < 255 &&
2459 inventory.TryPutItem(item, inventorySlotIndex,
false,
false,
null,
false))
2463 inventory.TryPutItem(item,
null, item.AllowedSlots,
false);
2469 partial
void RemoveProjSpecific()
static bool BlocksInteraction(InteractionType interactionType)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
static readonly List< Character > CharacterList
static bool DebugDrawInteract
static Character? Controlled
readonly AnimController AnimController
readonly bool UseWhenAttached
readonly DecorativeSpriteBehaviorType DecorativeSpriteBehavior
DecorativeSpriteBehaviorType
static void UpdateSpriteStates(ImmutableDictionary< int, ImmutableArray< DecorativeSprite >> spriteGroups, Dictionary< DecorativeSprite, State > animStates, int entityID, float deltaTime, Func< PropertyConditional, bool > checkConditional)
float GetScale(float randomScaleModifier)
virtual Vector2 WorldPosition
virtual Vector2 DrawPosition
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual RichString ToolTip
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIComponent that can be used to render custom content on the UI
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
List< GUIButton > Buttons
static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
TextBoxEvent OnDeselected
OnEnterHandler OnEnterPressed
static SubEditorScreen SubEditorScreen
static int GraphicsHeight
static GameScreen GameScreen
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
static Sprite SlotSpriteSmall
Inventory(Entity owner, int capacity, int slotsPerRow=5)
static VisualSlot DraggingSlot
static readonly List< Item > DraggingItems
const float SlotSpriteSmallScale
const int MaxPossibleStackSize
void ResetCachedVisibleSize()
List< ColoredText > GetHUDTexts(Character character, bool recreateHudTexts=true)
override Quad2D GetTransformedQuad()
void UpdateSpriteStates(float deltaTime)
void RemoveTag(Identifier tag)
BallastFloraBranch Infector
override bool DrawBelowWater
override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
Adds a new upgrade to the item
float LastImpactSoundTime
void CreateContainerTagPicker([MaybeNull] GUITextBox tagTextBox)
override bool IsMouseOn(Vector2 position)
void DrawDecorativeSprites(SpriteBatch spriteBatch, Vector2 drawPos, bool flipX, bool flipY, float rotation, float depth, Color? overrideColor=null)
GUIComponentStyle IconStyle
Inventory ParentInventory
bool ConditionalMatches(PropertyConditional conditional)
IEnumerable< ItemComponent > ActiveHUDs
Color GetSpriteColor(Color? defaultColor=null, bool withHighlight=false)
override void AddToGUIUpdateList(int order=0)
void OnPlayerSkillsChanged()
override bool DrawOverWater
void ClientEventRead(IReadMessage msg, float sendingTime)
override Vector2? Position
const float ImpactSoundInterval
override bool SelectableInEditor
void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character)
override void FlipX(bool relativeToSub)
Flip the entity horizontally
Rectangle TransformTrigger(Rectangle trigger, bool world=false)
override void FlipY(bool relativeToSub)
Flip the entity vertically
Color GetInventoryIconColor()
override void CheckIsHighlighted()
static Item ReadSpawnData(IReadMessage msg, bool spawn=true)
void ForceHUDLayoutUpdate(bool ignoreLocking=false)
float ConditionPercentageRelativeToDefaultMaxCondition
Condition percentage disregarding MaxRepairConditionMultiplier (i.e. this can go above 100% if the it...
override void Draw(SpriteBatch spriteBatch, bool editing, bool back=true)
void UpdateHUD(Camera cam, Character character, float deltaTime)
override bool IsVisible(Rectangle worldView)
IEnumerable< Item > ContainedItems
void ClientReadPosition(IReadMessage msg, float sendingTime)
bool HasVisibleInteraction(Character character)
void CheckNeedsSoundUpdate(ItemComponent ic)
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
SpriteEffects SpriteEffects
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
override void UpdateEditing(Camera cam, float deltaTime)
void Draw(SpriteBatch spriteBatch, bool editing, bool back=true, Color? overrideColor=null)
GUIComponent CreateEditingHUD(bool inGame=false)
bool DisplaySideBySideWhenLinked
ImmutableArray< ContainedItemSprite > ContainedSprites
static ItemPrefab Find(string name, Identifier identifier)
override ImmutableHashSet< Identifier > Tags
ImmutableArray< BrokenItemSprite > BrokenSprites
Color OwnerFacialHairColor
int OwnerFaceAttachmentIndex
void ApplyTo(RectTransform target)
The base class for components holding the different functionalities of the item
virtual bool ValidateEventData(NetEntityEvent.IData data)
GUILayoutSettings AlternativeLayout
virtual void UpdateEditing(float deltaTime)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
virtual void CreateEditingHUD(SerializableEntityEditor editor)
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
ItemComponent GetLinkUIToComponent()
virtual void AddToGUIUpdateList(int order=0)
void UpdateHUD(Character character, float deltaTime, Camera cam)
virtual bool ShouldDrawHUD(Character character)
virtual void OnPlayerSkillsChanged()
bool LockGuiFramePosition
readonly List< RelatedItem > DisabledRequiredItems
virtual void DrawHUD(SpriteBatch spriteBatch, Character character)
LocalizedString DisplayMsg
bool?? UseAlternativeLayout
Dictionary< RelatedItem.RelationType, List< RelatedItem > > RequiredItems
GUILayoutSettings DefaultLayout
readonly List< WearableSprite > WearingItems
readonly? BallastFloraBehavior ParentBallastFlora
bool RemoveIfLinkedOutpostDoorInUse
IEnumerable< Identifier > AllowedLinks
virtual void FlipX(bool relativeToSub)
Flip the entity horizontally
static HashSet< MapEntity > SelectedList
bool IsIncludedInSelection
static GUIComponent editingHUD
virtual void FlipY(bool relativeToSub)
Flip the entity vertically
static void ColorFlipButton(GUIButton btn, bool flip)
readonly MapEntityPrefab Prefab
static void PositionEditingHUD()
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Vector2 GetCollapseEffectOffset()
static readonly HashSet< MapEntity > highlightedEntities
readonly List< MapEntity > linkedTo
readonly List< Upgrade > Upgrades
List of upgrades this item has
virtual bool IsMouseOn(Vector2 position)
override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData=null)
void DebugDraw(SpriteBatch spriteBatch, Color color, bool forceColor=false)
void Draw(DeformableSprite deformSprite, Camera cam, Vector2 scale, Color color, bool invert=false)
float GetDepthOffset()
Offset added to the default draw depth of the character's limbs. For example, climbing on ladders aff...
Limb GetLimb(LimbType limbType, bool excludeSevered=true)
Note that if there are multiple limbs of the same type, only the first (valid) limb is returned.
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
Vector2 RelativeOrigin
0 - 1
void EnsureLazyLoaded(bool isAsync=false)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
void DrawTiled(ISpriteBatch spriteBatch, Vector2 position, Vector2 targetSize, float rotation=0f, Vector2? origin=null, Color? color=null, Vector2? startOffset=null, Vector2? textureScale=null, float? depth=null, SpriteEffects? spriteEffects=null)
void ReloadXML()
Works only if there is a name attribute defined for the sprite. For items and structures,...
static bool IsLayerVisible(MapEntity entity)
bool IsSubcategoryHidden(string subcategory)
static bool IsWiringMode()
static bool TransparentWiringMode
List< Item > GetItems(bool alsoFromConnectedSubs)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
static List< Submarine > Loaded
override Vector2? Position
bool IsEditable(ISerializableEntity entity)
Vector2 DrawSize
The extents of the sprites or other graphics this component needs to draw. Used to determine which it...
Interface for entities that the clients can send events to the server
Microsoft.Xna.Framework.Color ReadColorR8G8B8A8()
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
Microsoft.Xna.Framework.Color ReadColorR8G8B8()
Identifier ReadIdentifier()
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)
void WriteUInt16(UInt16 val)
ActionType
ActionTypes define when a StatusEffect is executed.
readonly ItemComponent Component
readonly ItemContainer Component