Compare commits
28 Commits
master
...
des-fishin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb169a18a | ||
|
|
8564d878c3 | ||
|
|
29cbf0560f | ||
|
|
2da1977ae1 | ||
|
|
f53c68d272 | ||
|
|
44b757a033 | ||
|
|
a1e452de63 | ||
|
|
f31e82f7b3 | ||
|
|
2fdd3923f5 | ||
|
|
88575e2286 | ||
|
|
636b2a5514 | ||
|
|
90797f88b6 | ||
|
|
122193bc7b | ||
|
|
0c7dd03f79 | ||
|
|
59d776ca72 | ||
|
|
782ae7c588 | ||
|
|
aa16b587fc | ||
|
|
4de7747fb9 | ||
|
|
45f4668b11 | ||
|
|
5fbb8789df | ||
|
|
c8b0560c7a | ||
|
|
5d2fb2c4bc | ||
|
|
3759f240b3 | ||
|
|
af2e62ff59 | ||
|
|
307c0dc01b | ||
|
|
0c44159c49 | ||
|
|
dcecaded17 | ||
|
|
310126c660 |
@@ -318,6 +318,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(CP14ContentKeyFunctions.CP14OpenSkillMenu);
|
||||
AddButton(CP14ContentKeyFunctions.OpenBelt2);
|
||||
AddButton(CP14ContentKeyFunctions.SmartEquipBelt2);
|
||||
AddButton(CP14ContentKeyFunctions.CP14FishingAction);
|
||||
//CP14 end
|
||||
|
||||
foreach (var control in _keyControls.Values)
|
||||
|
||||
86
Content.Client/_CP14/Fishing/CP14FishingSystem.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared._CP14.Fishing;
|
||||
using Content.Shared._CP14.Fishing.Components;
|
||||
using Content.Shared._CP14.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client._CP14.Fishing;
|
||||
|
||||
public sealed class CP14FishingSystem : CP14SharedFishingSystem
|
||||
{
|
||||
[Dependency] private readonly InputSystem _input = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14FishingRodComponent, AfterAutoHandleStateEvent>(OnFishingRodState);
|
||||
}
|
||||
|
||||
public override void Update(float dt)
|
||||
{
|
||||
base.Update(dt);
|
||||
|
||||
var heldUid = _hands.GetActiveHandEntity();
|
||||
|
||||
if (!TryComp<CP14FishingRodComponent>(heldUid, out var fishingRodComponent))
|
||||
return;
|
||||
|
||||
UpdatePressedButtons(fishingRodComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles BUI updates
|
||||
/// </summary>
|
||||
private void OnFishingRodState(Entity<CP14FishingRodComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_userInterface.TryGetOpenUi(entity.Owner, CP14FishingUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles user inputs
|
||||
/// </summary>
|
||||
private void UpdatePressedButtons(CP14FishingRodComponent fishingRodComponent)
|
||||
{
|
||||
if (fishingRodComponent.CaughtFish is null)
|
||||
return;
|
||||
|
||||
var reelKey = _input.CmdStates.GetState(CP14ContentKeyFunctions.CP14FishingAction) == BoundKeyState.Down;
|
||||
|
||||
if (fishingRodComponent.Reeling == reelKey)
|
||||
return;
|
||||
|
||||
fishingRodComponent.Reeling = reelKey;
|
||||
RaiseNetworkEvent(new CP14FishingReelKeyMessage(reelKey));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to get fish and rod component by FishingUI
|
||||
/// </summary>
|
||||
/// <returns>True when all info can be resolved</returns>
|
||||
public bool GetInfo([NotNullWhen(true)] out CP14FishingRodComponent? rodComponent,
|
||||
[NotNullWhen(true)] out CP14FishComponent? fishComponent)
|
||||
{
|
||||
rodComponent = null;
|
||||
fishComponent = null;
|
||||
|
||||
var heldUid = _hands.GetActiveHandEntity();
|
||||
|
||||
if (!TryComp<CP14FishingRodComponent>(heldUid, out var posRodComponent))
|
||||
return false;
|
||||
|
||||
if (!TryComp<CP14FishComponent>(posRodComponent.CaughtFish, out var posFishComponent))
|
||||
return false;
|
||||
|
||||
rodComponent = posRodComponent;
|
||||
fishComponent = posFishComponent;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Shared._CP14.Fishing.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._CP14.Fishing.UI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CP14FishingBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private CP14FishingWindow? _fishingWindow;
|
||||
|
||||
public CP14FishingBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if (!EntMan.TryGetComponent<CP14FishingRodComponent>(Owner, out var rodComponent))
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.Resolve(rodComponent.FishingMinigame, out var fishingMinigame))
|
||||
return;
|
||||
|
||||
_fishingWindow = this.CreateWindow<CP14FishingWindow>();
|
||||
_fishingWindow.InitVisuals(fishingMinigame);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
_fishingWindow?.UpdateDraw();
|
||||
}
|
||||
}
|
||||
7
Content.Client/_CP14/Fishing/UI/CP14FishingWindow.xaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- Empty fishing popup -->
|
||||
<fishingWindow:CP14FishingWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:fishingWindow="clr-namespace:Content.Client._CP14.Fishing.UI"
|
||||
Name="FishingWindow" MaxSize="0 0">
|
||||
<PanelContainer Name="FishingBackground"></PanelContainer>
|
||||
</fishingWindow:CP14FishingWindow>
|
||||
85
Content.Client/_CP14/Fishing/UI/CP14FishingWindow.xaml.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared._CP14.Fishing;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._CP14.Fishing.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14FishingWindow : BaseWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
private readonly CP14FishingSystem _fishing;
|
||||
|
||||
private CP14FishingMinigamePrototype? _fishingMinigame;
|
||||
private Texture? _floatTexture;
|
||||
private Texture? _fishTexture;
|
||||
|
||||
public CP14FishingWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_fishing = _entity.System<CP14FishingSystem>();
|
||||
}
|
||||
|
||||
public void InitVisuals(CP14FishingMinigamePrototype fishingMinigame)
|
||||
{
|
||||
FishingWindow.Visible = false;
|
||||
|
||||
// Hashing
|
||||
_fishingMinigame = fishingMinigame;
|
||||
|
||||
// Getting data
|
||||
var background = _fishingMinigame.Background;
|
||||
var fish = _fishingMinigame.FishIcon;
|
||||
var floater = _fishingMinigame.Float;
|
||||
|
||||
FishingWindow.MaxSize = background.Size;
|
||||
FishingWindow.SetSize = background.Size;
|
||||
|
||||
FishingBackground.SetSize = background.Size;
|
||||
|
||||
var backgroundTexture = _resourceCache.GetTexture(background.Texture);
|
||||
FishingBackground.PanelOverride = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundTexture,
|
||||
};
|
||||
|
||||
_floatTexture = _resourceCache.GetTexture(floater.Texture);
|
||||
_fishTexture = _resourceCache.GetTexture(fish.Texture);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_fishingMinigame is null || _floatTexture is null || _fishTexture is null)
|
||||
return;
|
||||
|
||||
if (!_fishing.GetInfo(out var rodComponent, out var fishComponent))
|
||||
return;
|
||||
|
||||
var floatBox = CalculateUIBox(_fishingMinigame.Float, rodComponent.FloatPosition);
|
||||
var fishBox = CalculateUIBox(_fishingMinigame.FishIcon, fishComponent.Position);
|
||||
|
||||
handle.DrawTextureRect(_floatTexture, floatBox);
|
||||
handle.DrawTextureRect(_fishTexture, fishBox);
|
||||
}
|
||||
|
||||
private static UIBox2 CalculateUIBox(FishingMinigameElementData data, float verticalOffset)
|
||||
{
|
||||
var left = data.Offset.X;
|
||||
var top = data.Offset.Y + verticalOffset + data.Size.Y;
|
||||
var right = data.Offset.X + data.Size.X;
|
||||
var bottom = data.Offset.Y + verticalOffset;
|
||||
|
||||
return new UIBox2(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Content.Client._CP14.Input
|
||||
human.AddFunction(CP14ContentKeyFunctions.OpenBelt2);
|
||||
human.AddFunction(CP14ContentKeyFunctions.SmartEquipBelt2);
|
||||
human.AddFunction(CP14ContentKeyFunctions.CP14OpenSkillMenu);
|
||||
human.AddFunction(CP14ContentKeyFunctions.CP14FishingAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
Content.Server/_CP14/Fishing/CP14FishingSystem.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
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
|
||||
{
|
||||
[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))
|
||||
{
|
||||
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 (rod.Comp.User is null)
|
||||
return false;
|
||||
|
||||
if (rod.Comp.FishingFloat is null)
|
||||
return false;
|
||||
|
||||
if (rod.Comp.Target is 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.Resolve(pondComp.LootTable, out var lootTable))
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing.Behaviors;
|
||||
|
||||
[ImplicitDataDefinitionForInheritors, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public abstract partial class CP14FishBaseBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates fish position
|
||||
/// </summary>
|
||||
/// <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(fishPos, fishDest, speed);
|
||||
|
||||
return nextPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formula to calculate fish speed
|
||||
/// </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);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing.Behaviors;
|
||||
|
||||
public sealed partial class CP14FishDartBehaviour : CP14FishBaseBehavior
|
||||
{
|
||||
public override float CalculateSpeed(IRobustRandom random)
|
||||
{
|
||||
return Speed * (0.5f + random.NextFloat() * Difficulty);
|
||||
}
|
||||
}
|
||||
64
Content.Shared/_CP14/Fishing/CP14FishingMinigamePrototype.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype of fishing minigame. Starter position of minigame in the bottom
|
||||
/// </summary>
|
||||
[Prototype("CP14FishingMinigameStyle")]
|
||||
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;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public partial struct FishingMinigameElementData
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture path
|
||||
/// </summary>
|
||||
[DataField(required: true)] public ResPath Texture;
|
||||
|
||||
/// <summary>
|
||||
/// Size of a texture
|
||||
/// </summary>
|
||||
[DataField(required: true)] public Vector2 Size;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from bottom left corner
|
||||
/// </summary>
|
||||
[DataField(required: true)] public Vector2 Offset;
|
||||
}
|
||||
21
Content.Shared/_CP14/Fishing/CP14FishingSerializable.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing;
|
||||
|
||||
/// <summary>
|
||||
/// Key for CP14FishingBoundUserInterface
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public enum CP14FishingUiKey : byte
|
||||
{
|
||||
Key,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event for sending reeling key status
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class CP14FishingReelKeyMessage(bool reeling) : EntityEventArgs
|
||||
{
|
||||
public bool Reeling = reeling;
|
||||
}
|
||||
278
Content.Shared/_CP14/Fishing/CP14SharedFishingSystem.cs
Normal file
@@ -0,0 +1,278 @@
|
||||
using Content.Shared._CP14.Fishing.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing;
|
||||
|
||||
public abstract class CP14SharedFishingSystem : EntitySystem
|
||||
{
|
||||
[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 IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private EntityQuery<CP14FishComponent> _fishQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_fishQuery = GetEntityQuery<CP14FishComponent>();
|
||||
|
||||
SubscribeLocalEvent<CP14FishingRodComponent, AfterInteractEvent>(OnInteract);
|
||||
SubscribeLocalEvent<CP14FishingRodComponent, DroppedEvent>(OnDropEvent);
|
||||
SubscribeNetworkEvent<CP14FishingReelKeyMessage>(OnReelingMessage);
|
||||
}
|
||||
|
||||
public override void Update(float 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;
|
||||
|
||||
RevalidateFishing((uid, 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;
|
||||
|
||||
_random.SetSeed((int)_gameTiming.CurTick.Value + GetNetEntity(uid).Id);
|
||||
|
||||
UpdateFishWaitingStatus((uid, fishRod), curTime);
|
||||
UpdatePositions((uid, fishRod), curTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles float and fish positions updates
|
||||
/// </summary>
|
||||
/// <remarks> Please burn it down </remarks>
|
||||
private void UpdatePositions(Entity<CP14FishingRodComponent> rod, TimeSpan curTime)
|
||||
{
|
||||
if (rod.Comp.CaughtFish is null)
|
||||
return;
|
||||
|
||||
if (!rod.Comp.FishHooked)
|
||||
return;
|
||||
|
||||
var fish = rod.Comp.CaughtFish;
|
||||
|
||||
if (!_fishQuery.TryComp(fish, out var fishComp))
|
||||
return;
|
||||
|
||||
_proto.Resolve(rod.Comp.FishingMinigame, out var minigamePrototype);
|
||||
|
||||
if (minigamePrototype is null)
|
||||
return;
|
||||
|
||||
var maxCord = minigamePrototype.FishingMinigameSize;
|
||||
var floatSpeed = rod.Comp.FloatSpeed;
|
||||
var floatPosition = rod.Comp.FloatPosition;
|
||||
|
||||
if (rod.Comp.Reeling)
|
||||
{
|
||||
Math.Clamp(floatPosition + floatSpeed, 0, maxCord);
|
||||
}
|
||||
else
|
||||
{
|
||||
Math.Clamp(floatPosition - floatSpeed, 0, maxCord);
|
||||
}
|
||||
|
||||
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.SelectPosTime = curTime + fishBaseWaitTime + fishBaseWaitTime * 0.2 * _random.NextFloat(-1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
fishComp.Position = fishComp.Behavior.TryCalculatePosition(_random, fishComp.Position, fishComp.Destination);
|
||||
}
|
||||
|
||||
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.FloatPosition));
|
||||
DirtyField(fish.Value, fishComp, nameof(CP14FishComponent.Position));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates new fish destination
|
||||
/// </summary>
|
||||
private void UpdateFishDestination(Entity<CP14FishComponent> fish, TimeSpan curTime, float maxCord)
|
||||
{
|
||||
if (curTime < fish.Comp.SelectPosTime)
|
||||
return;
|
||||
|
||||
fish.Comp.Destination = _random.NextFloat(0, maxCord);
|
||||
DirtyField(fish, fish.Comp, nameof(CP14FishComponent.Destination));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles if fish got caught or flees
|
||||
/// </summary>
|
||||
private void UpdateFishWaitingStatus(Entity<CP14FishingRodComponent> rod, TimeSpan curTime)
|
||||
{
|
||||
if (rod.Comp.CaughtFish is null)
|
||||
return;
|
||||
|
||||
if (rod.Comp.FishHooked)
|
||||
return;
|
||||
|
||||
if (rod.Comp.User is null)
|
||||
return;
|
||||
|
||||
var fish = rod.Comp.CaughtFish;
|
||||
if (!_fishQuery.TryComp(fish, out var fishComp))
|
||||
return;
|
||||
|
||||
if (rod.Comp.Reeling)
|
||||
{
|
||||
rod.Comp.FishHooked = true;
|
||||
|
||||
_userInterface.TryOpenUi(rod.Owner, CP14FishingUiKey.Key, rod.Comp.User.Value);
|
||||
|
||||
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.FishHooked));
|
||||
return;
|
||||
}
|
||||
|
||||
if (curTime < fishComp.GetAwayTime)
|
||||
return;
|
||||
|
||||
rod.Comp.CaughtFish = null;
|
||||
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.CaughtFish));
|
||||
PredictedDel(fish);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates if user is still in range of fishing float
|
||||
/// </summary>
|
||||
private void RevalidateFishing(Entity<CP14FishingRodComponent> rod)
|
||||
{
|
||||
if (rod.Comp.FishingFloat is null)
|
||||
return;
|
||||
|
||||
if (_transform.InRange(rod.Owner, rod.Comp.FishingFloat.Value, rod.Comp.MaxFishingDistance * 1.5f))
|
||||
return;
|
||||
|
||||
PredictedDel(rod.Comp.FishingFloat);
|
||||
|
||||
rod.Comp.FishHooked = false;
|
||||
rod.Comp.CaughtFish = null;
|
||||
rod.Comp.FishingFloat = null;
|
||||
rod.Comp.Target = null;
|
||||
rod.Comp.User = null;
|
||||
|
||||
DirtyFields(rod,
|
||||
rod.Comp,
|
||||
null,
|
||||
nameof(CP14FishingRodComponent.FishingFloat),
|
||||
nameof(CP14FishingRodComponent.Target),
|
||||
nameof(CP14FishingRodComponent.User),
|
||||
nameof(CP14FishingRodComponent.CaughtFish),
|
||||
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)
|
||||
return;
|
||||
|
||||
if (!_hands.TryGetActiveItem(player, out var activeItem) ||
|
||||
!TryComp<CP14FishingRodComponent>(activeItem, out var fishingRodComponent))
|
||||
return;
|
||||
|
||||
fishingRodComponent.Reeling = msg.Reeling;
|
||||
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)
|
||||
return;
|
||||
|
||||
if (args.Target is not { Valid: true })
|
||||
return;
|
||||
|
||||
if (rod.Comp.FishingFloat is not null)
|
||||
return;
|
||||
|
||||
if (!TryComp<CP14FishingPondComponent>(args.Target, out _))
|
||||
return;
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(rod.Owner, args.Target.Value, rod.Comp.MaxFishingDistance))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
rod.Comp.FishingTime = _gameTiming.CurTime;
|
||||
rod.Comp.FishingTime += TimeSpan.FromSeconds(_random.NextDouble(rod.Comp.MinAwaitTime, rod.Comp.MaxAwaitTime));
|
||||
rod.Comp.User = args.User;
|
||||
|
||||
DirtyFields(rod, rod.Comp, null, nameof(CP14FishingRodComponent.FishingTime), nameof(CP14FishingRodComponent.User));
|
||||
|
||||
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;
|
||||
DirtyField(rod, rod.Comp, nameof(CP14FishingRodComponent.User));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns and throws fishing float
|
||||
/// </summary>
|
||||
private void ThrowFishingFloat(Entity<CP14FishingRodComponent> rod, EntityUid fishingPond)
|
||||
{
|
||||
var rodCoords = Transform(rod).Coordinates;
|
||||
var targetCoords = Transform(fishingPond).Coordinates;
|
||||
|
||||
var fishingFloat = PredictedSpawnAtPosition(rod.Comp.FloatPrototype, rodCoords);
|
||||
|
||||
rod.Comp.FishingFloat = fishingFloat;
|
||||
rod.Comp.Target = fishingPond;
|
||||
DirtyFields(rod,
|
||||
rod.Comp,
|
||||
null,
|
||||
nameof(CP14FishingRodComponent.FishingFloat),
|
||||
nameof(CP14FishingRodComponent.Target));
|
||||
|
||||
_throwing.TryThrow(fishingFloat, targetCoords, rod.Comp.ThrowPower, recoil: false, doSpin: false);
|
||||
}
|
||||
}
|
||||
53
Content.Shared/_CP14/Fishing/Components/CP14FishComponent.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Shared._CP14.Fishing.Behaviors;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing.Components;
|
||||
|
||||
/// <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 Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// Time when fish will select next position
|
||||
/// </summary>
|
||||
[AutoNetworkedField, AutoPausedField, ViewVariables]
|
||||
public TimeSpan SelectPosTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Time when the fish will get away if it is not hooked
|
||||
/// </summary>
|
||||
[AutoNetworkedField, AutoPausedField, ViewVariables]
|
||||
public TimeSpan GetAwayTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Fish current position in minigame coordinates
|
||||
/// </summary>
|
||||
[AutoNetworkedField, ViewVariables]
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Content.Shared.EntityTable;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing.Components;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._CP14.Fishing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Allows to fish with this item
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// Link to caught fish
|
||||
/// </summary>
|
||||
[AutoNetworkedField, ViewVariables]
|
||||
public EntityUid? CaughtFish;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
@@ -9,5 +9,6 @@ namespace Content.Shared._CP14.Input
|
||||
public static readonly BoundKeyFunction OpenBelt2 = "OpenBelt2";
|
||||
public static readonly BoundKeyFunction SmartEquipBelt2 = "SmartEquipBelt2";
|
||||
public static readonly BoundKeyFunction CP14OpenSkillMenu = "CP14OpenSkillMenu";
|
||||
public static readonly BoundKeyFunction CP14FishingAction = "CP14FishingAction";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
Resources/Locale/en-US/_CP14/fishing/fishing.ftl
Normal file
@@ -0,0 +1 @@
|
||||
fishing-paused-map-name = Fish map
|
||||
1
Resources/Locale/ru-RU/_CP14/fishing/fishing.ftl
Normal file
@@ -0,0 +1 @@
|
||||
fishing-paused-map-name = Рыбная карта
|
||||
@@ -96,4 +96,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- FootstepSound
|
||||
- CP14Mosquito
|
||||
- CP14Mosquito
|
||||
- type: CP14Fish
|
||||
fishBehavior: !type:CP14FishDartBehaviour
|
||||
@@ -0,0 +1,61 @@
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: CP14FishingRod
|
||||
name: fishing rod
|
||||
description: Wooden stick with string attached.
|
||||
categories: [ ForkFiltered ]
|
||||
components:
|
||||
- type: Item
|
||||
shape:
|
||||
- 0,0,0,1
|
||||
- type: Sprite
|
||||
sprite: _CP14/Objects/Tools/fishing_rod.rsi
|
||||
state: icon
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 50
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:CP14ModularDisassembleBehavior
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
- type: MeleeWeapon
|
||||
angle: 45
|
||||
attackRate: 1
|
||||
wideAnimationRotation: 135
|
||||
wideAnimation: CP14WeaponArcSlash
|
||||
damage:
|
||||
types:
|
||||
Blunt: 0.1
|
||||
soundHit:
|
||||
collection: MetalThud
|
||||
cPAnimationLength: 0.25
|
||||
- type: Clothing
|
||||
equipDelay: 0.25
|
||||
unequipDelay: 0.25
|
||||
quickEquip: false
|
||||
breakOnMove: false
|
||||
slots:
|
||||
- neck
|
||||
- type: CP14FishingRod
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.CP14FishingUiKey.Key:
|
||||
type: CP14FishingBoundUserInterface
|
||||
|
||||
- type: entity
|
||||
parent: BaseItem
|
||||
id: CP14DefaultFishingFloat
|
||||
name: Fishing float
|
||||
description: Little floating ball.
|
||||
categories: [ HideSpawnMenu ]
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: _CP14/Objects/Tools/float.rsi
|
||||
state: float
|
||||
@@ -71,6 +71,8 @@
|
||||
- type: TileEntityEffect
|
||||
effects:
|
||||
- !type:ExtinguishReaction
|
||||
- type: CP14FishingPond
|
||||
lootTable: CP14WaterFishingLootTable
|
||||
|
||||
- type: entity
|
||||
parent: CP14FloorWaterOptimized
|
||||
|
||||
5
Resources/Prototypes/_CP14/Fishing/loottables.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- type: entityTable
|
||||
id: CP14WaterFishingLootTable
|
||||
table: !type:GroupSelector
|
||||
children:
|
||||
- id: CP14MobMonsterFlem
|
||||
19
Resources/Prototypes/_CP14/Fishing/styles.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
- type: CP14FishingMinigameStyle
|
||||
id: Default
|
||||
background:
|
||||
texture: /Textures/_CP14/Interface/Fishing/Default/background.png
|
||||
size: 78, 298
|
||||
offset: 0, 0
|
||||
fishIcon:
|
||||
texture: /Textures/_CP14/Interface/Fishing/Default/fish_icon.png
|
||||
size: 22, 18
|
||||
offset: 26, 12
|
||||
progressbar:
|
||||
texture: /Textures/_CP14/Interface/Fishing/Default/progressbar.png
|
||||
size: 4, 284
|
||||
offset: 6, 8
|
||||
float:
|
||||
texture: /Textures/_CP14/Interface/Fishing/Default/float.png
|
||||
size: 22, 76
|
||||
offset: 26, 12
|
||||
fishingMinigameSize: 281
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Resources/Textures/_CP14/Interface/Fishing/Default/fish_icon.png
Normal file
|
After Width: | Height: | Size: 320 B |
BIN
Resources/Textures/_CP14/Interface/Fishing/Default/float.png
Normal file
|
After Width: | Height: | Size: 343 B |
|
After Width: | Height: | Size: 265 B |
BIN
Resources/Textures/_CP14/Objects/Tools/fishing_rod.rsi/icon.png
Normal file
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 676 B |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 48,
|
||||
"y": 48
|
||||
},
|
||||
"license": "CC-BY-SA-4.0",
|
||||
"copyright": "Created by omsoyk (Discord)",
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/_CP14/Objects/Tools/float.rsi/animation.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
Resources/Textures/_CP14/Objects/Tools/float.rsi/float.png
Normal file
|
After Width: | Height: | Size: 266 B |
25
Resources/Textures/_CP14/Objects/Tools/float.rsi/meta.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-4.0",
|
||||
"copyright": "Created by omsoyk (Discord)",
|
||||
"states": [
|
||||
{
|
||||
"name": "float"
|
||||
},
|
||||
{
|
||||
"name": "animation",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||