This commit is contained in:
Deserty0
2025-10-29 23:06:20 +10:00
parent 2da1977ae1
commit 29cbf0560f
10 changed files with 289 additions and 124 deletions

View File

@@ -66,7 +66,7 @@ public sealed partial class CP14FishingWindow : BaseWindow
return;
var floatBox = CalculateUIBox(_fishingMinigame.Float, rodComponent.FloatPosition);
var fishBox = CalculateUIBox(_fishingMinigame.FishIcon, fishComponent.FishPosAndDestination.X);
var fishBox = CalculateUIBox(_fishingMinigame.FishIcon, fishComponent.Position);
handle.DrawTextureRect(_floatTexture, floatBox);
handle.DrawTextureRect(_fishTexture, fishBox);

View File

@@ -1,5 +1,131 @@
using System.Linq;
using System.Numerics;
using Content.Shared._CP14.Fishing;
using Content.Shared._CP14.Fishing.Components;
using Content.Shared.EntityTable;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._CP14.Fishing;
public sealed class CP14FishingSystem : CP14SharedFishingSystem;
public sealed class CP14FishingSystem : CP14SharedFishingSystem
{
[Dependency] private readonly EntityTableSystem _entityTable = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly MapSystem _map = default!;
[Dependency] private readonly PvsOverrideSystem _pvs= default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private MapId? _mapId;
private EntityQuery<CP14FishingPondComponent> _pondQuery;
private EntityQuery<CP14FishComponent> _fishQuery;
public override void Initialize()
{
base.Initialize();
_pondQuery = GetEntityQuery<CP14FishingPondComponent>();
_fishQuery = GetEntityQuery<CP14FishComponent>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
base.Update(frameTime);
var curTime = _gameTiming.CurTime;
var query = EntityQueryEnumerator<CP14FishingRodComponent>();
// Seeding prediction doesnt work
while (query.MoveNext(out var uid, out var fishRod))
{
if (fishRod.User is null)
continue;
if (fishRod.FishingFloat is null)
continue;
if (fishRod.Target is null)
continue;
var fish = fishRod.CaughtFish;
if (fishRod.CaughtFish is not null &&
_fishQuery.TryComp(fish, out var fishComp)) //TODO: remove multiple fish TryComp in next functions
continue;
TryToCatchFish((uid, fishRod), curTime);
}
}
/// <summary>
/// Tries to cath fish
/// </summary>
private bool TryToCatchFish(Entity<CP14FishingRodComponent> rod, TimeSpan curTime)
{
if (rod.Comp.CaughtFish is not null)
return false;
if (curTime < rod.Comp.FishingTime)
return false;
var pond = rod.Comp.Target;
if (!_pondQuery.TryComp(pond, out var pondComp))
return false;
if (pondComp.LootTable is null)
return false;
if (_proto.TryIndex(pondComp.LootTable, out var lootTable))
return false;
if (lootTable is null)
return false;
var fishes = _entityTable.GetSpawns(lootTable, _random.GetRandom());
var fishId = fishes.First();
EnsurePausedMap();
var fish = PredictedSpawnAtPosition(fishId, new EntityCoordinates(_map.GetMap(_mapId!.Value), Vector2.Zero));
if (!_player.TryGetSessionByEntity(rod.Comp.User!.Value, out var session))
return false;
if (!_fishQuery.TryComp(fish, out var fishComp))
return false;
_pvs.AddSessionOverride(fish, session);
rod.Comp.CaughtFish = fish;
fishComp.GetAwayTime = curTime;
fishComp.GetAwayTime += TimeSpan.FromSeconds(_random.NextDouble(rod.Comp.MinAwaitTime, rod.Comp.MaxAwaitTime));
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.CaughtFish));
DirtyField(fish, fishComp, nameof(CP14FishComponent.GetAwayTime));
return true;
}
/// <summary>
/// Ensures that paused map exists
/// </summary>
private void EnsurePausedMap()
{
if (_map.MapExists(_mapId))
return;
var mapUid = _map.CreateMap(out var newMapId);
_meta.SetEntityName(mapUid, Loc.GetString("fishing-paused-map-name"));
_mapId = newMapId;
_map.SetPaused(mapUid, true);
}
}

View File

