Harpies Flightless No More! (#4888)
* Initial port of EE Harpy flying (#919) * Added better friction and acceleration modifiers. * Moved server's flight system to shared. I don't know why it isn't in the first place. * Shifting things around. * Code tweaks to make it better * Comment fixes * Fixed footsteps and footstep sounds * Added a quick conditional if InitialStaminaCost greater than zero * Removed a pop-up that wasn't showing * Additional adjustments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed a few more systems to call the Flying system instead of the component * Comment consistency. DeltaV comments where I made it better. EE comments where they originally had things * YAML fixes * Guidebook * Apply suggestions from code review Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> * Removed DoAfterDashEvent * Fixed suggestions * Updated comment * Added new copyright free wingflaps with proper attribution * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Appeased YAML gods --------- Signed-off-by: Vanessa <908648+ShepardToTheStars@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tobias Berger <toby@tobot.dev>
This commit is contained in:
parent
1b7d405ecb
commit
e945f5869a
|
|
@ -0,0 +1,36 @@
|
|||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client._EE.Flight.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class FlightVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The speed of the shader animation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Speed;
|
||||
/// <summary>
|
||||
/// How far it goes in any direction.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Multiplier;
|
||||
|
||||
/// <summary>
|
||||
/// How much the limbs (if there are any) rotate.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Are we animating layers or the entire sprite?
|
||||
/// </summary>
|
||||
public bool AnimateLayer = false;
|
||||
public int? TargetLayer;
|
||||
|
||||
[DataField]
|
||||
public string AnimationKey = "default";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ShaderInstance Shader = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Content.Shared._EE.Flight;
|
||||
using Content.Shared._EE.Flight.Events;
|
||||
using Content.Client._EE.Flight.Components;
|
||||
|
||||
namespace Content.Client._EE.Flight;
|
||||
|
||||
public sealed class FlightSystem : SharedFlightSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<FlightEvent>(OnFlight);
|
||||
}
|
||||
|
||||
private void OnFlight(FlightEvent args)
|
||||
{
|
||||
var uid = GetEntity(args.Uid);
|
||||
if (!_entityManager.TryGetComponent(uid, out SpriteComponent? sprite)
|
||||
|| !args.IsAnimated
|
||||
|| !_entityManager.TryGetComponent(uid, out FlightComponent? flight))
|
||||
return;
|
||||
|
||||
int? targetLayer = null;
|
||||
if (flight.IsLayerAnimated && flight.Layer is not null)
|
||||
{
|
||||
targetLayer = GetAnimatedLayer(uid, flight.Layer, sprite);
|
||||
if (targetLayer == null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.IsFlying && args.IsAnimated && flight.AnimationKey != "default")
|
||||
{
|
||||
var comp = new FlightVisualsComponent
|
||||
{
|
||||
AnimateLayer = flight.IsLayerAnimated,
|
||||
AnimationKey = flight.AnimationKey,
|
||||
Multiplier = flight.ShaderMultiplier,
|
||||
Offset = flight.ShaderOffset,
|
||||
Speed = flight.ShaderSpeed,
|
||||
TargetLayer = targetLayer,
|
||||
};
|
||||
AddComp(uid, comp);
|
||||
}
|
||||
if (!args.IsFlying)
|
||||
RemComp<FlightVisualsComponent>(uid);
|
||||
}
|
||||
|
||||
public int? GetAnimatedLayer(EntityUid uid, string targetLayer, SpriteComponent? sprite = null)
|
||||
{
|
||||
if (!Resolve(uid, ref sprite))
|
||||
return null;
|
||||
|
||||
var index = 0;
|
||||
foreach (var layer in sprite.AllLayers)
|
||||
{
|
||||
// This feels like absolute shitcode, isn't there a better way to check for it?
|
||||
if (layer.Rsi?.Path.ToString() == targetLayer)
|
||||
return index;
|
||||
index++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using Content.Client._EE.Flight.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._EE.Flight;
|
||||
|
||||
/// <summary>
|
||||
/// Handles offsetting an entity while flying
|
||||
/// </summary>
|
||||
public sealed class FlyingVisualizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<FlightVisualsComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<FlightVisualsComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<FlightVisualsComponent, BeforePostShaderRenderEvent>(OnBeforeShaderPost);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, FlightVisualsComponent comp, ComponentStartup args)
|
||||
{
|
||||
comp.Shader = _protoMan.Index<ShaderPrototype>(comp.AnimationKey).InstanceUnique();
|
||||
AddShader(uid, comp.Shader, comp.AnimateLayer, comp.TargetLayer);
|
||||
SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, FlightVisualsComponent comp, ComponentShutdown args)
|
||||
{
|
||||
AddShader(uid, null, comp.AnimateLayer, comp.TargetLayer);
|
||||
}
|
||||
|
||||
private void AddShader(Entity<SpriteComponent?> entity, ShaderInstance? shader, bool animateLayer, int? layer)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
entity.Comp.PostShader = shader;
|
||||
|
||||
if (animateLayer && layer is not null)
|
||||
entity.Comp.LayerSetShader(layer.Value, shader);
|
||||
|
||||
entity.Comp.GetScreenTexture = shader is not null;
|
||||
entity.Comp.RaiseShaderEvent = shader is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function can be used to modify the shader's values while its running.
|
||||
/// </summary>
|
||||
private void OnBeforeShaderPost(EntityUid uid, FlightVisualsComponent comp, ref BeforePostShaderRenderEvent args)
|
||||
{
|
||||
SetValues(comp, comp.Speed, comp.Offset, comp.Multiplier);
|
||||
}
|
||||
|
||||
private void SetValues(FlightVisualsComponent comp, float speed, float offset, float multiplier)
|
||||
{
|
||||
comp.Shader.SetParameter("Speed", speed);
|
||||
comp.Shader.SetParameter("Offset", offset);
|
||||
comp.Shader.SetParameter("Multiplier", multiplier);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared._EE.Flight;
|
||||
|
||||
namespace Content.Server._EE.Flight;
|
||||
|
||||
public sealed class FlightSystem : SharedFlightSystem;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared._EE.Flight; // DeltaV
|
||||
using Content.Shared._EE.FootPrint;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared._EE.FootPrint;
|
||||
// using Content.Shared.Standing;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
|
@ -21,10 +21,12 @@ public sealed class FootPrintsSystem : EntitySystem
|
|||
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV
|
||||
|
||||
private EntityQuery<TransformComponent> _transformQuery;
|
||||
private EntityQuery<MobThresholdsComponent> _mobThresholdQuery;
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
|
||||
// private EntityQuery<LayingDownComponent> _layingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
|
|
@ -47,6 +49,9 @@ public sealed class FootPrintsSystem : EntitySystem
|
|||
|
||||
private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints
|
||||
return;
|
||||
|
||||
if (component.PrintsColor.A <= 0f
|
||||
|| !_transformQuery.TryComp(uid, out var transform)
|
||||
|| !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using Content.Shared._EE.Flight; // DeltaV
|
||||
using Content.Shared._EE.FootPrint;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
|
@ -12,6 +13,7 @@ public sealed class PuddleFootPrintsSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -21,6 +23,9 @@ public sealed class PuddleFootPrintsSystem : EntitySystem
|
|||
|
||||
private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args)
|
||||
{
|
||||
if (_flight.IsFlying(uid)) // DeltaV - Flying players won't make footprints
|
||||
return;
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance)
|
||||
|| !TryComp<PuddleComponent>(uid, out var puddle)
|
||||
|| !TryComp<FootPrintsComponent>(args.OtherEntity, out var tripper)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using Content.Shared._EE.Flight; // DeltaV - Harpy Flight
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
|
|
@ -55,6 +56,7 @@ namespace Content.Shared.Cuffs
|
|||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV - Harpy flight
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -514,6 +516,15 @@ namespace Content.Shared.Cuffs
|
|||
return false;
|
||||
}
|
||||
|
||||
// EE - Harpy Flight
|
||||
if (_flight.IsFlying(target))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("handcuff-component-target-flying-error",
|
||||
("targetName", Identity.Name(target, EntityManager, user))), user, user);
|
||||
return false;
|
||||
}
|
||||
// END EE
|
||||
|
||||
var cuffTime = handcuffComponent.CuffTime;
|
||||
|
||||
if (HasComp<StunnedComponent>(target))
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
|||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _status = default!;
|
||||
[Dependency] protected readonly SharedStunSystem StunSystem = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movement = default!; // EE - Harpy Flight
|
||||
|
||||
/// <summary>
|
||||
/// How much of a buffer is there between the stun duration and when stuns can be re-applied.
|
||||
|
|
@ -265,7 +266,8 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
|||
}
|
||||
|
||||
public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null,
|
||||
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool ignoreResist = false)
|
||||
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null, bool ignoreResist = false,
|
||||
bool? allowsSlowdown = true) // EE - Harpy Flight
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
|
@ -283,6 +285,9 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
|||
|
||||
value = UniversalStaminaDamageModifier * value;
|
||||
|
||||
if (allowsSlowdown == true) // EE - Harpy Flight
|
||||
_movement.RefreshMovementSpeedModifiers(uid);
|
||||
|
||||
// Have we already reached the point of max stamina damage?
|
||||
if (component.Critical)
|
||||
return;
|
||||
|
|
@ -362,12 +367,23 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
|||
{
|
||||
// Just in case we have active but not stamina we'll check and account for it.
|
||||
if (!stamQuery.TryGetComponent(uid, out var comp) ||
|
||||
comp.StaminaDamage <= 0f && !comp.Critical)
|
||||
comp.StaminaDamage <= 0f && !comp.Critical && comp.ActiveDrains.Count == 0) // EE - Harpy Flight
|
||||
{
|
||||
RemComp<ActiveStaminaComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
// EE - Harpy Flight
|
||||
if (comp.ActiveDrains.Count > 0)
|
||||
foreach (var (source, (drainRate, modifiesSpeed)) in comp.ActiveDrains)
|
||||
TakeStaminaDamage(uid,
|
||||
drainRate * frameTime,
|
||||
comp,
|
||||
source: source,
|
||||
visual: false,
|
||||
allowsSlowdown: modifiesSpeed);
|
||||
// End EE
|
||||
|
||||
// Shouldn't need to consider paused time as we're only iterating non-paused stamina components.
|
||||
var nextUpdate = comp.NextUpdate;
|
||||
|
||||
|
|
@ -380,10 +396,11 @@ public abstract partial class SharedStaminaSystem : EntitySystem
|
|||
|
||||
comp.NextUpdate += TimeSpan.FromSeconds(1f);
|
||||
|
||||
TakeStaminaDamage(
|
||||
uid,
|
||||
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
||||
comp);
|
||||
if (comp.ActiveDrains.Count == 0) // EE - Harpy Flight
|
||||
TakeStaminaDamage(
|
||||
uid,
|
||||
comp.AfterCritical ? -comp.Decay * comp.AfterCritDecayMultiplier : -comp.Decay, // Recover faster after crit
|
||||
comp);
|
||||
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared._EE.Flight.Events;
|
||||
|
||||
namespace Content.Shared.Gravity;
|
||||
|
||||
/// <summary>
|
||||
/// Handles offsetting a sprite when there is no gravity
|
||||
/// </summary>
|
||||
public abstract class SharedFloatingVisualizerSystem : EntitySystem
|
||||
public abstract partial class SharedFloatingVisualizerSystem : EntitySystem // DeltaV - Made Partial for Harpy Flying
|
||||
{
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
|
||||
|
|
@ -16,6 +17,8 @@ public abstract class SharedFloatingVisualizerSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<FloatingVisualsComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<FloatingVisualsComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
|
||||
|
||||
SubscribeNetworkEvent<FlightEvent>(OnFlight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared._EE.Flight; // DeltaV - Harpy Flight
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Throwing;
|
||||
|
|
@ -16,6 +17,7 @@ public abstract partial class SharedGravitySystem : EntitySystem
|
|||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly SharedFlightSystem _flight = default!; // DeltaV - Harpy Flight
|
||||
|
||||
public static readonly ProtoId<AlertPrototype> WeightlessAlert = "Weightless";
|
||||
|
||||
|
|
@ -69,6 +71,9 @@ public abstract partial class SharedGravitySystem : EntitySystem
|
|||
if (entity.Comp2.BodyType is BodyType.Static or BodyType.Kinematic)
|
||||
return false;
|
||||
|
||||
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
|
||||
return true;
|
||||
|
||||
// Check if something other than the grid or map is overriding our gravity
|
||||
var ev = new IsWeightlessEvent();
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
namespace Content.Shared.Damage.Components;
|
||||
|
||||
public sealed partial class StaminaComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A dictionary of active stamina drains, with the key being the source of the drain,
|
||||
/// DrainRate how much it changes per tick, and ModifiesSpeed if it should slow down the user.
|
||||
///
|
||||
/// Used primarily for harpy flying.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<EntityUid, (float DrainRate, bool ModifiesSpeed)> ActiveDrains = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Shared.Damage.Components;
|
||||
|
||||
namespace Content.Shared.Damage.Systems;
|
||||
|
||||
public abstract partial class SharedStaminaSystem : EntitySystem
|
||||
{
|
||||
public void ToggleStaminaDrain(EntityUid target, float drainRate, bool enabled, bool modifiesSpeed, EntityUid? source = null)
|
||||
{
|
||||
if (!TryComp<StaminaComponent>(target, out var stamina))
|
||||
return;
|
||||
|
||||
// If theres no source, we assume its the target that caused the drain.
|
||||
var actualSource = source ?? target;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
stamina.ActiveDrains[actualSource] = (drainRate, modifiesSpeed);
|
||||
EnsureComp<ActiveStaminaComponent>(target);
|
||||
}
|
||||
else
|
||||
stamina.ActiveDrains.Remove(actualSource);
|
||||
|
||||
Dirty(target, stamina);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared._EE.Flight;
|
||||
|
||||
/// <summary>
|
||||
/// Adds an action that allows the user to become temporarily
|
||||
/// weightless at the cost of stamina and hand usage.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent(), AutoGenerateComponentState]
|
||||
public sealed partial class FlightComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? ToggleAction = "ActionToggleFlight";
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? ToggleActionEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Is the user flying right now?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsCurrentlyFlying;
|
||||
|
||||
/// <summary>
|
||||
/// Stamina drain per second when flying
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float StaminaDrainRate = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Stamina cost when taking off.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float InitialStaminaCost = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// DoAfter delay until the user becomes weightless.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float ActivationDelay = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Speed modifier while in flight
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float SpeedModifier = 2.0f;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Friction modifier while in flight. Should be less than one so
|
||||
/// they have less control while flying. Also applies to friction with no inputs.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float FrictionModifier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Acceleration modifer while in flight.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float AccelerationModifer = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Path to a sound specifier or collection for the noises made during flight
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier FlapSound = new SoundCollectionSpecifier("WingFlaps");
|
||||
|
||||
/// <summary>
|
||||
/// Is the flight animated?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsAnimated = true;
|
||||
|
||||
/// <summary>
|
||||
/// Does the animation animate a layer?.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsLayerAnimated;
|
||||
|
||||
/// <summary>
|
||||
/// Which RSI layer path does this animate?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? Layer;
|
||||
|
||||
/// <summary>
|
||||
/// Whats the speed of the shader?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ShaderSpeed = 6.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How much are the values in the shader's calculations multiplied by?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ShaderMultiplier = 0.02f;
|
||||
|
||||
/// <summary>
|
||||
/// What is the offset on the shader?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ShaderOffset = 0.25f;
|
||||
|
||||
/// <summary>
|
||||
/// What animation does the flight use?
|
||||
/// </summary>
|
||||
|
||||
[DataField]
|
||||
public string AnimationKey = "default";
|
||||
|
||||
/// <summary>
|
||||
/// Time between sounds being played
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float FlapInterval = 1.0f;
|
||||
|
||||
public float TimeUntilFlap;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Robust.Shared.Serialization;
|
||||
using Content.Shared.DoAfter;
|
||||
|
||||
namespace Content.Shared._EE.Flight.Events;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class FlightDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class FlightEvent(NetEntity uid, bool isFlying, bool isAnimated) : EntityEventArgs
|
||||
{
|
||||
public NetEntity Uid { get; } = uid;
|
||||
public bool IsFlying { get; } = isFlying;
|
||||
public bool IsAnimated { get; } = isAnimated;
|
||||
}
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
using Content.Shared.Actions;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared._EE.Flight.Events;
|
||||
using Content.Shared.Standing;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.StepTrigger.Systems;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Shared._EE.Flight;
|
||||
|
||||
public abstract class SharedFlightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!;
|
||||
[Dependency] private readonly SharedStaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly StandingStateSystem _standing = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FlightComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<FlightComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<FlightComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMoveSpeed);
|
||||
SubscribeLocalEvent<FlightComponent, RefreshFrictionModifiersEvent>(OnRefreshFrictionModifiers);
|
||||
SubscribeLocalEvent<FlightComponent, RefreshWeightlessModifiersEvent>(OnRefreshWeightlessModifiers);
|
||||
|
||||
SubscribeLocalEvent<FlightComponent, ToggleFlightEvent>(OnToggleFlight);
|
||||
SubscribeLocalEvent<FlightComponent, FlightDoAfterEvent>(OnFlightDoAfter);
|
||||
SubscribeLocalEvent<FlightComponent, MobStateChangedEvent>(OnMobStateChangedEvent);
|
||||
SubscribeLocalEvent<FlightComponent, EntityZombifiedEvent>(OnZombified);
|
||||
SubscribeLocalEvent<FlightComponent, KnockedDownEvent>(OnKnockedDown);
|
||||
SubscribeLocalEvent<FlightComponent, StunnedEvent>(OnStunned);
|
||||
SubscribeLocalEvent<FlightComponent, SleepStateChangedEvent>(OnSleep);
|
||||
SubscribeLocalEvent<FlightComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<FlightComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (!component.IsCurrentlyFlying)
|
||||
continue;
|
||||
|
||||
component.TimeUntilFlap -= frameTime;
|
||||
|
||||
if (component.TimeUntilFlap > 0f)
|
||||
continue;
|
||||
|
||||
_audio.PlayPredicted(component.FlapSound, uid, uid);
|
||||
component.TimeUntilFlap = component.FlapInterval;
|
||||
|
||||
}
|
||||
}
|
||||
#region Query Functions
|
||||
|
||||
public bool IsFlying(Entity<FlightComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return false;
|
||||
|
||||
return entity.Comp.IsCurrentlyFlying;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Core Functions
|
||||
|
||||
public void ToggleActive(Entity<FlightComponent> ent, bool active)
|
||||
{
|
||||
ent.Comp.IsCurrentlyFlying = active;
|
||||
ent.Comp.TimeUntilFlap = 0f;
|
||||
_actionsSystem.SetToggled(ent.Comp.ToggleActionEntity, ent.Comp.IsCurrentlyFlying);
|
||||
RaiseNetworkEvent(new FlightEvent(GetNetEntity(ent), ent.Comp.IsCurrentlyFlying, ent.Comp.IsAnimated));
|
||||
UpdateHands(ent, active);
|
||||
_stamina.TryTakeStamina(ent.Owner, ent.Comp.InitialStaminaCost, visual: false);
|
||||
_stamina.ToggleStaminaDrain(ent, ent.Comp.StaminaDrainRate, active, false);
|
||||
|
||||
_gravity.RefreshWeightless(ent.Owner, active);
|
||||
_movementSpeed.RefreshMovementSpeedModifiers(ent);
|
||||
_movementSpeed.RefreshFrictionModifiers(ent);
|
||||
_movementSpeed.RefreshWeightlessModifiers(ent);
|
||||
|
||||
Dirty(ent, ent.Comp);
|
||||
}
|
||||
|
||||
private bool CanFly(EntityUid uid, FlightComponent component)
|
||||
{
|
||||
if (TryComp<StandingStateComponent>(uid, out var standing) && _standing.IsDown((uid, standing)))
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("no-flight-while-down"), uid, uid, PopupType.Small);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<CuffableComponent>(uid, out var cuffableComp) && !cuffableComp.CanStillInteract)
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("no-flight-while-restrained"), uid, uid, PopupType.Small);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<ZombieComponent>(uid))
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("no-flight-while-zombified"), uid, uid, PopupType.Small);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Got to have stamina to fly
|
||||
if (!TryComp<StaminaComponent>(uid, out var stam))
|
||||
return false;
|
||||
|
||||
var hasEnoughStamina = stam.StaminaDamage + component.InitialStaminaCost < stam.CritThreshold || stam.Critical;
|
||||
if (!hasEnoughStamina)
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("no-flight-exhausted"), uid, uid, PopupType.MediumCaution);
|
||||
return false;
|
||||
}
|
||||
|
||||
// All preflight checks complete, ready for take-off!
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateHands(EntityUid uid, bool flying)
|
||||
{
|
||||
if (!TryComp<HandsComponent>(uid, out var handsComponent))
|
||||
return;
|
||||
|
||||
if (flying)
|
||||
BlockHands(uid, handsComponent);
|
||||
else
|
||||
FreeHands(uid);
|
||||
}
|
||||
|
||||
private void BlockHands(EntityUid uid, HandsComponent handsComponent)
|
||||
{
|
||||
var freeHands = 0;
|
||||
foreach (var hand in _hands.EnumerateHands((uid, handsComponent)))
|
||||
{
|
||||
if (!_hands.TryGetHeldItem((uid, handsComponent), hand, out var heldItem))
|
||||
{
|
||||
freeHands++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this entity removable? (they might have handcuffs on)
|
||||
if (HasComp<UnremoveableComponent>(heldItem) && heldItem != uid)
|
||||
continue;
|
||||
|
||||
if (_hands.TryDrop((uid, handsComponent), hand))
|
||||
{
|
||||
freeHands++;
|
||||
}
|
||||
|
||||
if (freeHands == 2)
|
||||
break;
|
||||
}
|
||||
if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem1))
|
||||
EnsureComp<UnremoveableComponent>(virtItem1.Value);
|
||||
|
||||
if (_virtualItem.TrySpawnVirtualItemInHand(uid, uid, out var virtItem2))
|
||||
EnsureComp<UnremoveableComponent>(virtItem2.Value);
|
||||
}
|
||||
|
||||
private void FreeHands(EntityUid uid)
|
||||
{
|
||||
_virtualItem.DeleteInHandsMatching(uid, uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
private void OnStartup(EntityUid uid, FlightComponent component, ComponentStartup args)
|
||||
{
|
||||
_actionsSystem.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, FlightComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(uid, component.ToggleActionEntity);
|
||||
}
|
||||
private void OnRefreshMoveSpeed(EntityUid uid, FlightComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying) // If we're not flying, don't apply flying's modifier
|
||||
return;
|
||||
|
||||
args.ModifySpeed(component.SpeedModifier, component.SpeedModifier);
|
||||
}
|
||||
|
||||
// DeltaV - Since we use the new movement system and EE doesn't, we got to also apply friction modifiers.
|
||||
private void OnRefreshFrictionModifiers(Entity<FlightComponent> ent, ref RefreshFrictionModifiersEvent args)
|
||||
{
|
||||
if (!ent.Comp.IsCurrentlyFlying) // If we're not flying, don't apply flying's modifier
|
||||
return;
|
||||
|
||||
args.ModifyFriction(ent.Comp.FrictionModifier, ent.Comp.FrictionModifier);
|
||||
args.ModifyAcceleration(ent.Comp.AccelerationModifer);
|
||||
}
|
||||
|
||||
private void OnRefreshWeightlessModifiers(Entity<FlightComponent> ent, ref RefreshWeightlessModifiersEvent args)
|
||||
{
|
||||
if (!ent.Comp.IsCurrentlyFlying) // If we're not flying, don't apply flying's modifier
|
||||
return;
|
||||
|
||||
//args.ModifyFriction(ent.Comp.FrictionModifier, ent.Comp.FrictionModifier);
|
||||
args.ModifyAcceleration(ent.Comp.AccelerationModifer);
|
||||
}
|
||||
|
||||
private void OnToggleFlight(EntityUid uid, FlightComponent component, ToggleFlightEvent args)
|
||||
{
|
||||
// If the user isnt flying, we check for conditionals and initiate a doafter.
|
||||
if (!component.IsCurrentlyFlying)
|
||||
{
|
||||
if (!CanFly(uid, component))
|
||||
return;
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager,
|
||||
uid, component.ActivationDelay,
|
||||
new FlightDoAfterEvent(), uid, target: uid)
|
||||
{
|
||||
BlockDuplicate = true,
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
if (!_doAfter.TryStartDoAfter(doAfterArgs))
|
||||
return;
|
||||
}
|
||||
else
|
||||
ToggleActive((uid, component), false);
|
||||
}
|
||||
|
||||
private void OnFlightDoAfter(EntityUid uid, FlightComponent component, FlightDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
ToggleActive((uid, component), true);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnMobStateChangedEvent(EntityUid uid, FlightComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying || args.NewMobState is MobState.Critical or MobState.Dead)
|
||||
return;
|
||||
|
||||
ToggleActive((args.Target, component), false);
|
||||
}
|
||||
|
||||
private void OnZombified(EntityUid uid, FlightComponent component, ref EntityZombifiedEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying)
|
||||
return;
|
||||
|
||||
ToggleActive((args.Target, component), false);
|
||||
|
||||
if (!TryComp<StaminaComponent>(uid, out var stamina))
|
||||
return;
|
||||
|
||||
Dirty(uid, stamina);
|
||||
}
|
||||
|
||||
private void OnKnockedDown(EntityUid uid, FlightComponent component, ref KnockedDownEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying)
|
||||
return;
|
||||
|
||||
ToggleActive((uid, component), false);
|
||||
}
|
||||
|
||||
private void OnStunned(EntityUid uid, FlightComponent component, ref StunnedEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying)
|
||||
return;
|
||||
|
||||
ToggleActive((uid, component), false);
|
||||
}
|
||||
|
||||
private void OnSleep(EntityUid uid, FlightComponent component, ref SleepStateChangedEvent args)
|
||||
{
|
||||
if (!component.IsCurrentlyFlying || !args.FellAsleep)
|
||||
return;
|
||||
|
||||
ToggleActive((uid, component), false);
|
||||
if (!TryComp<StaminaComponent>(uid, out var stamina))
|
||||
return;
|
||||
|
||||
Dirty(uid, stamina);
|
||||
}
|
||||
private void OnStepTriggerAttempt(Entity<FlightComponent> ent, ref StepTriggerAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.IsCurrentlyFlying)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
public sealed partial class ToggleFlightEvent : InstantActionEvent { }
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using Content.Shared._EE.Flight.Events;
|
||||
|
||||
namespace Content.Shared.Gravity;
|
||||
|
||||
/// <summary>
|
||||
/// Handles flying event handlers.
|
||||
/// </summary>
|
||||
public abstract partial class SharedFloatingVisualizerSystem : EntitySystem
|
||||
{
|
||||
private void OnFlight(FlightEvent args)
|
||||
{
|
||||
var uid = GetEntity(args.Uid);
|
||||
if (!TryComp<FloatingVisualsComponent>(uid, out var floating))
|
||||
return;
|
||||
|
||||
floating.CanFloat = args.IsFlying;
|
||||
|
||||
if (!args.IsFlying || !args.IsAnimated)
|
||||
return;
|
||||
|
||||
FloatAnimation(uid, floating.Offset, floating.AnimationKey, floating.AnimationTime);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
- files:
|
||||
- "wingflap1.ogg"
|
||||
- "wingflap2.ogg"
|
||||
- "wingflap3.ogg"
|
||||
- "wingflap4.ogg"
|
||||
- "wingflap5.ogg"
|
||||
- "wingflap6.ogg"
|
||||
- "wingflap7.ogg"
|
||||
- "wingflap8.ogg"
|
||||
license: "CC0-1.0"
|
||||
copyright: "Wing Flap originally made by DRAGONSTUDIO. Split into 1s segments by ShepardToTheStars."
|
||||
source: "https://ko-fi.com/s/964e22818d/"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
|||
handcuff-component-target-flying-error = You cannot reach {$targetName}'s hands!
|
||||
|
||||
no-flight-while-restrained = You can't fly right now.
|
||||
no-flight-while-zombified = You can't use your wings right now.
|
||||
|
||||
no-flight-while-down = You can't fly while crawling on the ground.
|
||||
no-flight-exhausted = You are too exhausted to take flight.
|
||||
|
|
@ -6,6 +6,10 @@
|
|||
abstract: true
|
||||
components:
|
||||
- type: HarpySinger
|
||||
- type: Flight # EE - Flight
|
||||
isLayerAnimated: true
|
||||
layer: "/Textures/_EE/Mobs/Customization/Harpy/harpy_wings.rsi"
|
||||
animationKey: "Flap"
|
||||
- type: Instrument
|
||||
allowPercussion: false
|
||||
program: 52
|
||||
|
|
@ -236,5 +240,16 @@
|
|||
- type: Action
|
||||
icon: _DV/Interface/Actions/harpy_syrinx.png
|
||||
itemIconStyle: BigAction
|
||||
|
||||
- type: entity
|
||||
id: ActionToggleFlight
|
||||
name: Fly
|
||||
description: Make use of your wings to fly. Beat the flightless bird allegations.
|
||||
components:
|
||||
- type: Action
|
||||
itemIconStyle: BigAction
|
||||
icon: { sprite: _EE/Interface/Actions/flight.rsi, state: flight_off }
|
||||
iconOn: { sprite : _EE/Interface/Actions/flight.rsi, state: flight_on }
|
||||
checkCanInteract: false
|
||||
- type: InstantAction
|
||||
event: !type:VoiceMaskSetNameEvent
|
||||
event: !type:ToggleFlightEvent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
- type: soundCollection
|
||||
id: WingFlaps
|
||||
files:
|
||||
- /Audio/_DV/Effects/Flight/wingflap1.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap2.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap3.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap4.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap5.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap6.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap7.ogg
|
||||
- /Audio/_DV/Effects/Flight/wingflap8.ogg
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Flight shaders
|
||||
- type: shader
|
||||
id: Flap
|
||||
kind: source
|
||||
path: "/Textures/_EE/Shaders/flap.swsl"
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
- Somewhat smaller than Humans, although not as small as Felinids.
|
||||
- Their talons deal Piercing damage.
|
||||
- They have the ability to fly for short amounts of time.
|
||||
- Can imitate around 70% of the game's sound library through a huge list of voice emotes.
|
||||
- They can "Sing" midis by imitating instruments. Harpies can select their imitated instrument by right-clicking on themselves.
|
||||
- Moves 10% faster than other species.
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"copyright" : "Made by dootythefrooty (273243513800622090)",
|
||||
"license" : "CC-BY-SA-3.0",
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "flight_off"
|
||||
},
|
||||
{
|
||||
"name": "flight_on"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
preset raw;
|
||||
|
||||
varying highp vec4 VtxModulate;
|
||||
varying highp vec2 Pos;
|
||||
|
||||
uniform highp float Speed;
|
||||
uniform highp float Multiplier;
|
||||
uniform highp float Offset;
|
||||
|
||||
void fragment() {
|
||||
highp vec4 texColor = zTexture(UV);
|
||||
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
|
||||
COLOR = texColor * VtxModulate * vec4(lightSample, 1.0);
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
vec2 pos = aPos;
|
||||
|
||||
// Apply MVP transformation first
|
||||
vec2 transformedPos = apply_mvp(pos);
|
||||
|
||||
// Calculate vertical movement in screen space
|
||||
float verticalOffset = (sin(TIME * Speed) + Offset) * Multiplier;
|
||||
|
||||
// Apply vertical movement after MVP transformation
|
||||
transformedPos.y += verticalOffset;
|
||||
|
||||
// Assign the final position
|
||||
VERTEX = transformedPos;
|
||||
|
||||
// Keep the original UV coordinates
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
Loading…
Reference in New Issue