Port Fishing from Goob (#4615)
* FISHING REAL * Update fishingspots.yml * Delete fishingspots.yml * Create fishingspots.yml Removed the component that was causing errors * Update Content.Client/_Goobstation/Fishing/Overlays/FishingOverlay.cs Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Structures/Furniture/toilet.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Tiles/lava.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Tiles/liquid_plasma.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Tiles/liquid_plasma.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Tiles/water.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/_Goobstation/Recipes/Construction/Graphs/misc/makeshift_rod.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/Entities/Tiles/water.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/_Goobstation/Actions/fishing.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Update Resources/Prototypes/_Goobstation/Catalog/Fills/Crates/service.yml Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> * Changed the name spaces * Update SharedFishingSystem.cs * Revert "Update SharedFishingSystem.cs" This reverts commit78451cd5c2. * Update FishingOverlay.cs * Update FishingOverlay.cs * Revert "Changed the name spaces" This reverts commit50bac6c4be. * Revert "Changed the name spaces" * Fixed name spaces and the sprite system * Update makeshift_rod.yml * Update makeshift_rod.yml * Update Content.Client/_Goobstation/Fishing/Overlays/FishingOverlay.cs Signed-off-by: Tobias Berger <toby@tobot.dev> * Update Content.Client/_Goobstation/Fishing/Overlays/FishingOverlay.cs Signed-off-by: Tobias Berger <toby@tobot.dev> * Fixed Name Spaced Changed the names spaces and did the ones I forgot to do initially. * Changed name spaces again * Fixed name spaces real * Update SharedFishingSystem.cs * Update SharedFishingSystem.cs * Update SharedFishingSystem.cs * Update FishingSystem.cs * Update FishingSystem.cs --------- Signed-off-by: Apachito <ivanlopezlegos2004@gmail.com> Signed-off-by: Tobias Berger <toby@tobot.dev> Co-authored-by: Tobias Berger <toby@tobot.dev>
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Client._Goobstation.Fishing.Overlays;
|
||||
using Content.Shared._Goobstation.Fishing.Components;
|
||||
using Content.Shared._Goobstation.Fishing.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._Goobstation.Fishing;
|
||||
|
||||
public sealed class FishingSystem : SharedFishingSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlay.AddOverlay(new FishingOverlay(EntityManager, _player));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlay.RemoveOverlay<FishingOverlay>();
|
||||
}
|
||||
|
||||
// Does nothing on client, because can't spawn entities in prediction
|
||||
protected override void SetupFishingFloat(Entity<FishingRodComponent> fishingRod, EntityUid player, EntityCoordinates target) {}
|
||||
|
||||
// Does nothing on client, because can't delete entities in prediction
|
||||
protected override void ThrowFishReward(EntProtoId fishId, EntityUid fishSpot, EntityUid target) {}
|
||||
|
||||
// Does nothing on client, because NUKE ALL PREDICTION!!!! (UseInHands event sometimes gets declined on Server side, and it desyncs, so we can't predict that sadly.
|
||||
protected override void CalculateFightingTimings(Entity<ActiveFisherComponent> fisher, ActiveFishingSpotComponent activeSpotComp) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Systems;
|
||||
using Content.Shared._Goobstation.Fishing.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._Goobstation.Fishing.Overlays;
|
||||
|
||||
public sealed class FishingOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IPlayerManager _player;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly ProgressColorSystem _progressColor;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly Texture _barTexture;
|
||||
|
||||
// Fractional positions for progress bar fill (relative to texture height/width)
|
||||
private const float StartYFraction = 0.09375f; // 3/32
|
||||
private const float EndYFraction = 0.90625f; // 29/32
|
||||
private const float BarWidthFraction = 0.2f; // 2/10
|
||||
|
||||
// Apply a custom scale factor to reduce the size of the progress bar
|
||||
// We dont want to do this because muh pixel consistency, but i'll keep it here as an option
|
||||
private const float BarScale = 1f;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
public FishingOverlay(IEntityManager entManager, IPlayerManager player)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_player = player;
|
||||
_transform = _entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
||||
_progressColor = _entManager.System<ProgressColorSystem>();
|
||||
_sprite = _entManager.System<SpriteSystem>();
|
||||
// Load the progress bar texture
|
||||
var sprite = new SpriteSpecifier.Rsi(new("/Textures/_Goobstation/Interface/Misc/fish_bar.rsi"), "icon");
|
||||
_barTexture = _entManager.EntitySysManager.GetEntitySystem<SpriteSystem>().Frame0(sprite);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
const float scale = 1f;
|
||||
var scaleMatrix = Matrix3Helpers.CreateScale(new Vector2(scale, scale));
|
||||
var rotationMatrix = Matrix3Helpers.CreateRotation(-rotation);
|
||||
|
||||
// Define bounds for culling entities outside the viewport
|
||||
var bounds = args.WorldAABB.Enlarged(5f);
|
||||
var localEnt = _player.LocalSession?.AttachedEntity;
|
||||
|
||||
// Calculate the size of the texture in world units
|
||||
var textureSize = new Vector2(_barTexture.Width, _barTexture.Height) / EyeManager.PixelsPerMeter;
|
||||
|
||||
var scaledTextureSize = textureSize * BarScale;
|
||||
|
||||
// Define the progress bar's width as a fraction of the texture width
|
||||
var barWidth = scaledTextureSize.X * BarWidthFraction;
|
||||
|
||||
// Iterate through all entities with ActiveFisherComponent
|
||||
var enumerator = _entManager.AllEntityQueryEnumerator<ActiveFisherComponent, SpriteComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var comp, out var sprite, out var xform))
|
||||
{
|
||||
// Skip if the entity is not on the current map, has invalid progress, or is not the local player
|
||||
if (xform.MapID != args.MapId ||
|
||||
comp.TotalProgress == null ||
|
||||
comp.TotalProgress < 0 ||
|
||||
uid != localEnt)
|
||||
continue;
|
||||
|
||||
// Get the world position of the entity
|
||||
var worldPosition = _transform.GetWorldPosition(xform, xformQuery);
|
||||
if (!bounds.Contains(worldPosition))
|
||||
continue;
|
||||
|
||||
// Set up the transformation matrix for rendering
|
||||
var worldMatrix = Matrix3Helpers.CreateTranslation(worldPosition);
|
||||
var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
|
||||
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);
|
||||
handle.SetTransform(matty);
|
||||
|
||||
// Calculate the position of the progress bar relative to the entity
|
||||
var position = new Vector2(
|
||||
_sprite.GetLocalBounds((uid, sprite)).Width / 2f,
|
||||
-scaledTextureSize.Y / 2f // Center vertically
|
||||
);
|
||||
|
||||
// Draw the background texture at the scaled size
|
||||
handle.DrawTextureRect(_barTexture, new Box2(position, position + scaledTextureSize));
|
||||
|
||||
// Calculate progress and clamp it to [0, 1]
|
||||
var progress = Math.Clamp(comp.TotalProgress.Value, 0f, 1f);
|
||||
|
||||
// Calculate the fill height based on progress
|
||||
var startYPixel = scaledTextureSize.Y * StartYFraction;
|
||||
var endYPixel = scaledTextureSize.Y * EndYFraction;
|
||||
var yProgress = (endYPixel - startYPixel) * progress + startYPixel;
|
||||
|
||||
// Define the fill box with the correct width and height
|
||||
var box = new Box2(
|
||||
new Vector2((scaledTextureSize.X - barWidth) / 2f, startYPixel),
|
||||
new Vector2((scaledTextureSize.X + barWidth) / 2f, yProgress)
|
||||
);
|
||||
|
||||
// Translate the box to the correct position
|
||||
box = box.Translated(position);
|
||||
|
||||
// Draw the progress fill
|
||||
var color = GetProgressColor(progress);
|
||||
handle.DrawRect(box, color);
|
||||
}
|
||||
|
||||
// Reset the shader and transform
|
||||
handle.UseShader(null);
|
||||
handle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color for the progress bar based on the progress value.
|
||||
/// </summary>
|
||||
public Color GetProgressColor(float progress, float alpha = 1f)
|
||||
{
|
||||
return _progressColor.GetProgressColor(progress).WithAlpha(alpha);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <93730715+Aviu00@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <aviu00@protonmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared._Goobstation.Fishing.Components;
|
||||
using Content.Shared._Goobstation.Fishing.Events;
|
||||
using Content.Shared._Goobstation.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._Goobstation.Fishing;
|
||||
|
||||
public sealed class FishingSystem : SharedFishingSystem
|
||||
{
|
||||
// Here we calculate the start of fishing, because apparently StartCollideEvent
|
||||
// works janky on clientside so we can't predict when fishing starts.
|
||||
[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<FishingLureComponent, StartCollideEvent>(OnFloatCollide);
|
||||
SubscribeLocalEvent<FishingRodComponent, UseInHandEvent>(OnFishingInteract);
|
||||
}
|
||||
|
||||
#region Event handling
|
||||
|
||||
private void OnFloatCollide(Entity<FishingLureComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
// TODO: make it so this can collide with any unacnchored objects (items, mobs, etc) but not the player casting it (get parent of rod?)
|
||||
// Fishing spot logic
|
||||
var attachedEnt = args.OtherEntity;
|
||||
|
||||
if (HasComp<ActiveFishingSpotComponent>(attachedEnt))
|
||||
return;
|
||||
|
||||
if (!FishSpotQuery.TryComp(attachedEnt, out var spotComp))
|
||||
{
|
||||
if (args.OtherBody.BodyType == BodyType.Static)
|
||||
return;
|
||||
|
||||
Anchor(ent, attachedEnt);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anchor fishing float on an entity
|
||||
Anchor(ent, attachedEnt);
|
||||
|
||||
// Currently we don't support multiple loots from this
|
||||
var fish = spotComp.FishList.GetSpawns(_random.GetRandom(), EntityManager, _proto, new EntityTableContext()).First();
|
||||
|
||||
// Get fish difficulty
|
||||
_proto.Index(fish).TryGetComponent(out FishComponent? fishComp, _compFactory);
|
||||
|
||||
// Assign things that depend on the fish
|
||||
var activeFishSpot = EnsureComp<ActiveFishingSpotComponent>(attachedEnt);
|
||||
activeFishSpot.Fish = fish;
|
||||
activeFishSpot.FishDifficulty = fishComp?.FishDifficulty ?? FishComponent.DefaultDifficulty;
|
||||
|
||||
// Assign things that depend on the spot
|
||||
var time = spotComp.FishDefaultTimer + _random.NextFloat(-spotComp.FishTimerVariety, spotComp.FishTimerVariety);
|
||||
activeFishSpot.FishingStartTime = Timing.CurTime + TimeSpan.FromSeconds(time);
|
||||
activeFishSpot.AttachedFishingLure = ent;
|
||||
|
||||
// Declares war on prediction
|
||||
Dirty(attachedEnt, activeFishSpot);
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
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); // That's a bit evil, but we want to keep numbers real.
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void Anchor(Entity<FishingLureComponent> 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<ItemComponent>(ent);
|
||||
RemComp<PullableComponent>(ent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void SetupFishingFloat(Entity<FishingRodComponent> 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);
|
||||
|
||||
// Calculate throw direction
|
||||
var direction = targetCoords.Position - playerCoords.Position;
|
||||
if (direction == Vector2.Zero)
|
||||
direction = Vector2.UnitX; // If the user somehow manages to click directly in the center of themself, just toss it to the right i guess.
|
||||
|
||||
// Yeet
|
||||
Throwing.TryThrow(fishFloat, direction, 15f, player, 2f, null, true);
|
||||
|
||||
// Set up lure component
|
||||
var fishLureComp = EnsureComp<FishingLureComponent>(fishFloat);
|
||||
fishLureComp.FishingRod = uid;
|
||||
Dirty(fishFloat, fishLureComp);
|
||||
|
||||
// Rope visuals
|
||||
var visuals = EnsureComp<JointVisualsComponent>(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);
|
||||
// Throw da fish back to the player because it looks funny
|
||||
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<ActiveFisherComponent> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Applied to players that are pulling fish out from water
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ActiveFisherComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? NextStruggle;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public float? TotalProgress;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ProgressPerUse = 0.05f;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid FishingRod;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic component, that is assigned to active fishing spots that are currently waiting for da fish.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ActiveFishingSpotComponent : Component
|
||||
{
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public EntityUid? AttachedFishingLure;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? FishingStartTime;
|
||||
|
||||
/// <summary>
|
||||
/// If true, someone is pulling fish out of this spot.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsActive;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public float FishDifficulty;
|
||||
|
||||
/// <summary>
|
||||
/// Fish that we're currently trying to catch
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? Fish; // not networked because useless for client
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The fish itself!
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class FishComponent : Component
|
||||
{
|
||||
public const float DefaultDifficulty = 0.021f;
|
||||
|
||||
[DataField("difficulty")]
|
||||
public float FishDifficulty = DefaultDifficulty;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FishingLureComponent : Component
|
||||
{
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid FishingRod;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? AttachedEntity;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan NextUpdate;
|
||||
|
||||
[DataField]
|
||||
public float UpdateInterval = 1f;
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FishingRodComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Higher value will make every interact more productive.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Efficiency = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// At what progress fishing starts.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float StartingProgress = 0.33f;
|
||||
|
||||
/// <summary>
|
||||
/// How many seconds we wait until fish starts to fight with us
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float StartingStruggleTime = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// If lure moves bigger than this distance away from the rod,
|
||||
/// it will force it to reel instantly.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float BreakOnDistance = 8f;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId FloatPrototype = "FishingLure";
|
||||
|
||||
[DataField]
|
||||
public SpriteSpecifier RopeSprite =
|
||||
new SpriteSpecifier.Rsi(new ResPath("_Goobstation/Objects/Specific/Fishing/fishing_lure.rsi"), "rope");
|
||||
|
||||
[DataField, ViewVariables]
|
||||
public Vector2 RopeUserOffset = new (0f, 0f);
|
||||
|
||||
[DataField, ViewVariables]
|
||||
public Vector2 RopeLureOffset = new (0f, 0f);
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? FishingLure;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId ThrowLureActionId = "ActionStartFishing";
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ThrowLureActionEntity;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId PullLureActionId = "ActionStopFishing";
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? PullLureActionEntity;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.EntityTable.EntitySelectors;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class FishingSpotComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// All possible fishes to catch here
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityTableSelector FishList;
|
||||
|
||||
/// <summary>
|
||||
/// Default time for fish to occur
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float FishDefaultTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Variety number that FishDefaultTimer can go up or down to randomly
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float FishTimerVariety;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Events;
|
||||
|
||||
public sealed partial class ThrowFishingLureActionEvent : WorldTargetActionEvent;
|
||||
|
||||
public sealed partial class PullFishingLureActionEvent : InstantActionEvent;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ActiveFishingSpotComponentState : ComponentState
|
||||
{
|
||||
public readonly float FishDifficulty;
|
||||
public bool IsActive;
|
||||
public TimeSpan? FishingStartTime;
|
||||
public NetEntity? AttachedFishingLure;
|
||||
|
||||
public ActiveFishingSpotComponentState(float fishDifficulty, bool isActive, TimeSpan? fishingStartTime, NetEntity? attachedFishingLure)
|
||||
{
|
||||
FishDifficulty = fishDifficulty;
|
||||
IsActive = isActive;
|
||||
FishingStartTime = fishingStartTime;
|
||||
AttachedFishingLure = attachedFishingLure;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
// SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
// SPDX-FileCopyrightText: 2025 Aviu00 <aviu00@protonmail.com>
|
||||
// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
// SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
// SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
using Content.Shared._Goobstation.Fishing.Components;
|
||||
using Content.Shared._Goobstation.Fishing.Events;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._Goobstation.Fishing.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles... da fish
|
||||
/// </summary>
|
||||
public abstract class SharedFishingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] protected readonly INetManager Net = default!;
|
||||
[Dependency] protected readonly ThrowingSystem Throwing = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem Xform = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
|
||||
protected EntityQuery<ActiveFisherComponent> FisherQuery;
|
||||
protected EntityQuery<ActiveFishingSpotComponent> ActiveFishSpotQuery;
|
||||
protected EntityQuery<FishingSpotComponent> FishSpotQuery;
|
||||
protected EntityQuery<FishingRodComponent> FishRodQuery;
|
||||
protected EntityQuery<FishingLureComponent> FishLureQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
FisherQuery = GetEntityQuery<ActiveFisherComponent>();
|
||||
ActiveFishSpotQuery = GetEntityQuery<ActiveFishingSpotComponent>();
|
||||
FishSpotQuery = GetEntityQuery<FishingSpotComponent>();
|
||||
FishRodQuery = GetEntityQuery<FishingRodComponent>();
|
||||
FishLureQuery = GetEntityQuery<FishingLureComponent>();
|
||||
|
||||
SubscribeLocalEvent<FishingRodComponent, MapInitEvent>(OnFishingRodInit);
|
||||
SubscribeLocalEvent<FishingRodComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<FishingRodComponent, ThrowFishingLureActionEvent>(OnThrowFloat);
|
||||
SubscribeLocalEvent<FishingRodComponent, PullFishingLureActionEvent>(OnPullFloat);
|
||||
SubscribeLocalEvent<FishingRodComponent, EntParentChangedMessage>(OnRodParentChanged);
|
||||
|
||||
SubscribeLocalEvent<FishingRodComponent, EntityTerminatingEvent>(OnRodTerminating);
|
||||
SubscribeLocalEvent<FishingLureComponent, EntityTerminatingEvent>(OnLureTerminating);
|
||||
SubscribeLocalEvent<ActiveFishingSpotComponent, EntityTerminatingEvent>(OnSpotTerminating);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
UpdateFishing();
|
||||
}
|
||||
|
||||
private void UpdateFishing()
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var currentTime = Timing.CurTime;
|
||||
var activeFishers = EntityQueryEnumerator<ActiveFisherComponent>();
|
||||
while (activeFishers.MoveNext(out var fisher, out var fisherComp))
|
||||
{
|
||||
// Get fishing rod, then float, then spot... ReCurse.
|
||||
if (TerminatingOrDeleted(fisherComp.FishingRod) ||
|
||||
!FishRodQuery.TryComp(fisherComp.FishingRod, out var fishingRodComp) ||
|
||||
TerminatingOrDeleted(fishingRodComp.FishingLure) ||
|
||||
!FishLureQuery.TryComp(fishingRodComp.FishingLure, out var fishingFloatComp) ||
|
||||
TerminatingOrDeleted(fishingFloatComp.AttachedEntity) ||
|
||||
!ActiveFishSpotQuery.TryComp(fishingFloatComp.AttachedEntity, out var activeSpotComp))
|
||||
continue;
|
||||
|
||||
var fishRod = fisherComp.FishingRod;
|
||||
var fishSpot = fishingFloatComp.AttachedEntity.Value;
|
||||
|
||||
fisherComp.TotalProgress ??= fishingRodComp.StartingProgress;
|
||||
fisherComp.NextStruggle ??= Timing.CurTime + TimeSpan.FromSeconds(fishingRodComp.StartingStruggleTime);
|
||||
|
||||
// Fish fighting logic
|
||||
CalculateFightingTimings((fisher, fisherComp), activeSpotComp);
|
||||
|
||||
switch (fisherComp.TotalProgress)
|
||||
{
|
||||
case < 0f:
|
||||
// It's over
|
||||
_popup.PopupEntity(Loc.GetString("fishing-progress-fail"), fisher, fisher);
|
||||
StopFishing((fishRod, fishingRodComp), fisher);
|
||||
continue;
|
||||
|
||||
case >= 1f:
|
||||
if (activeSpotComp.Fish != null)
|
||||
{
|
||||
ThrowFishReward(activeSpotComp.Fish.Value, fishSpot, fisher);
|
||||
_popup.PopupEntity(Loc.GetString("fishing-progress-success"), fisher, fisher);
|
||||
}
|
||||
|
||||
StopFishing((fishRod, fishingRodComp), fisher);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var fishingSpots = EntityQueryEnumerator<ActiveFishingSpotComponent>();
|
||||
while (fishingSpots.MoveNext(out var activeSpotComp))
|
||||
{
|
||||
if (currentTime < activeSpotComp.FishingStartTime || activeSpotComp.IsActive || activeSpotComp.FishingStartTime == null)
|
||||
continue;
|
||||
|
||||
// Trigger start of the fishing process
|
||||
if (TerminatingOrDeleted(activeSpotComp.AttachedFishingLure))
|
||||
continue;
|
||||
|
||||
// Get fishing lure, then rod, then player... ReCurse.
|
||||
if (!FishLureQuery.TryComp(activeSpotComp.AttachedFishingLure, out var fishingFloatComp) ||
|
||||
TerminatingOrDeleted(fishingFloatComp.FishingRod) ||
|
||||
!FishRodQuery.TryComp(fishingFloatComp.FishingRod, out var fishRodComp))
|
||||
continue;
|
||||
|
||||
var fishRod = fishingFloatComp.FishingRod;
|
||||
|
||||
if (TerminatingOrDeleted(fishingFloatComp.FishingRod))
|
||||
continue;
|
||||
|
||||
var fisher = Transform(fishingFloatComp.FishingRod).ParentUid;
|
||||
|
||||
if (!Exists(fisher) || TerminatingOrDeleted(fisher))
|
||||
continue;
|
||||
|
||||
var activeFisher = EnsureComp<ActiveFisherComponent>(fisher);
|
||||
activeFisher.FishingRod = fishRod;
|
||||
activeFisher.ProgressPerUse *= fishRodComp.Efficiency;
|
||||
activeFisher.TotalProgress = fishRodComp.StartingProgress;
|
||||
activeFisher.NextStruggle = Timing.CurTime + TimeSpan.FromSeconds(fishRodComp.StartingStruggleTime); // Compensate ping for 0.3 seconds
|
||||
|
||||
// Predicted because it works like 99.9% of the time anyway.
|
||||
_popup.PopupPredicted(Loc.GetString("fishing-progress-start"), fisher, fisher);
|
||||
activeSpotComp.IsActive = true;
|
||||
}
|
||||
|
||||
var fishingLures = EntityQueryEnumerator<FishingLureComponent, TransformComponent>();
|
||||
while (fishingLures.MoveNext(out var fishingLure, out var lureComp, out var xform))
|
||||
{
|
||||
if (lureComp.NextUpdate > Timing.CurTime)
|
||||
continue;
|
||||
|
||||
lureComp.NextUpdate = Timing.CurTime + TimeSpan.FromSeconds(lureComp.UpdateInterval);
|
||||
|
||||
if (TerminatingOrDeleted(lureComp.FishingRod) ||
|
||||
!FishRodQuery.TryComp(lureComp.FishingRod, out var fishingRodComp))
|
||||
continue;
|
||||
|
||||
var lurePos = Xform.GetMapCoordinates(fishingLure, xform);
|
||||
var rodPos = Xform.GetMapCoordinates(lureComp.FishingRod);
|
||||
var distance = lurePos.Position - rodPos.Position;
|
||||
var fisher = Transform(lureComp.FishingRod).ParentUid;
|
||||
|
||||
if (!Exists(fisher) || TerminatingOrDeleted(fisher) ||
|
||||
distance.Length() > fishingRodComp.BreakOnDistance ||
|
||||
lurePos.MapId != rodPos.MapId ||
|
||||
!_hands.IsHolding(fisher, lureComp.FishingRod) ||
|
||||
!HasComp<ActorComponent>(fisher))
|
||||
{
|
||||
var rod = (lureComp.FishingRod, fishingRodComp);
|
||||
StopFishing(rod, fisher);
|
||||
ToggleFishingActions(rod, fisher, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// if AddPulling is true, we ADD Pulling action and REMOVE Throwing action.
|
||||
/// Basically true if we start, and false if we end.
|
||||
/// </summary>
|
||||
private void ToggleFishingActions(Entity<FishingRodComponent> ent, EntityUid fisher, bool addPulling)
|
||||
{
|
||||
if (TerminatingOrDeleted(ent) || !Exists(fisher) || TerminatingOrDeleted(fisher))
|
||||
return;
|
||||
|
||||
if (addPulling)
|
||||
{
|
||||
_actions.RemoveAction(ent.Comp.ThrowLureActionEntity);
|
||||
_actions.AddAction(fisher, ref ent.Comp.PullLureActionEntity, ent.Comp.PullLureActionId, ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_actions.RemoveAction(ent.Comp.PullLureActionEntity);
|
||||
_actions.AddAction(fisher, ref ent.Comp.ThrowLureActionEntity, ent.Comp.ThrowLureActionId, ent);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void CalculateFightingTimings(Entity<ActiveFisherComponent> fisher, ActiveFishingSpotComponent activeSpotComp);
|
||||
|
||||
/// <summary>
|
||||
/// Server-side only, sets up fishing float and throws it
|
||||
/// </summary>
|
||||
protected abstract void SetupFishingFloat(Entity<FishingRodComponent> fishingRod, EntityUid player, EntityCoordinates target);
|
||||
|
||||
/// <summary>
|
||||
/// Server-side only, spawns a fish and throws it to our player!
|
||||
/// </summary>
|
||||
protected abstract void ThrowFishReward(EntProtoId fishId, EntityUid fishSpot, EntityUid target);
|
||||
|
||||
/// <summary>
|
||||
/// Reels the fishing rod back and stops fishing progress if arguments are passed to it.
|
||||
/// </summary>
|
||||
private void StopFishing(
|
||||
Entity<FishingRodComponent> fishingRod,
|
||||
EntityUid? fisher)
|
||||
{
|
||||
var nullOrDeleted =
|
||||
fishingRod.Comp.FishingLure == null || TerminatingOrDeleted(fishingRod.Comp.FishingLure.Value);
|
||||
|
||||
if (!nullOrDeleted && FishLureQuery.TryComp(fishingRod.Comp.FishingLure, out var lureComp) &&
|
||||
!TerminatingOrDeleted(lureComp.AttachedEntity) &&
|
||||
ActiveFishSpotQuery.TryComp(lureComp.AttachedEntity, out var activeSpotComp))
|
||||
RemCompDeferred(lureComp.AttachedEntity.Value, activeSpotComp);
|
||||
|
||||
if (!nullOrDeleted && Net.IsServer)
|
||||
QueueDel(fishingRod.Comp.FishingLure);
|
||||
|
||||
if (Exists(fisher) && !TerminatingOrDeleted(fisher) && FisherQuery.TryComp(fisher, out var fisherComp))
|
||||
{
|
||||
RemCompDeferred(fisher.Value, fisherComp);
|
||||
|
||||
ToggleFishingActions(fishingRod, fisher.Value, false);
|
||||
}
|
||||
|
||||
fishingRod.Comp.FishingLure = null;
|
||||
}
|
||||
|
||||
#region Terminating Events
|
||||
|
||||
private void OnRodTerminating(Entity<FishingRodComponent> ent, ref EntityTerminatingEvent args)
|
||||
{
|
||||
TryStopFishing(ent);
|
||||
}
|
||||
|
||||
private void OnLureTerminating(Entity<FishingLureComponent> ent, ref EntityTerminatingEvent args)
|
||||
{
|
||||
TryStopFishing(ent);
|
||||
}
|
||||
|
||||
private void OnSpotTerminating(Entity<ActiveFishingSpotComponent> ent, ref EntityTerminatingEvent args)
|
||||
{
|
||||
TryStopFishing(ent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Deletion Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Stops fishing by taking only the Fishing rod as an argument.
|
||||
/// </summary>
|
||||
private void TryStopFishing(Entity<FishingRodComponent> rod)
|
||||
{
|
||||
var player = Transform(rod).ParentUid;
|
||||
StopFishing(rod, player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops fishing by taking only the Fishing lure as an argument.
|
||||
/// </summary>
|
||||
private void TryStopFishing(Entity<FishingLureComponent> lure)
|
||||
{
|
||||
if (!FishRodQuery.TryComp(lure.Comp.FishingRod, out var rodComp))
|
||||
return;
|
||||
|
||||
TryStopFishing((lure.Comp.FishingRod, rodComp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops fishing by taking only the Active spot as an argument.
|
||||
/// </summary>
|
||||
private void TryStopFishing(Entity<ActiveFishingSpotComponent> spot)
|
||||
{
|
||||
if (!FishLureQuery.TryComp(spot.Comp.AttachedFishingLure, out var lureComp))
|
||||
return;
|
||||
|
||||
if (!FishRodQuery.TryComp(lureComp.FishingRod, out var rodComp))
|
||||
return;
|
||||
|
||||
TryStopFishing((lureComp.FishingRod, rodComp));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
|
||||
private void OnThrowFloat(Entity<FishingRodComponent> ent, ref ThrowFishingLureActionEvent args)
|
||||
{
|
||||
if (args.Handled || !Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var player = args.Performer;
|
||||
|
||||
if (ent.Comp.FishingLure != null || !Xform.IsValid(args.Target))
|
||||
{
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SetupFishingFloat(ent, player, args.Target);
|
||||
ToggleFishingActions(ent, player, true);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPullFloat(Entity<FishingRodComponent> ent, ref PullFishingLureActionEvent args)
|
||||
{
|
||||
if (args.Handled || !Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var player = args.Performer;
|
||||
var (uid, component) = ent;
|
||||
|
||||
if (component.FishingLure == null)
|
||||
{
|
||||
ToggleFishingActions(ent, player, true);
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupPredicted(Loc.GetString("fishing-rod-remove-lure", ("ent", Name(uid))), uid, uid);
|
||||
|
||||
if (!FishLureQuery.TryComp(component.FishingLure, out var lureComp))
|
||||
return;
|
||||
|
||||
if (lureComp.AttachedEntity != null && Exists(lureComp.AttachedEntity))
|
||||
{
|
||||
// TODO: so this kinda just lets you pull anything right up to you, it should instead just apply an impulse in your direction modfiied by the weight of the player vs the object
|
||||
// Also we need to autoreel/snap the line if the player gets too far away
|
||||
// Also we should probably PVS override the lure if the rod is in PVS, and vice versa to stop the joint visuals from popping in/out
|
||||
var attachedEnt = lureComp.AttachedEntity.Value;
|
||||
var targetCoords = Xform.GetMapCoordinates(Transform(attachedEnt));
|
||||
var playerCoords = Xform.GetMapCoordinates(Transform(player));
|
||||
var rand = new System.Random((int) Timing.CurTick.Value); // evil random prediction hack
|
||||
|
||||
// Calculate throw direction
|
||||
var direction = (playerCoords.Position - targetCoords.Position) * rand.NextFloat(0.2f, 0.85f);
|
||||
|
||||
// Yeet
|
||||
Throwing.TryThrow(attachedEnt, direction, 4f, player);
|
||||
}
|
||||
|
||||
StopFishing(ent, player);
|
||||
ToggleFishingActions(ent, player, false);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnFishingRodInit(Entity<FishingRodComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
_actions.AddAction(ent, ref ent.Comp.ThrowLureActionEntity, ent.Comp.ThrowLureActionId);
|
||||
}
|
||||
|
||||
private void OnRodParentChanged(Entity<FishingRodComponent> ent, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (TerminatingOrDeleted(ent) || !Exists(args.Transform.ParentUid))
|
||||
return;
|
||||
|
||||
// Anything that is an active fisher should be fine.
|
||||
if (!FisherQuery.HasComp(args.Transform.ParentUid))
|
||||
{
|
||||
StopFishing(ent, args.OldParent);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetActions(Entity<FishingRodComponent> ent, ref GetItemActionsEvent args)
|
||||
{
|
||||
if (ent.Comp.FishingLure == null)
|
||||
args.AddAction(ref ent.Comp.ThrowLureActionEntity, ent.Comp.ThrowLureActionId);
|
||||
else
|
||||
args.AddAction(ref ent.Comp.PullLureActionEntity, ent.Comp.PullLureActionId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- files: ["fishing_rod_cast.ogg"]
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "Taken from Goon at commit 0c11a46275f493f295d6d05d1aa5fad1d935efb4"
|
||||
source: "https://github.com/goonstation/goonstation/blob/master/sound/items/fishing_rod_cast.ogg"
|
||||
|
||||
- files: ["fishing_rod_reel.ogg"]
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "Taken from Goon at commit 0c11a46275f493f295d6d05d1aa5fad1d935efb4"
|
||||
source: "https://github.com/goonstation/goonstation/blob/master/sound/items/fishing_rod_cast.ogg"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fishing-rod-remove-lure = { $ent } is reeling
|
||||
fishing-progress-success = You pull something up from the spot!
|
||||
fishing-progress-fail = You missed it...
|
||||
fishing-progress-lost-rod = You lost control over the { $ent }!
|
||||
fishing-progress-start = You feel something clinging to the fishing lure!
|
||||
|
|
@ -63,6 +63,18 @@
|
|||
- MachineMask
|
||||
layer:
|
||||
- None
|
||||
# Begin Goobstation - fishing
|
||||
fishing:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.4
|
||||
layer:
|
||||
- ItemMask
|
||||
mask:
|
||||
- HighImpassable
|
||||
density: 1000
|
||||
hard: false
|
||||
# End Goobstation - fishing
|
||||
- type: PlungerUse
|
||||
- type: Appearance
|
||||
- type: SecretStash
|
||||
|
|
@ -92,6 +104,13 @@
|
|||
reagents:
|
||||
- ReagentId: Water
|
||||
Quantity: 1
|
||||
# Goobstation - fishing in toilet is fun
|
||||
- type: FishingSpot
|
||||
fishList: !type:NestedSelector
|
||||
tableId: ToiletFishingLootTable
|
||||
fishDefaultTimer: 45.0
|
||||
fishTimerVariety: 15.0
|
||||
# Goobstation - fishing
|
||||
- type: DrainableSolution
|
||||
solution: tank
|
||||
- type: ReagentTank
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@
|
|||
multiplier: 3.75
|
||||
multiplierOnExisting: 0.75
|
||||
- !type:Ignite
|
||||
# Begin Goobstation - fishing
|
||||
- type: FishingSpot
|
||||
fishList: !type:NestedSelector
|
||||
tableId: LavaFishingLootTable
|
||||
fishDefaultTimer: 25.0
|
||||
fishTimerVariety: 15.0
|
||||
# End Goobstation - fishing
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: SyncSprite
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@
|
|||
multiplier: 3.75
|
||||
multiplierOnExisting: 0.75
|
||||
- !type:Ignite
|
||||
# Begin Goobstation - fishing
|
||||
- type: FishingSpot
|
||||
fishList: !type:NestedSelector
|
||||
tableId: PlasmaFishingLootTable
|
||||
fishDefaultTimer: 25.0
|
||||
fishTimerVariety: 15.0
|
||||
# End Goobstation - fishing
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: SyncSprite
|
||||
|
|
@ -51,6 +58,18 @@
|
|||
- ItemMask
|
||||
density: 1000
|
||||
hard: false
|
||||
# Begin Goobstation - fishing
|
||||
fishing:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.4
|
||||
layer:
|
||||
- ItemMask
|
||||
mask:
|
||||
- HighImpassable
|
||||
density: 1000
|
||||
hard: false
|
||||
# End Goobstation - fishing
|
||||
- type: Tag
|
||||
tags:
|
||||
- HideContextMenu
|
||||
|
|
|
|||
|
|
@ -49,6 +49,18 @@
|
|||
- ItemMask
|
||||
density: 1000
|
||||
hard: false
|
||||
# Begin Goobstation - fishing
|
||||
fishing:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.4
|
||||
layer:
|
||||
- ItemMask
|
||||
mask:
|
||||
- HighImpassable
|
||||
density: 1000
|
||||
hard: false
|
||||
# End Goobstation - fishing
|
||||
- type: FootstepModifier
|
||||
footstepSoundCollection:
|
||||
collection: FootstepWater
|
||||
|
|
@ -63,5 +75,12 @@
|
|||
- type: TileEntityEffect
|
||||
effects:
|
||||
- !type:ExtinguishReaction
|
||||
# Begin Goobstation - fishing
|
||||
- type: FishingSpot
|
||||
fishList: !type:NestedSelector
|
||||
tableId: WaterFishingLootTable
|
||||
fishDefaultTimer: 25.0
|
||||
fishTimerVariety: 15.0
|
||||
# End Goobstation - fishing
|
||||
- type: CosmicCorruptible # DeltaV - Cosmic Cult
|
||||
convertTo: FloorCosmicDecay
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionStartFishing
|
||||
name: Throw fishing lure
|
||||
description: Throw lure from the fishing rod to catch something!
|
||||
components:
|
||||
- type: Action
|
||||
icon:
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_lure.rsi
|
||||
state: icon
|
||||
sound:
|
||||
path: /Audio/_Goobstation/Items/Fishing/fishing_rod_cast.ogg
|
||||
itemIconStyle: BigAction
|
||||
useDelay: 2.5
|
||||
- type: TargetAction
|
||||
range: 15
|
||||
checkCanAccess: false
|
||||
- type: WorldTargetAction
|
||||
event: !type:ThrowFishingLureActionEvent
|
||||
|
||||
- type: entity
|
||||
parent: BaseAction
|
||||
id: ActionStopFishing
|
||||
name: Reel fishing rod
|
||||
description: Reel your fishing rod to pull an object that it attached to, or stop fishing.
|
||||
components:
|
||||
- type: Action
|
||||
icon:
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/goon_rod.rsi
|
||||
state: icon
|
||||
sound:
|
||||
path: /Audio/_Goobstation/Items/Fishing/fishing_rod_reel.ogg
|
||||
itemIconStyle: NoItem
|
||||
- type: InstantAction
|
||||
event: !type:PullFishingLureActionEvent
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: cargoProduct
|
||||
id: FishingGoodies
|
||||
icon:
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_rod.rsi
|
||||
state: icon
|
||||
product: CrateFishingGoodies
|
||||
cost: 1000
|
||||
category: cargoproduct-category-name-service
|
||||
group: market
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
id: CrateFishingGoodies
|
||||
parent: CratePlastic
|
||||
name: fishing goodies crate
|
||||
description: A couple of fishing rods, a bear, and a hat. Everything you need to catch all fish on the station!
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- id: FishingRod
|
||||
- id: FishingRodGoon
|
||||
- id: ClothingHeadFishCap
|
||||
- id: DrinkBeerBottleFull
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
# SPDX-FileCopyrightText: 2025 Evige <jussi.heikkila1@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Rouden <149893554+Roudenn@users.noreply.github.com>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
id: BaseFish
|
||||
parent: [SimpleMobBase, BaseItem]
|
||||
abstract: true
|
||||
name: fish
|
||||
description: Water-born creature from the infinite sea... Or toilet.
|
||||
components:
|
||||
- type: Fish
|
||||
difficulty: 0.029
|
||||
- type: StaticPrice
|
||||
price: 250
|
||||
- type: MobThresholds
|
||||
thresholds:
|
||||
0: Alive
|
||||
1: Critical
|
||||
5: Dead
|
||||
- type: MovementSpeedModifier
|
||||
baseWalkSpeed: 0.1
|
||||
baseSprintSpeed: 0.1
|
||||
- type: PassiveDamage
|
||||
allowedStates:
|
||||
- Alive
|
||||
damageCap: 20
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: 1
|
||||
- type: Respirator
|
||||
damage:
|
||||
types:
|
||||
Asphyxiation: 1.0
|
||||
damageRecovery:
|
||||
types:
|
||||
Asphyxiation: 0
|
||||
- type: Butcherable
|
||||
spawned:
|
||||
- id: FoodMeatFish
|
||||
amount: 2
|
||||
- type: Bloodstream
|
||||
bloodMaxVolume: 20
|
||||
bloodReagent: Blood
|
||||
- type: ZombieImmune
|
||||
|
||||
- type: entity
|
||||
id: BaseFishRare
|
||||
parent: BaseFish
|
||||
abstract: true
|
||||
name: rare fish
|
||||
description: Water-born creature from the infinite sea... Or toilet.
|
||||
components:
|
||||
- type: Fish
|
||||
difficulty: 0.038
|
||||
- type: StaticPrice
|
||||
price: 750
|
||||
|
||||
- type: entity
|
||||
id: FishAlien
|
||||
parent: BaseFishRare
|
||||
name: alien fish
|
||||
description: Looks pretty... Abductory.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: alien
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishAngler
|
||||
parent: BaseFish
|
||||
name: angler fish
|
||||
description: Scariest thing in existence, after bingles.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: angler
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishBass
|
||||
parent: BaseFish
|
||||
name: bass fish
|
||||
description: Probably the most normal fish in existence. Unless you use it as bass guitar.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: bass
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishBingle
|
||||
parent: BaseFishRare
|
||||
name: bingle fish
|
||||
description: bingle
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: bingle
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishBlob
|
||||
parent: BaseFish
|
||||
name: blob fish
|
||||
description: Unfortuantly, this is not a 5th level bio-hazard threat.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: blob
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishBlueFintuna
|
||||
parent: BaseFish
|
||||
name: blue fintuna
|
||||
description: It's looking straight to you...
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: blue_fintuna
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishBoyFisher
|
||||
parent: BaseFishRare
|
||||
name: boy fisher
|
||||
description: You like kissing fish, don't you?
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: boy_fisher
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishCat
|
||||
parent: BaseFish
|
||||
name: cat fish
|
||||
description: Ironically this looks nothing like a cat. And it doesn't meow.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: catfish
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishTropicalClown
|
||||
parent: BaseFish
|
||||
name: clown clown fish
|
||||
description: That is almost certanly some kind of joke.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: clown
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
- type: Bloodstream
|
||||
bloodMaxVolume: 20
|
||||
bloodReagent: Laughter
|
||||
|
||||
- type: entity
|
||||
id: FishNukeDisk
|
||||
parent: BaseFishRare
|
||||
name: nuke disk fish
|
||||
description: Erm, Actually, it's a bad idea trying to fit a fish inside a nuclear explosion device.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: disk
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishGib
|
||||
parent: BaseFishRare
|
||||
name: gib stick fish
|
||||
description: "My face when i open a present and get gibstick:"
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: gib
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishMutant
|
||||
parent: BaseFishRare
|
||||
name: mutant fish
|
||||
description: Anomalous fish that looks like it ate some nuclear waste.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: mutant
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishNuker
|
||||
parent: BaseFishRare
|
||||
name: nuclear operative fish
|
||||
description: Predator of the nuke disk fish.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: nuker
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishPuffer
|
||||
parent: BaseFish
|
||||
name: puffer fish
|
||||
description: It is poisonous... in many ways.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: pufferfish
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishSilver
|
||||
parent: BaseFish
|
||||
name: silver fish
|
||||
description: Actually contains silver.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: silverfish
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishTropicalSun
|
||||
parent: BaseFish
|
||||
name: tropical sun fish
|
||||
description: It probably looked at the sun for too long...?
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: sun_tropical
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishToxic
|
||||
parent: BaseFishRare
|
||||
name: toxic waste fish
|
||||
description: "Looking at this fish, you understand how much it was a bad idea to allow nuclear waste to be dumped into rivers."
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: toxic
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
||||
- type: entity
|
||||
id: FishTropical
|
||||
parent: BaseFish
|
||||
name: clown fish
|
||||
description: This fish is always searching for something... Or maybe someone?
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: tropical
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
- type: Bloodstream
|
||||
bloodMaxVolume: 10
|
||||
bloodReagent: Laughter
|
||||
|
||||
- type: entity
|
||||
id: FishIan
|
||||
parent: BaseFishRare
|
||||
name: ian fish
|
||||
description: Water-born ian from the sea.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
state: ian
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fish.rsi
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: entity
|
||||
id: BaseFishingSpot
|
||||
abstract: true
|
||||
name: suspicious waves
|
||||
description: Something is floating here...
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
components:
|
||||
- type: FishingSpot
|
||||
fishList: !type:NestedSelector
|
||||
tableId: WaterFishingLootTable
|
||||
fishDefaultTimer: 25.0
|
||||
fishTimerVariety: 15.0
|
||||
- type: Physics
|
||||
bodyType: Static
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
layer:
|
||||
- ItemMask
|
||||
mask:
|
||||
- HighImpassable
|
||||
density: 1000
|
||||
hard: false
|
||||
- type: Transform
|
||||
anchored: true
|
||||
noRot: true
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
# TODO: change visuals when it's active
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_spot.rsi
|
||||
state: water
|
||||
- type: Icon
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_spot.rsi
|
||||
state: water
|
||||
|
||||
- type: entity
|
||||
id: FishingSpotWater
|
||||
parent: BaseFishingSpot
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# This should be on every other fishing table
|
||||
- type: entityTable
|
||||
id: BasicFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
# 75% chance of scrap of some kind
|
||||
- !type:GroupSelector
|
||||
weight: 75
|
||||
children:
|
||||
- !type:NestedSelector
|
||||
tableId: SalvageScrapLowValue
|
||||
weight: 65
|
||||
- !type:NestedSelector
|
||||
tableId: SalvageScrapHighValue
|
||||
weight: 35
|
||||
# 15% chance of some trash
|
||||
- !type:NestedSelector
|
||||
tableId: GenericTrashItems
|
||||
weight: 15
|
||||
# 10% chance of low-value treasure or maintenance tools
|
||||
- !type:GroupSelector
|
||||
weight: 10
|
||||
children:
|
||||
- !type:NestedSelector
|
||||
tableId: SalvageTreasureCommon
|
||||
- !type:NestedSelector
|
||||
tableId: MaintToolsTable
|
||||
|
||||
# All types of the rare and unique fishes
|
||||
- type: entityTable
|
||||
id: RareFishTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
- id: FishAlien
|
||||
- id: FishBingle
|
||||
- id: FishBoyFisher
|
||||
- id: FishNukeDisk
|
||||
- id: FishGib
|
||||
- id: FishMutant
|
||||
- id: FishNuker
|
||||
- id: FishToxic
|
||||
- id: FishIan
|
||||
|
||||
# Fish from water
|
||||
- type: entityTable
|
||||
id: WaterFishTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
- id: FishAngler
|
||||
- id: FishBass
|
||||
- id: FishBlob
|
||||
- id: FishBlueFintuna
|
||||
- id: FishCat
|
||||
- id: FishTropicalClown
|
||||
- id: FishPuffer
|
||||
- id: FishSilver
|
||||
- id: FishTropicalSun
|
||||
- id: FishTropical
|
||||
|
||||
# Items from Water
|
||||
- type: entityTable
|
||||
id: WaterFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
- !type:NestedSelector
|
||||
tableId: BasicFishingLootTable
|
||||
weight: 70
|
||||
- !type:NestedSelector
|
||||
tableId: WaterFishTable
|
||||
weight: 20
|
||||
- !type:NestedSelector
|
||||
tableId: RareFishTable
|
||||
weight: 10
|
||||
|
||||
# Items from Lava
|
||||
- type: entityTable
|
||||
id: LavaFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
# TODO: lava/plasma fish
|
||||
- !type:NestedSelector
|
||||
tableId: BasicFishingLootTable
|
||||
|
||||
# Items from Plasma
|
||||
- type: entityTable
|
||||
id: PlasmaFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
# TODO: lava/plasma fish
|
||||
- !type:NestedSelector
|
||||
tableId: BasicFishingLootTable
|
||||
|
||||
# Items from Toilets
|
||||
- type: entityTable
|
||||
id: ToiletFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
# like water but no rare fishes
|
||||
- !type:NestedSelector
|
||||
tableId: BasicFishingLootTable
|
||||
weight: 80
|
||||
- !type:NestedSelector
|
||||
tableId: WaterFishTable
|
||||
weight: 20
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# SPDX-FileCopyrightText: 2025 Aidenkrz <aiden@djkraz.com>
|
||||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# Basic rod
|
||||
- type: entity
|
||||
id: FishingRod
|
||||
parent: BaseItem
|
||||
name: fishing rod
|
||||
description: It's time to go fishing!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_rod.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_rod.rsi
|
||||
size: Normal
|
||||
- type: UseDelay # Just for visuals and to prevent autoclickers
|
||||
delay: 0.08 # 12,5 CPS at max
|
||||
- type: MeleeWeapon
|
||||
wideAnimationRotation: 45
|
||||
attackRate: 1.0
|
||||
damage:
|
||||
types:
|
||||
Piercing: 4
|
||||
- type: FishingRod
|
||||
|
||||
# Variation of a normal rod
|
||||
- type: entity
|
||||
id: FishingRodGoon
|
||||
suffix: Goon
|
||||
parent: FishingRod
|
||||
name: fishing rod
|
||||
description: It's time to go fishing!
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/goon_rod.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/goon_rod.rsi
|
||||
size: Normal
|
||||
|
||||
# Makeshift rod
|
||||
- type: entity
|
||||
id: FishingRodMakeshift
|
||||
parent: FishingRod
|
||||
name: makeshift fishing rod
|
||||
description: Probably would be hard to catch a fish using that.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/makeshift_rod.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/makeshift_rod.rsi
|
||||
size: Normal
|
||||
- type: FishingRod
|
||||
floatPrototype: FishingLureMakeshift
|
||||
efficiency: 0.8
|
||||
- type: Construction
|
||||
graph: FishingRodMakeshift
|
||||
node: makeshiftRod
|
||||
|
||||
# Golden rod
|
||||
- type: entity
|
||||
id: FishingRodGolden
|
||||
parent: FishingRod
|
||||
name: golden fishing rod
|
||||
description: Finally, you caught 250 fishes. Here's your trophey.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/golden_rod.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/golden_rod.rsi
|
||||
size: Normal
|
||||
- type: FishingRod
|
||||
efficiency: 1.3
|
||||
- type: MeleeWeapon
|
||||
wideAnimationRotation: 45
|
||||
attackRate: 1.0
|
||||
damage:
|
||||
types:
|
||||
Piercing: 10
|
||||
|
||||
# Normal fishing lure
|
||||
- type: entity
|
||||
id: FishingLure
|
||||
parent: BaseItem
|
||||
name: fishing lure
|
||||
description: fish come here
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
noRot: true
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_lure.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/fishing_lure.rsi
|
||||
size: Normal
|
||||
- type: FishingLure
|
||||
|
||||
- type: entity
|
||||
id: FishingLureMakeshift
|
||||
parent: FishingLure
|
||||
name: fishing lure
|
||||
description: fish come here
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
noRot: true
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/makeshift_lure.rsi
|
||||
state: icon
|
||||
- type: Item
|
||||
sprite: _Goobstation/Objects/Specific/Fishing/makeshift_lure.rsi
|
||||
size: Normal
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: constructionGraph
|
||||
id: FishingRodMakeshift
|
||||
start: start
|
||||
graph:
|
||||
- node: start
|
||||
edges:
|
||||
- to: makeshiftRod
|
||||
steps:
|
||||
- material: Cloth
|
||||
amount: 3
|
||||
doAfter: 2
|
||||
- material: WoodPlank
|
||||
amount: 5
|
||||
doAfter: 2
|
||||
- node: makeshiftRod
|
||||
entity: FishingRodMakeshift
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
|
||||
# SPDX-FileCopyrightText: 2025 Roudenn <romabond091@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
- type: construction
|
||||
id: FishingRodMakeshift
|
||||
graph: FishingRodMakeshift
|
||||
startNode: start
|
||||
targetNode: makeshiftRod
|
||||
category: construction-category-misc
|
||||
objectType: Item
|
||||
|
After Width: | Height: | Size: 191 B |
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 8,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "https://github.com/tgstation/tgstation/blob/886ca0f8dddf83ecaf10c92ff106172722352192/icons/effects/progessbar.dmi, resized and edited by rouden_ (discord)",
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 676 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 783 B |
|
After Width: | Height: | Size: 813 B |
|
After Width: | Height: | Size: 473 B |
|
After Width: | Height: | Size: 660 B |
|
After Width: | Height: | Size: 638 B |
|
After Width: | Height: | Size: 687 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 511 B |
|
After Width: | Height: | Size: 575 B |
|
After Width: | Height: | Size: 573 B |
|
|
@ -0,0 +1,95 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "bingle, boy_fisher, gib, disk, nuker, pufferfish by arraydeess (discord), clown, tropical, mutant, alien, bluefintuna, blob, toxic by imasleeping (discord), ian fish by latibulate_ (discord)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "alien"
|
||||
},
|
||||
{
|
||||
"name": "angler"
|
||||
},
|
||||
{
|
||||
"name": "bass"
|
||||
},
|
||||
{
|
||||
"name": "bingle"
|
||||
},
|
||||
{
|
||||
"name": "blob"
|
||||
},
|
||||
{
|
||||
"name": "blue_fintuna"
|
||||
},
|
||||
{
|
||||
"name": "boy_fisher",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "catfish"
|
||||
},
|
||||
{
|
||||
"name": "clown"
|
||||
},
|
||||
{
|
||||
"name": "disk",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "gib",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mutant"
|
||||
},
|
||||
{
|
||||
"name": "nuker"
|
||||
},
|
||||
{
|
||||
"name": "pufferfish"
|
||||
},
|
||||
{
|
||||
"name": "silverfish"
|
||||
},
|
||||
{
|
||||
"name": "sun_tropical"
|
||||
},
|
||||
{
|
||||
"name": "toxic"
|
||||
},
|
||||
{
|
||||
"name": "tropical"
|
||||
},
|
||||
{
|
||||
"name": "ian"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 845 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 217 B |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Sprites by arraydeess (discord)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "rope"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 135 B |
|
After Width: | Height: | Size: 736 B |
|
After Width: | Height: | Size: 623 B |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 429 B |
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 435 B |
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Goonstation, taken at commit 39ddf6bbd54c9f27fe49f5cea1a3119738b09596",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "icon-active"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "water by Rouge2t7 on Discord",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "water",
|
||||
"delays": [
|
||||
[
|
||||
0.25,
|
||||
0.25,
|
||||
0.25
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 463 B |
|
After Width: | Height: | Size: 243 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 317 B |
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Goonstation, taken at commit 39ddf6bbd54c9f27fe49f5cea1a3119738b09596, modified by rouden_ (discord)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "icon-active"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 559 B |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 429 B |
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 435 B |
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Goonstation, taken at commit 39ddf6bbd54c9f27fe49f5cea1a3119738b09596",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "icon-active"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 218 B |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Sprites by arraydeess (discord)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "rope"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 430 B |
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 240 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 241 B |
|
After Width: | Height: | Size: 309 B |
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-NC-SA-3.0",
|
||||
"copyright": "Sprites by arraydeess (discord) and rouden_ (discord)",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-left-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right-active",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "icon-active"
|
||||
}
|
||||
]
|
||||
}
|
||||