@@ -10,15 +10,16 @@ public abstract partial class CP14FishBaseBehavior
/// <summary>
/// Calculates fish position
/// </summary>
/// <param name="random"> Robust random </param>
/// <param name="fishCords"> Current fish coordinates </param>
/// <returns> Calculated fish coordinates </returns>
public Vector2 TryCalculatePosition(IRobustRandom random, Vector2 fishCords)
/// <param name="random"> Robust random interface </param>
/// <param name="fishPos"> Current position of fish </param>
/// <param name="fishDest"> Fish destination </param>
/// <returns> Calculated fish position </returns>
public float TryCalculatePosition(IRobustRandom random, float fishPos, float fishDest)
{
var speed = CalculateSpeed(random);
var nextPos = float.Lerp(fishCords.X, fishCords.Y, speed);
var nextPos = float.Lerp(fishPos, fishDest, speed);
return new Vector2(nextPos, fishCords.Y);
return nextPos;
}
/// <summary>
@@ -26,12 +27,21 @@ public abstract partial class CP14FishBaseBehavior
/// </summary>
public abstract float CalculateSpeed(IRobustRandom random);
/// <summary>
/// Speed of a fish
/// </summary>
[DataField]
public float Speed = 0.25f;
/// <summary>
/// Salt of speed calculations
/// </summary>
[DataField]
public float Difficulty = 2f;
/// <summary>
/// Base time which fish will wait in destination before selecting new
/// </summary>
[DataField]
public TimeSpan BaseWaitTime = TimeSpan.FromSeconds(5);
}

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Utility;
namespace Content.Shared._CP14.Fishing;
/// <summary>
/// Prototype of fishing minigame stylesheet.
/// Prototype of fishing minigame. Starter position of minigame in the bottom
/// </summary>
[Prototype("CP14FishingMinigameStyle")]
public sealed class CP14FishingMinigamePrototype : IPrototype
@@ -13,18 +13,33 @@ public sealed class CP14FishingMinigamePrototype : IPrototype
[IdDataField]
public string ID { get; } = default!;
/// <summary>
/// Fishing minigame background data
/// </summary>
[DataField(required: true)]
public FishingMinigameElementData Background;
/// <summary>
/// Fishing minigame fish icon data
/// </summary>
[DataField(required: true)]
public FishingMinigameElementData FishIcon;
/// <summary>
/// Fishing minigame progressbar data
/// </summary>
[DataField(required: true)]
public FishingMinigameElementData Progressbar;
/// <summary>
/// Fishing minigame float data
/// </summary>
[DataField(required: true)]
public FishingMinigameElementData Float;
/// <summary>
/// Size of the area where the float and fish will move
/// </summary>
[DataField(required: true)]
public float FishingMinigameSize;
}
@@ -33,17 +48,17 @@ public sealed class CP14FishingMinigamePrototype : IPrototype
public partial struct FishingMinigameElementData
{
/// <summary>
/// Texture path.
/// Texture path
/// </summary>
[DataField(required: true)] public ResPath Texture;
/// <summary>
/// Size of a texture.
/// Size of a texture
/// </summary>
[DataField(required: true)] public Vector2 Size;
/// <summary>
/// Offset from bottom left corner. Starter position in the bottom.
/// Offset from bottom left corner
/// </summary>
[DataField(required: true)] public Vector2 Offset;
}

View File

@@ -1,6 +1,6 @@
using Robust.Shared.Serialization;
namespace Content.Shared._CP14.Fishing.Components;
namespace Content.Shared._CP14.Fishing;
/// <summary>
/// Key for CP14FishingBoundUserInterface

View File

