Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Ahorn/libraries/extendedVariantDictionary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const BooleanVariants = [
"BadelineChasersEverywhere",
"BounceEverywhere",
"BufferableGrab",
"CancelStLaunchOnGround",
"ChangePatternsOfExistingBosses",
"ConsistentThrowing",
"CornerboostProtection",
Expand Down
4 changes: 4 additions & 0 deletions Dialog/English.txt
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,7 @@ MODOPTIONS_EXTENDEDVARIANTS_CLIMBDOWNSPEED= Climb Down Speed
MODOPTIONS_EXTENDEDVARIANTS_CLIMBJUMPSTAMINACOST= Climb Jump Stamina Cost
MODOPTIONS_EXTENDEDVARIANTS_CLIMBUPSTAMINADRAINRATE= Climb Up Stamina Drain Rate
MODOPTIONS_EXTENDEDVARIANTS_CLIMBHOLDSTAMINADRAINRATE= Climb Hold Stamina Drain Rate

MODOPTIONS_EXTENDEDVARIANTS_CANCELSTLAUNCHONGROUND= Cancel StLaunch On Ground
MODOPTIONS_EXTENDEDVARIANTS_CANCELSTLAUNCHONGROUND_HINT_1= Allows the player to exit StLaunch immediately when touching the ground.
MODOPTIONS_EXTENDEDVARIANTS_CANCELSTLAUNCHONGROUND_HINT_2= This allows jumping earlier in situations where the player is launched towards the ground.
1 change: 1 addition & 0 deletions Loenn/triggers/booleanExtendedVariantTrigger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ trigger.fieldInformation = {
"BadelineChasersEverywhere",
"BounceEverywhere",
"BufferableGrab",
"CancelStLaunchOnGround",
"ChangePatternsOfExistingBosses",
"ConsistentThrowing",
"CornerboostProtection",
Expand Down
2 changes: 2 additions & 0 deletions Module/ExtendedVariantsModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public enum Variant {
WaterSurfaceSpeedX, WaterSurfaceSpeedY, LiftboostCapX, LiftboostCapUp, LiftboostCapDown, AutoJump, SlowfallGravityMultiplier, SlowfallSpeedThreshold, AutoDash,
ConsistentThrowing, JumpBoost,
ClimbUpSpeed, ClimbDownSpeed, ClimbJumpStaminaCost, ClimbUpStaminaDrainRate, ClimbHoldStaminaDrainRate,
CancelStLaunchOnGround,

// vanilla variants
AirDashes, DashAssist, VanillaGameSpeed, Hiccups, InfiniteStamina, Invincible, InvisibleMotion, LowFriction, MirrorMode, NoGrabbing, PlayAsBadeline,
Expand Down Expand Up @@ -242,6 +243,7 @@ public ExtendedVariantsModule() {
VariantHandlers[Variant.ClimbJumpStaminaCost] = new ClimbJumpStaminaCost();
VariantHandlers[Variant.ClimbUpStaminaDrainRate] = new ClimbUpStaminaDrainRate();
VariantHandlers[Variant.ClimbHoldStaminaDrainRate] = new ClimbHoldStaminaDrainRate();
VariantHandlers[Variant.CancelStLaunchOnGround] = new CancelStLaunchOnGround();
// vanilla variants
VariantHandlers[Variant.AirDashes] = new AirDashes();
VariantHandlers[Variant.DashAssist] = new DashAssist();
Expand Down
4 changes: 2 additions & 2 deletions UI/ModOptionsEntries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ public void CreateAllOptions(VariantCategory category, bool includeMasterSwitch,

qualityOfLifeSubmenu.GetHighlightColor = () => getColorForVariantSubmenu(new List<Variant> {
Variant.UltraProtection, Variant.LiftboostProtection, Variant.CornerboostProtection, Variant.CrouchDashFix, Variant.AlternativeBuffering, Variant.MultiBuffering,
Variant.SaferDiagonalSmuggle, Variant.DashBeforePickup, Variant.ThrowIgnoresForcedMove, Variant.ConsistentThrowing
Variant.SaferDiagonalSmuggle, Variant.DashBeforePickup, Variant.ThrowIgnoresForcedMove, Variant.ConsistentThrowing, Variant.CancelStLaunchOnGround
});

elementsToHideOnToggle = new List<TextMenu.Item>() { resetVanillaVariants, resetExtendedVariants, title, movementSubmenu, gameElementsSubmenu, visualSubmenu, gameplayTweaksSubmenu, qualityOfLifeSubmenu };
Expand Down Expand Up @@ -808,7 +808,7 @@ public void CreateAllOptions(VariantCategory category, bool includeMasterSwitch,
if (category == VariantCategory.QualityOfLife) {
menu.Add(buildHeading(menu, "QUALITYOFLIFE"));

foreach (Variant variant in new Variant[] { Variant.UltraProtection, Variant.LiftboostProtection, Variant.CornerboostProtection, Variant.CrouchDashFix, Variant.AlternativeBuffering, Variant.ConsistentThrowing }) {
foreach (Variant variant in new Variant[] { Variant.UltraProtection, Variant.LiftboostProtection, Variant.CornerboostProtection, Variant.CrouchDashFix, Variant.AlternativeBuffering, Variant.ConsistentThrowing, Variant.CancelStLaunchOnGround }) {
TextMenuExt.OnOff option = getToggleOption(variant);
menu.Add(option);
option.AddDescription(menu, Dialog.Clean($"MODOPTIONS_EXTENDEDVARIANTS_{variant}_HINT_2"));
Expand Down
69 changes: 69 additions & 0 deletions Variants/CancelStLaunchOnGround.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Reflection;
using Celeste;
using Celeste.Mod;
using Microsoft.Xna.Framework;
using Mono.Cecil.Cil;
using MonoMod.Cil;

using static ExtendedVariants.Module.ExtendedVariantsModule;

namespace ExtendedVariants.Variants;

public class CancelStLaunchOnGround : AbstractExtendedVariant
{
private readonly FieldInfo f_Player_onGround = typeof(Player)
.GetField("onGround", BindingFlags.NonPublic | BindingFlags.Instance);

public CancelStLaunchOnGround() : base(variantType: typeof(bool), defaultVariantValue: false) { }

public override object ConvertLegacyVariantValue(int value) => value != 0;

public override void Load() => IL.Celeste.Player.LaunchUpdate += ILPlayerLaunchUpdate;

public override void Unload() => IL.Celeste.Player.LaunchUpdate -= ILPlayerLaunchUpdate;

private void ILPlayerLaunchUpdate(ILContext il)
{
ILCursor cur = new(il);

if (f_Player_onGround is null)
throw new Exception("Private field player.onGround hasn't been found!");

Logger.Log(LogLevel.Info, $"ExtendedVariantMode/{nameof(CancelStLaunchOnGround)}",
"Patching IL for Player.LaunchUpdate to add grounded condition"
);

/*
GroundedCheck(this.onGround) ||
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
if ( Speed.Length() < 220f)
return 0; // StNormal

return 7; // StLaunch
*/
ILLabel returnStNormalLabel = cur.DefineLabel();

cur.GotoNext(MoveType.AfterLabel,
instr => instr.MatchLdarg(0),
instr => instr.MatchLdflda<Player>(nameof(Player.Speed)),
instr => instr.MatchCall<Vector2>(nameof(Vector2.Length))
); // search forward for this.Speed.Length()

// Clone the cursor
ILCursor clone = cur.Clone();
clone.GotoNext(MoveType.AfterLabel, instr => instr.MatchLdcI4(0)); // search forward for 0 (StNormal)
clone.MarkLabel(returnStNormalLabel); // mark label here

// Back to the original cursor
cur.Emit(OpCodes.Ldarg_0); // this
cur.Emit(OpCodes.Ldfld, f_Player_onGround); // this.onGround
cur.EmitDelegate(GroundedCheck); // GroundedCheck(this.onGround)
cur.Emit(OpCodes.Brtrue_S, returnStNormalLabel); // go to that label if the check returns true

// Since we can't access private members of base game classes with this mod,
// we have to pass player.onGround as an argument.
bool GroundedCheck(bool player_onGround)
=> GetVariantValue<bool>(Variant.CancelStLaunchOnGround) && player_onGround;
}
}