using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using Barotrauma.Sounds; namespace Barotrauma.Items.Components { partial class Rope : ItemComponent, IDrawableComponent { private Sprite sprite, startSprite, endSprite; private RoundSound snapSound, reelSound; private SoundChannel reelSoundChannel; [Serialize(5, IsPropertySaveable.No)] public int SpriteWidth { get; set; } [Serialize("255,255,255,255", IsPropertySaveable.No)] public Color SpriteColor { get; set; } [Serialize(false, IsPropertySaveable.No)] public bool Tile { get; set; } [Serialize("0.5,0.5", IsPropertySaveable.No)] public Vector2 Origin { get; set; } [Serialize(true, IsPropertySaveable.No, description: "")] public bool BreakFromMiddle { get; set; } public Vector2 DrawSize { get { if (target == null || source == null) { return Vector2.Zero; } Vector2 sourcePos = GetSourcePos(); //need to double the size because this is essentially just the radius, we need the diameter // + some extra margin to be on the safe side return new Vector2( Math.Abs(target.DrawPosition.X - sourcePos.X), Math.Abs(target.DrawPosition.Y - sourcePos.Y)) * 2.2f; } } [Serialize("1.0, 1.0", IsPropertySaveable.No, description: "When reeling in, the pitch slides from X to Y, depending on the length of the rope.")] public Vector2 ReelSoundPitchSlide { get => _reelSoundPitchSlide; set { _reelSoundPitchSlide = new Vector2( Math.Max(value.X, SoundChannel.MinFrequencyMultiplier), Math.Min(value.Y, SoundChannel.MaxFrequencyMultiplier)); } } private Vector2 _reelSoundPitchSlide; partial void InitProjSpecific(ContentXElement element) { foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": sprite = new Sprite(subElement); break; case "startsprite": startSprite = new Sprite(subElement); break; case "endsprite": endSprite = new Sprite(subElement); break; case "snapsound": snapSound = RoundSound.Load(subElement); break; case "reelsound": reelSound = RoundSound.Load(subElement); break; } } } partial void UpdateProjSpecific() { if (isReelingIn && !Snapped) { PlaySound(reelSound, source.WorldPosition); } else { reelSoundChannel?.FadeOutAndDispose(); reelSoundChannel = null; } } public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null) { if (target == null || target.Removed) { return; } if (target.ParentInventory != null) { return; } if (source is Limb limb && limb.Removed) { return; } if (source is Entity e && e.Removed) { return; } Vector2 startPos = GetSourcePos(useDrawPosition: true); startPos.Y = -startPos.Y; if ((source as Item)?.GetComponent() is { } turret) { if (turret.BarrelSprite != null) { startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * turret.Item.Scale * BarrelLengthMultiplier; } startPos -= turret.GetRecoilOffset(); } Vector2 endPos = new Vector2(target.DrawPosition.X, target.DrawPosition.Y); Vector2 flippedPos = target.Sprite.size * target.Scale * (Origin - new Vector2(0.5f)); if (target.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; } endPos += Vector2.Transform(flippedPos, Matrix.CreateRotationZ(target.body.Rotation)); endPos.Y = -endPos.Y; if (Snapped) { float snapState = 1.0f - snapTimer / SnapAnimDuration; Vector2 diff = target.DrawPosition - new Vector2(startPos.X, -startPos.Y); diff.Y = -diff.Y; int width = (int)(SpriteWidth * snapState); if (width > 0.0f) { float positionMultiplier = snapState; if (BreakFromMiddle) { positionMultiplier /= 2; DrawRope(spriteBatch, endPos - diff * positionMultiplier, endPos, width); } DrawRope(spriteBatch, startPos, startPos + diff * positionMultiplier, width); } } else { DrawRope(spriteBatch, startPos, endPos, SpriteWidth); } if (startSprite != null || endSprite != null) { Vector2 dir = endPos - startPos; float angle = (float)Math.Atan2(dir.Y, dir.X); if (startSprite != null) { float depth = Math.Min(item.GetDrawDepth() + (startSprite.Depth - item.Sprite.Depth), 0.999f); startSprite?.Draw(spriteBatch, startPos, overrideColor ?? SpriteColor, angle, depth: depth); } if (endSprite != null && (!Snapped || BreakFromMiddle)) { float depth = Math.Min(item.GetDrawDepth() + (endSprite.Depth - item.Sprite.Depth), 0.999f); endSprite?.Draw(spriteBatch, endPos, overrideColor ?? SpriteColor, angle, depth: depth); } } } private void DrawRope(SpriteBatch spriteBatch, Vector2 startPos, Vector2 endPos, int width, Color? overrideColor = null) { float depth = sprite == null ? item.Sprite.Depth + 0.001f : Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f); if (sprite?.Texture == null) { GUI.DrawLine(spriteBatch, startPos, endPos, overrideColor ?? SpriteColor, depth: depth, width: width); return; } if (Tile) { float length = Vector2.Distance(startPos, endPos); Vector2 dir = (endPos - startPos) / length; float x; for (x = 0.0f; x <= length - sprite.size.X; x += sprite.size.X) { GUI.DrawLine(spriteBatch, sprite, startPos + dir * (x - 5.0f), startPos + dir * (x + sprite.size.X), overrideColor ?? SpriteColor, depth: depth, width: width); } float leftOver = length - x; if (leftOver > 0.0f) { GUI.DrawLine(spriteBatch, sprite, startPos + dir * (x - 5.0f), endPos, overrideColor ?? SpriteColor, depth: depth, width: width); } } else { GUI.DrawLine(spriteBatch, sprite, startPos, endPos, overrideColor ?? SpriteColor, depth: depth, width: width); } } private void PlaySound(RoundSound sound, Vector2 position) { if (sound == null) { return; } if (sound == reelSound) { if (reelSoundChannel is not { IsPlaying: true }) { reelSoundChannel = SoundPlayer.PlaySound(sound, position); if (reelSoundChannel != null) { reelSoundChannel.Looping = true; } } else { reelSoundChannel.Position = new Vector3(position, 0); reelSoundChannel.Gain = MathHelper.Lerp(0, 1.0f, MathUtils.InverseLerp(MinPullDistance, MaxLength, MathUtils.Pow(currentRopeLength, 1.5f))); reelSoundChannel.FrequencyMultiplier = MathHelper.Lerp(ReelSoundPitchSlide.X, ReelSoundPitchSlide.Y, MathUtils.InverseLerp(MinPullDistance, MaxLength, currentRopeLength)); } } else { SoundPlayer.PlaySound(sound, position); } } public void ClientEventRead(IReadMessage msg, float sendingTime) { snapped = msg.ReadBoolean(); if (!snapped) { ushort targetId = msg.ReadUInt16(); ushort sourceId = msg.ReadUInt16(); byte limbIndex = msg.ReadByte(); if (Entity.FindEntityByID(targetId) is not Item target) { return; } var source = Entity.FindEntityByID(sourceId); switch (source) { case Character sourceCharacter when limbIndex >= 0 && limbIndex < sourceCharacter.AnimController.Limbs.Length: { Limb sourceLimb = sourceCharacter.AnimController.Limbs[limbIndex]; Attach(sourceLimb, target); sourceCharacter.AnimController.DragWithRope(); break; } case ISpatialEntity spatialEntity: Attach(spatialEntity, target); break; } } } protected override void RemoveComponentSpecific() { sprite?.Remove(); sprite = null; startSprite?.Remove(); startSprite = null; endSprite?.Remove(); endSprite = null; reelSoundChannel?.FadeOutAndDispose(); reelSoundChannel = null; } } }