@@ -18,32 +18,22 @@ namespace Content.Shared._CP14.Fishing;
public abstract class CP14SharedFishingSystem : EntitySystem
{
[Dependency] private readonly EntityTableSystem _entityTable = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPvsOverrideSystem _pvs= default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly SharedUserInterfaceSystem _userInterface = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _net = default!;
private MapId? _mapId;
private EntityQuery<CP14FishComponent> _fishQuery;
private EntityQuery<CP14FishingPondComponent> _pondQuery;
private EntityQuery<CP14FishComponent> _fishQuery;
public override void Initialize()
{
base.Initialize();
_fishQuery = GetEntityQuery<CP14FishComponent>();
_pondQuery = GetEntityQuery<CP14FishingPondComponent>();
SubscribeLocalEvent<CP14FishingRodComponent, AfterInteractEvent>(OnInteract);
SubscribeLocalEvent<CP14FishingRodComponent, DroppedEvent>(OnDropEvent);
@@ -60,7 +50,6 @@ public abstract class CP14SharedFishingSystem : EntitySystem
// Seeding prediction doesnt work
while (query.MoveNext(out var uid, out var fishRod))
{
_random.SetSeed((int)_gameTiming.CurTick.Value + GetNetEntity(uid).Id);
if (fishRod.User is null)
continue;
@@ -80,7 +69,8 @@ public abstract class CP14SharedFishingSystem : EntitySystem
if (fishRod.CaughtFish is not null && _fishQuery.TryComp(fish, out var fishComp)) //TODO: remove multiple fish TryComp in next functions
continue;
TryToCatchFish((uid, fishRod), curTime);
_random.SetSeed((int)_gameTiming.CurTick.Value + GetNetEntity(uid).Id);
UpdateFishWaitingStatus((uid, fishRod), curTime);
UpdatePositions((uid, fishRod), curTime);
}
@@ -88,8 +78,8 @@ public abstract class CP14SharedFishingSystem : EntitySystem
/// <summary>
/// Handles float and fish positions updates
/// Please burn it down
/// </summary>
/// <remarks> Please burn it down </remarks>
private void UpdatePositions(Entity<CP14FishingRodComponent> rod, TimeSpan curTime)
{
if (rod.Comp.CaughtFish is null)
@@ -121,34 +111,34 @@ public abstract class CP14SharedFishingSystem : EntitySystem
Math.Clamp(floatPosition - floatSpeed, 0, maxCord);
}
var fishPos = fishComp.FishPosAndDestination.X;
var fishDest = fishComp.FishPosAndDestination.Y;
var fishBaseWaitTime = fishComp.FishBehavior.BaseWaitTime;
var fishPos = fishComp.Position;
var fishDest = fishComp.Destination;
var fishBaseWaitTime = fishComp.Behavior.BaseWaitTime;
if (Math.Abs(fishPos - fishDest) < 0.1f)
{
UpdateFishDestination((fish.Value, fishComp), curTime, maxCord);
fishComp.FishSelectPosTime = curTime + fishBaseWaitTime + fishBaseWaitTime * 0.2 * _random.NextFloat(-1, 1);
fishComp.SelectPosTime = curTime + fishBaseWaitTime + fishBaseWaitTime * 0.2 * _random.NextFloat(-1, 1);
}
else
{
fishComp.FishPosAndDestination = fishComp.FishBehavior.TryCalculatePosition(_random, fishComp.FishPosAndDestination);
fishComp.Position = fishComp.Behavior.TryCalculatePosition(_random, fishComp.Position, fishComp.Destination);
}
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.FloatPosition));
DirtyField(fish.Value, fishComp, nameof(CP14FishComponent.FishPosAndDestination));
DirtyField(fish.Value, fishComp, nameof(CP14FishComponent.Position));
}
/// <summary>
/// Handles updating fish destination
/// Calculates new fish destination
/// </summary>
private void UpdateFishDestination(Entity<CP14FishComponent> fish, TimeSpan curTime, float maxCord)
{
if (curTime < fish.Comp.FishSelectPosTime)
if (curTime < fish.Comp.SelectPosTime)
return;
fish.Comp.FishPosAndDestination.X = _random.NextFloat(0, maxCord);
DirtyField(fish, fish.Comp, nameof(CP14FishComponent.FishPosAndDestination));
fish.Comp.Destination = _random.NextFloat(0, maxCord);
DirtyField(fish, fish.Comp, nameof(CP14FishComponent.Destination));
}
/// <summary>
@@ -179,7 +169,7 @@ public abstract class CP14SharedFishingSystem : EntitySystem
return;
}
if (curTime < fishComp.FishGetAwayTime)
if (curTime < fishComp.GetAwayTime)
return;
rod.Comp.CaughtFish = null;
@@ -187,56 +177,6 @@ public abstract class CP14SharedFishingSystem : EntitySystem
PredictedDel(fish);
}
/// <summary>
/// Handles fish catching
/// </summary>
private bool TryToCatchFish(Entity<CP14FishingRodComponent> rod, TimeSpan curTime)
{
if (!_net.IsServer)
return false;
if (rod.Comp.CaughtFish is not null)
return false;
if (curTime < rod.Comp.FishingTime)
return false;
var pond = rod.Comp.Target;
if (!_pondQuery.TryComp(pond, out var pondComp))
return false;
if (pondComp.LootTable is null)
return false;
if (_proto.TryIndex(pondComp.LootTable, out var lootTable))
return false;
if (lootTable is null)
return false;
var fishes = _entityTable.GetSpawns(lootTable, _random.GetRandom());
var fishId = fishes.First();
EnsurePausedMap();
var fish = PredictedSpawnAtPosition(fishId, new EntityCoordinates(_map.GetMap(_mapId!.Value), Vector2.Zero));
if (!_player.TryGetSessionByEntity(rod.Comp.User!.Value, out var session))
return false;
if (!_fishQuery.TryComp(fish, out var fishComp))
return false;
_pvs.AddSessionOverride(fish, session);
rod.Comp.CaughtFish = fish;
fishComp.FishGetAwayTime = curTime;
fishComp.FishGetAwayTime += TimeSpan.FromSeconds(_random.NextDouble(rod.Comp.MinAwaitTime, rod.Comp.MaxAwaitTime));
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.CaughtFish));
DirtyField(fish, fishComp, nameof(CP14FishComponent.FishGetAwayTime));
return true;
}
/// <summary>
/// Validates if user is still in range of fishing float
/// </summary>
@@ -266,6 +206,9 @@ public abstract class CP14SharedFishingSystem : EntitySystem
nameof(CP14FishingRodComponent.FishHooked));
}
/// <summary>
/// Sets <see cref="CP14FishingRodComponent.Reeling"/> to user button status
/// </summary>
private void OnReelingMessage(CP14FishingReelKeyMessage msg, EntitySessionEventArgs args)
{
if (args.SenderSession.AttachedEntity is not { } player)
@@ -279,6 +222,9 @@ public abstract class CP14SharedFishingSystem : EntitySystem
DirtyField(activeItem.Value, fishingRodComponent, nameof(CP14FishingRodComponent.Reeling));
}
/// <summary>
/// Starts new fishing process when user interacts with pond using fishing rod
/// </summary>
private void OnInteract(Entity<CP14FishingRodComponent> rod, ref AfterInteractEvent args)
{
if (args.Handled)
@@ -307,6 +253,9 @@ public abstract class CP14SharedFishingSystem : EntitySystem
ThrowFishingFloat(rod, args.Target.Value);
}
/// <summary>
/// Deletes <see cref="CP14FishingRodComponent.User"/> link
/// </summary>
private void OnDropEvent(Entity<CP14FishingRodComponent> rod, ref DroppedEvent args)
{
rod.Comp.User = null;
@@ -333,18 +282,4 @@ public abstract class CP14SharedFishingSystem : EntitySystem
_throwing.TryThrow(fishingFloat, targetCoords, rod.Comp.ThrowPower, recoil: false, doSpin: false);
}
private void EnsurePausedMap()
{
if (!_net.IsServer)
return;
if (_map.MapExists(_mapId))
return;
var mapUid = _map.CreateMap(out var newMapId);
_meta.SetEntityName(mapUid, Loc.GetString("fishing-paused-map-name"));
_mapId = newMapId;
_map.SetPaused(mapUid, true);
}
}

