Skip to content

Commit

Permalink
Merge pull request #34 from CommunalHelper/viv-slingshot-wallbounce
Browse files Browse the repository at this point in the history
Slingshot rewrite
  • Loading branch information
Viv-0 authored Jan 16, 2024
2 parents ec556db + ea3f8d7 commit b626b93
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 48 deletions.
8 changes: 6 additions & 2 deletions Ahorn/entities/purpleBooster.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ using ..Ahorn, Maple
const placements = Ahorn.PlacementDict(
"Booster (Purple) (Vortex Helper)" => Ahorn.EntityPlacement(
PurpleBooster,
"point"
"point",
Dict{String,Any} (
"QoL" => true,
)
),
"Booster (Lavender) (Vortex Helper)" => Ahorn.EntityPlacement(
PurpleBooster,
"point",
Dict{String, Any}(
"lavender" => true
"lavender" => true,
"QoL" => true
)
)
)
Expand Down
3 changes: 3 additions & 0 deletions Ahorn/lang/en_gb.lang
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ placements.entities.VortexHelper/Lilly.tooltips.maxLength=The maximum distance a

# Purple Booster
placements.entities.VortexHelper/PurpleBooster.tooltips.lavender=Whether the booster should instead be lavender in color, and launch the player forward instead of arcing back.
placements.entities.VortexHelper/PurpleBooster.names.QoL=Version 2
placements.entities.VortexHelper/PurpleBooster.tooltips.QoL=Enables Wallbouncing during the windup animation.\nFixes speedboost not applying when dashing early from booster.\nReduces Freeze Timer upon launch exit and ensures a shorter dashCooldownTimer.


