using System.Linq; using System.Numerics; using Content.Shared._Horizon.Fishing.Components; using Content.Shared._Horizon.Fishing.Systems; using Content.Shared.EntityTable; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Events; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server._Horizon.Fishing; public sealed class FishingSystem : SharedFishingSystem { [Dependency] private readonly IComponentFactory _compFactory = default!; [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly PhysicsSystem _physics = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnFloatCollide); SubscribeLocalEvent(OnFishingInteract); } private void OnFishingInteract(EntityUid uid, FishingRodComponent component, UseInHandEvent args) { if (!FisherQuery.TryComp(args.User, out var fisherComp) || fisherComp.TotalProgress == null || args.Handled || !Timing.IsFirstTimePredicted) return; fisherComp.TotalProgress += fisherComp.ProgressPerUse * component.Efficiency; Dirty(args.User, fisherComp); args.Handled = true; } private void OnFloatCollide(Entity ent, ref StartCollideEvent args) { var attachedEnt = args.OtherEntity; if (HasComp(attachedEnt)) return; if (!FishSpotQuery.TryComp(attachedEnt, out var spotComp)) { if (args.OtherBody.BodyType == BodyType.Static) return; Anchor(ent, attachedEnt); return; } Anchor(ent, attachedEnt); var fish = spotComp.FishList.GetSpawns(_random.GetRandom(), EntityManager, _proto, new EntityTableContext()).First(); _proto.Index(fish).TryGetComponent(out FishComponent? fishComp, _compFactory); var activeFishSpot = EnsureComp(attachedEnt); activeFishSpot.Fish = fish; activeFishSpot.FishDifficulty = fishComp?.FishDifficulty ?? FishComponent.DefaultDifficulty; var time = spotComp.FishDefaultTimer + _random.NextFloat(-spotComp.FishTimerVariety, spotComp.FishTimerVariety); activeFishSpot.FishingStartTime = Timing.CurTime + TimeSpan.FromSeconds(time); activeFishSpot.AttachedFishingLure = ent; Dirty(attachedEnt, activeFishSpot); Dirty(ent); } private void Anchor(Entity ent, EntityUid attachedEnt) { var spotPosition = Xform.GetWorldPosition(attachedEnt); Xform.SetWorldPosition(ent, spotPosition); Xform.SetParent(ent, attachedEnt); _physics.SetLinearVelocity(ent, Vector2.Zero); _physics.SetAngularVelocity(ent, 0f); ent.Comp.AttachedEntity = attachedEnt; RemComp(ent); RemComp(ent); } protected override void StopFishing( Entity fishingRod, EntityUid? fisher, EntityUid? attachedEnt) { QueueDel(fishingRod.Comp.FishingLure); base.StopFishing(fishingRod, fisher, attachedEnt); } protected override void SetupFishingFloat(Entity fishingRod, EntityUid player, EntityCoordinates target) { var (uid, component) = fishingRod; var targetCoords = Xform.ToMapCoordinates(target); var playerCoords = Xform.GetMapCoordinates(Transform(player)); var fishFloat = Spawn(component.FloatPrototype, playerCoords); component.FishingLure = fishFloat; Dirty(uid, component); var direction = targetCoords.Position - playerCoords.Position; if (direction == Vector2.Zero) direction = Vector2.UnitX; Throwing.TryThrow(fishFloat, direction, 15f, player, 2f, null, true); var fishLureComp = EnsureComp(fishFloat); fishLureComp.FishingRod = uid; Dirty(fishFloat, fishLureComp); var visuals = EnsureComp(fishFloat); visuals.Sprite = component.RopeSprite; visuals.OffsetA = component.RopeLureOffset; visuals.OffsetB = component.RopeUserOffset; visuals.Target = GetNetEntity(uid); } protected override void ThrowFishReward(EntProtoId fishId, EntityUid fishSpot, EntityUid target) { var position = Transform(fishSpot).Coordinates; var fish = Spawn(fishId, position); var direction = Xform.GetWorldPosition(target) - Xform.GetWorldPosition(fish); var length = direction.Length(); var distance = Math.Clamp(length, 0.5f, 15f); direction *= distance / length; Throwing.TryThrow(fish, direction, 7f); } protected override void CalculateFightingTimings(Entity fisher, ActiveFishingSpotComponent activeSpotComp) { if (Timing.CurTime < fisher.Comp.NextStruggle) return; fisher.Comp.NextStruggle = Timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(0.06f, 0.18f)); fisher.Comp.TotalProgress -= activeSpotComp.FishDifficulty; Dirty(fisher); } }