View File

@@ -4,24 +4,51 @@ using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Fishing.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
/// <summary>
/// Component for fish, that can be caught via fishing
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause, Access(typeof(CP14SharedFishingSystem))]
public sealed partial class CP14FishComponent : Component
{
/// <summary>
/// Fish behaviour that will be used for speed calculations
/// </summary>
[DataField(required: true), ViewVariables]
public CP14FishBaseBehavior FishBehavior = default!;
public CP14FishBaseBehavior Behavior;
/// <summary>
/// Time when fish will select next position
/// </summary>
[AutoNetworkedField, AutoPausedField, ViewVariables]
public TimeSpan FishSelectPosTime = TimeSpan.Zero;
public TimeSpan SelectPosTime = TimeSpan.Zero;
/// <summary>
/// Time when the fish will get away if it is not hooked
/// </summary>
[AutoNetworkedField, AutoPausedField, ViewVariables]
public TimeSpan FishGetAwayTime = TimeSpan.Zero;
public TimeSpan GetAwayTime = TimeSpan.Zero;
/// <summary>
/// Fish current position in minigame coordinates
/// </summary>
[AutoNetworkedField, ViewVariables]
public Vector2 FishPosAndDestination;
public float Position;
/// <summary>
/// Fish destination in minigame coordinates
/// </summary>
[AutoNetworkedField, ViewVariables]
public float Destination;
/// <summary>
/// Minimal time before fish will get away
/// </summary>
[DataField]
public double MinAwaitTime = 5;
/// <summary>
/// Maximum time before fish will get away
/// </summary>
[DataField]
public double MaxAwaitTime = 10;
}