# Switch Block
placements.entities.VortexHelper/SwitchBlock.tooltips.index=Changes the index and color of this Switch Block.
Expand Down
14 changes: 7 additions & 7 deletions Code/Entities/FloorBooster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,18 +275,18 @@ private static bool Player_RefillDash(On.Celeste.Player.orig_RefillDash orig, Pl
private static void Player_NormalBegin(On.Celeste.Player.orig_NormalBegin orig, Player self)
{
orig(self);
DynData<Player> playerData = self.GetData();
DynamicData playerData = DynamicData.For(self);
playerData.Set("floorBoosterSpeed", 0f);
playerData.Set<FloorBooster>("lastFloorBooster", null);
playerData.Set("lastFloorBooster", null);
}

private static int Player_NormalUpdate(On.Celeste.Player.orig_NormalUpdate orig, Player self)
{
DynData<Player> playerData = self.GetData();
DynamicData playerData = DynamicData.For(self);

// thanks max480 for the bug report.
if (!playerData.Data.ContainsKey("lastFloorBooster"))
playerData.Set<FloorBooster>("lastFloorBooster", null);
playerData.Set("lastFloorBooster", null);

FloorBooster lastFloorBooster = playerData.Get<FloorBooster>("lastFloorBooster");

Expand All @@ -300,7 +300,7 @@ private static int Player_NormalUpdate(On.Celeste.Player.orig_NormalUpdate orig,
self.LiftSpeed += vec / 1.6f;
self.Speed += vec / 1.6f;

playerData.Set<FloorBooster>("lastFloorBooster", null);
playerData.Set("lastFloorBooster", null);
}

bool touchedFloorBooster = false;
Expand All @@ -324,8 +324,8 @@ private static int Player_NormalUpdate(On.Celeste.Player.orig_NormalUpdate orig,
}
}

if (!touchedFloorBooster)
floorBoosterSpeed = Calc.Approach(playerData.Get<float>("floorBoosterSpeed"), 0f, 4f * Engine.DeltaTime);
if (!touchedFloorBooster && playerData?.Get("floorBoosterSpeed") is float f)
floorBoosterSpeed = Calc.Approach(f, 0f, 4f * Engine.DeltaTime);
playerData.Set("floorBoosterSpeed", floorBoosterSpeed);

return orig(self);
Expand Down
9 changes: 6 additions & 3 deletions Code/Entities/LavenderBooster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ public class LavenderBooster : Booster
public static readonly ParticleType P_BurstExplodeLavender = new(P_Burst);

private readonly DynData<Booster> boosterData;

private readonly bool QoL;
public LavenderBooster(EntityData data, Vector2 offset)
: base(data.Position + offset, red: false)
{
this.boosterData = new DynData<Booster>(this);
QoL = data.Bool("QoL", false);

Sprite oldSprite = this.boosterData.Get<Sprite>("sprite");
Remove(oldSprite);
Expand Down Expand Up @@ -56,11 +57,13 @@ public static void Unhook()
private static void Booster_PlayerReleased(On.Celeste.Booster.orig_PlayerReleased orig, Booster self)
{
orig(self);
if (Util.TryGetPlayer(out Player player) && player.LastBooster is LavenderBooster)
if (Util.TryGetPlayer(out Player player) && player.LastBooster is LavenderBooster l)
{
Audio.Play(SFX.game_05_redbooster_end, player.Center);
PurpleBooster.LaunchPlayerParticles(player, player.DashDir, P_BurstExplodeLavender);
PurpleBooster.PurpleBoosterExplodeLaunch(player, player.GetData(), self.Center - player.DashDir, null, -1f);
DynamicData dyn = DynamicData.For(player);
dyn.Set(PurpleBooster.QUALITYOFLIFEUPDATE, l.QoL);
PurpleBooster.PurpleBoosterExplodeLaunch(player, dyn, self.Center - player.DashDir, null, -1f);
}
}

Expand Down
128 changes: 107 additions & 21 deletions Code/Entities/PurpleBooster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
using Celeste.Mod.VortexHelper.Misc;
using Celeste.Mod.VortexHelper.Misc.Extensions;
using Microsoft.Xna.Framework;
using Mono.Cecil.Cil;
using Monocle;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using MonoMod.Utils;
using System;
using System.Collections;
Expand All @@ -15,6 +18,7 @@ namespace Celeste.Mod.VortexHelper.Entities;
public class PurpleBooster : Entity
{
internal const string POSSIBLE_EARLY_DASHSPEED = "purpleBoostPossibleEarlyDashSpeed";
internal const string QUALITYOFLIFEUPDATE = "purpleBoostQoL";

private readonly Sprite sprite;
private readonly Wiggler wiggler;
Expand Down Expand Up @@ -43,8 +47,11 @@ public bool BoostingPlayer
public static readonly ParticleType P_BurstExplode = new(Booster.P_Burst);

private readonly SoundSource loopingSfx;
public readonly bool QoL;
public PurpleBooster(EntityData data, Vector2 offset)
: this(data.Position + offset) { }
: this(data.Position + offset) {
QoL = data.Bool("QoL");
}

public PurpleBooster(Vector2 position)
: base(position)
Expand Down Expand Up @@ -124,8 +131,9 @@ public static void Boost(Player player, PurpleBooster booster)
{
player.StateMachine.State = VortexHelperModule.PurpleBoosterState;
player.Speed = Vector2.Zero;
DynData<Player> playerData = player.GetData();
DynamicData playerData = DynamicData.For(player);
playerData.Set("boostTarget", booster.Center);
playerData.Set(QUALITYOFLIFEUPDATE, booster.QoL);
booster.StartedBoosting = true;
}

Expand Down Expand Up @@ -352,7 +360,7 @@ public static void PurpleBoostBegin()
public static int PurpleBoostUpdate()
{
Util.TryGetPlayer(out Player player);
DynData<Player> playerData = player.GetData();
DynamicData playerData = DynamicData.For(player);

Vector2 boostTarget = playerData.Get<Vector2>("boostTarget");
Vector2 value = Input.Aim.Value * 3f;
Expand Down Expand Up @@ -385,10 +393,9 @@ public static int PurpleBoostUpdate()
public static void PurpleBoostEnd()
{
Util.TryGetPlayer(out Player player);

Vector2 boostTarget = player.GetData().Get<Vector2>("boostTarget");
DynamicData playerData = DynamicData.For(player);
Vector2 boostTarget = playerData.Get<Vector2>("boostTarget");
Vector2 vector = (boostTarget - player.Collider.Center).Floor();

player.MoveToX(vector.X, null);
player.MoveToY(vector.Y, null);
}
Expand All @@ -407,7 +414,7 @@ public static void PurpleDashingBegin()
Celeste.Freeze(0.05f); // this freeze makes fastbubbling much more lenient

Util.TryGetPlayer(out Player player);
DynData<Player> playerData = player.GetData();
DynamicData playerData = DynamicData.For(player);
player.DashDir = Input.GetAimVector(player.Facing);
playerData.Set(POSSIBLE_EARLY_DASHSPEED, Vector2.Zero);

Expand All @@ -426,33 +433,70 @@ public static void PurpleDashingBegin()

public static int PurpleDashingUpdate()
{
Util.TryGetPlayer(out Player player);
DynamicData playerData = DynamicData.For(player);
bool QoL = playerData.Get(QUALITYOFLIFEUPDATE) is bool b && b;
if (Input.DashPressed || Input.CrouchDashPressed)
{
Util.TryGetPlayer(out Player player);
DynData<Player> playerData = player.GetData();

player.LiftSpeed += playerData.Get<Vector2>(POSSIBLE_EARLY_DASHSPEED);

if (QoL) player.Speed += playerData.Get<Vector2>(POSSIBLE_EARLY_DASHSPEED);
else player.LiftSpeed += playerData.Get<Vector2>(POSSIBLE_EARLY_DASHSPEED);
return player.StartDash();
}

if (QoL && Math.Abs(player.DashDir.X) <= 0.02 &&
Input.Jump.Pressed && player.CanUnDuck &&
(player.DashDir.Y < 0 ? playerData.Get<Vector2>(POSSIBLE_EARLY_DASHSPEED).Y == 0 : playerData.Get<Vector2>(POSSIBLE_EARLY_DASHSPEED).Y < 0))
{
if ((bool)Util.player_WallJumpCheck.Invoke(player, new object[1]{1}))
{
Util.player_SuperWallJump.Invoke(player,new object[1]{-1});
return 0;
}else if ((bool) Util.player_WallJumpCheck.Invoke(player, new object[1]{-1}))
{
Util.player_SuperWallJump.Invoke(player, new object[1]{1});
return 0;
}
}
return VortexHelperModule.PurpleBoosterDashState;
}

public static IEnumerator PurpleDashingCoroutine()
{
float t = 0f;
Util.TryGetPlayer(out Player player);
DynData<Player> playerData = player.GetData();
DynamicData playerData = DynamicData.For(player);
Vector2 origin = playerData.Get<Vector2>("boostTarget");
if(playerData.Get(QUALITYOFLIFEUPDATE) is bool a && a) {
yield return null;
player.DashDir = playerData.Get<Vector2>("lastAim");
}

Vector2 earlyExitBoost;
Vector2 earlyExitBoost = Vector2.Zero;
while (t < 1f)
{
t = Calc.Approach(t, 1.0f, Engine.DeltaTime * 1.5f);
Vector2 vec = origin + Vector2.UnitY * 6f + player.DashDir * 60f * (float) Math.Sin(t * Math.PI);

playerData.Set(POSSIBLE_EARLY_DASHSPEED, earlyExitBoost = (t > .6f) ? (t - .5f) * 200f * -player.DashDir : Vector2.Zero);

if(playerData.Get(QUALITYOFLIFEUPDATE) is bool b && b)
{
if(t == 1f)
{
// frame 0: mimics speed at launch exit exactly, Input.MoveX.Value == -Math.Sign(player.DashDir) ? 300 : 250
earlyExitBoost = 250f * -player.DashDir;
Vector2 aim = Input.GetAimVector(player.Facing).EightWayNormal().Sign();
if (aim.X == Math.Sign(earlyExitBoost.X)) earlyExitBoost.X *= 1.2f;
if (aim.Y == Math.Sign(earlyExitBoost.Y)) earlyExitBoost.Y *= 1.2f;
} else if(t > 0.93f)
{
// frame -2 : 200 speed
// frame -1 : 205 speed
earlyExitBoost = (float)Math.Round(210f * t) * -player.DashDir;
}
}
else if (t > 0.6f)
{
earlyExitBoost = (t - .5f) * 200f * -player.DashDir;
}
playerData.Set(POSSIBLE_EARLY_DASHSPEED, earlyExitBoost);

if (player.CollideCheck<Solid>(vec))
{
Expand All @@ -467,11 +511,18 @@ public static IEnumerator PurpleDashingCoroutine()
PurpleBoosterExplodeLaunch(player, playerData, player.Center - player.DashDir, origin);
}

public static void PurpleBoosterExplodeLaunch(Player player, DynData<Player> playerData, Vector2 from, Vector2? origin, float factor = 1f)
public static void PurpleDashingEnd()
{
Util.TryGetPlayer(out Player player);
DynamicData playerData = DynamicData.For(player);
playerData.Set(QUALITYOFLIFEUPDATE, false);
}
public static void PurpleBoosterExplodeLaunch(Player player, DynamicData playerData, Vector2 from, Vector2? origin, float factor = 1f)
{
bool QoL = playerData?.Get(QUALITYOFLIFEUPDATE) is bool b && b;
Input.Rumble(RumbleStrength.Strong, RumbleLength.Medium);
Celeste.Freeze(0.1f);
playerData.Set<float?>("launchApproachX", null);
Celeste.Freeze(QoL ? 0.05f : 0.1f);
playerData.Set("launchApproachX", null);
Level level = player.SceneAs<Level>();

if (origin is not null)
Expand All @@ -493,6 +544,8 @@ public static void PurpleBoosterExplodeLaunch(Player player, DynData<Player> pla
if (!player.Inventory.NoRefills)
player.RefillDash();
player.RefillStamina();
if (QoL && playerData?.Get("dashCooldownTimer") is float f)
playerData.Set("dashCooldownTimer", f > 0.06f ? 0.06f : f);
player.StateMachine.State = Player.StLaunch;
player.Speed *= factor;
}
Expand All @@ -501,14 +554,18 @@ public static void PurpleBoosterExplodeLaunch(Player player, DynData<Player> pla

internal static class Hooks
{

public static void Hook()
{
On.Celeste.Player.ctor += Player_ctor;
IL.Celeste.Player.WallJumpCheck += Player_WallJumpCheck;
}


public static void Unhook()
{
On.Celeste.Player.ctor -= Player_ctor;
IL.Celeste.Player.WallJumpCheck -= Player_WallJumpCheck;
}

private static void Player_ctor(On.Celeste.Player.orig_ctor orig, Player self, Vector2 position, PlayerSpriteMode spriteMode)
Expand All @@ -519,7 +576,36 @@ private static void Player_ctor(On.Celeste.Player.orig_ctor orig, Player self, V
VortexHelperModule.PurpleBoosterState = self.StateMachine.AddState(PurpleBoostUpdate, PurpleBoostCoroutine, PurpleBoostBegin, PurpleBoostEnd);

// Custom Purple Booster State (Arc Motion)
VortexHelperModule.PurpleBoosterDashState = self.StateMachine.AddState(PurpleDashingUpdate, PurpleDashingCoroutine, PurpleDashingBegin);
VortexHelperModule.PurpleBoosterDashState = self.StateMachine.AddState(PurpleDashingUpdate, PurpleDashingCoroutine, PurpleDashingBegin, PurpleDashingEnd);
}

private static void Player_WallJumpCheck(ILContext il)
{
ILCursor cursor = new ILCursor(il);
if (cursor.TryGotoNext(MoveType.After, i => i.MatchCallvirt<Player>("get_DashAttacking")))
{
cursor.Emit(OpCodes.Ldarg_0);
cursor.EmitDelegate<Func<bool, Player, bool>>((b, p) =>
{
if (b) return true;
try { if (DynamicData.For(p).TryGet<bool>(QUALITYOFLIFEUPDATE, out bool c) && c) return true; }
catch (NullReferenceException) { return false; }
return false;
});
}
if(cursor.TryGotoNext(MoveType.After, i => i.MatchLdcR4(-1) && i.Next.MatchCeq()))
{
cursor.Emit(OpCodes.Ldarg_0);
cursor.EmitDelegate<Func<float, Player, float>>((f, p) =>
{
try { if (DynamicData.For(p).TryGet<bool>(QUALITYOFLIFEUPDATE, out bool c) && c) return p.DashDir.Y; }
catch (NullReferenceException) { return f; }
return f;
});
}


}

}
}
13 changes: 0 additions & 13 deletions Code/Misc/Extensions/PlayerExt.cs

This file was deleted.

10 changes: 10 additions & 0 deletions Code/Misc/Util.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Monocle;
using MonoMod.Utils;
using System.Reflection;

namespace Celeste.Mod.VortexHelper.Misc;

Expand All @@ -10,4 +12,12 @@ public static bool TryGetPlayer(out Player player)
player = Engine.Scene?.Tracker?.GetEntity<Player>();
return player is not null;
}

public static void LoadDelegates()
{
player_WallJumpCheck = typeof(Player).GetMethod("WallJumpCheck", BindingFlags.NonPublic | BindingFlags.Instance);
player_SuperWallJump = typeof(Player).GetMethod("SuperWallJump", BindingFlags.NonPublic | BindingFlags.Instance);
}
public static MethodInfo player_WallJumpCheck;
public static MethodInfo player_SuperWallJump;
}
2 changes: 2 additions & 0 deletions Code/VortexHelperModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public override void Load()
PufferBarrierRenderer.Hooks.Hook();
StaticMoverWithLiftSpeed.Hooks.Hook();
MiscHooks.Hook();

Util.LoadDelegates();
}

public override void Unload()
Expand Down
Loading

0 comments on commit b626b93

Please sign in to comment.