View File

@@ -1,12 +1,18 @@
using System.Runtime.Serialization;
using Content.Shared.EntityTable;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Fishing.Components;
[RegisterComponent]
/// <summary>
/// Component for fishing ponds
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(CP14SharedFishingSystem))]
public sealed partial class CP14FishingPondComponent : Component
{
/// <summary>
/// LootTable of loot that can be caught in this pond. Only first spawn will be caught
/// </summary>
[DataField]
public ProtoId<EntityTablePrototype>? LootTable;
}

View File

@@ -6,53 +6,100 @@ namespace Content.Shared._CP14.Fishing.Components;
/// <summary>
/// Allows to fish with this item
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true, true), AutoGenerateComponentPause]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true, true), AutoGenerateComponentPause, Access(typeof(CP14SharedFishingSystem))]
public sealed partial class CP14FishingRodComponent : Component
{
// Vars
/// <summary>
/// Link to a fishing float, attached to rod
/// </summary>
[AutoNetworkedField, ViewVariables]
public EntityUid? FishingFloat;
/// <summary>
/// Link to fishing rod user
/// </summary>
[AutoNetworkedField, ViewVariables]
public EntityUid? User;
[AutoNetworkedField, ViewVariables]
public float FloatPosition = 0f;
[AutoNetworkedField, AutoPausedField, ViewVariables]
public TimeSpan FishingTime = TimeSpan.Zero;
/// <summary>
/// Link to caught fish
/// </summary>
[AutoNetworkedField, ViewVariables]
public EntityUid? CaughtFish;
[AutoNetworkedField, ViewVariables]
public bool Reeling;
[AutoNetworkedField, ViewVariables]
public bool FishHooked;
/// <summary>
/// Link to a target fishing pond
/// </summary>
[AutoNetworkedField, ViewVariables]
public EntityUid? Target;
/// <summary>
/// Float position in minigame coordinates
/// </summary>
[AutoNetworkedField, ViewVariables]
public float FloatPosition = 0f;
/// <summary>
/// Time when fish will be caught
/// </summary>
[AutoNetworkedField, AutoPausedField, ViewVariables]
public TimeSpan FishingTime = TimeSpan.Zero;
/// <summary>
/// Does the user pull the fishing line
/// </summary>
[AutoNetworkedField, ViewVariables]
public bool Reeling;
/// <summary>
/// Is fish hooked
/// </summary>
[AutoNetworkedField, ViewVariables]
public bool FishHooked;
// Data definitions
/// <summary>
/// Fishing float prototype
/// </summary>
[DataField]
public EntProtoId FloatPrototype = "CP14DefaultFishingFloat";
/// <summary>
/// Fishing minigame prototype
/// </summary>
[DataField]
public ProtoId<CP14FishingMinigamePrototype> FishingMinigame = "Default";
/// <summary>
/// Speed of a float in minigame coordinates
/// </summary>
[DataField]
public float FloatSpeed = 2f;
/// <summary>
/// Max distance between rod and float
/// </summary>
[DataField]
public float MaxFishingDistance = 5f;
/// <summary>
/// Power with which float will be thrown
/// </summary>
[DataField]
public float ThrowPower = 10f;
/// <summary>
/// Minimal time before fish will be caught
/// </summary>
[DataField]
public double MinAwaitTime = 5;
/// <summary>
/// Maximum time before fish will be caught
/// </summary>
[DataField]
public double MaxAwaitTime = 20;
}

View File

@@ -54,8 +54,7 @@
id: CP14DefaultFishingFloat
name: Fishing float
description: Little floating ball.
suffix: DO NOT MAP
categories: [ ForkFiltered, DoNotMap]
categories: [ HideSpawnMenu ]
components:
- type: Sprite
sprite: _CP14/Objects/Tools/float.rsi