Permalink
Switch branches/tags
Nothing to show
Find file Copy path
5f1147e Mar 1, 2018
1 contributor

Users who have contributed to this file

5472 lines (4632 sloc) 182 KB
using System;
using System.Collections;
using System.Collections.Generic;
using Monocle;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Celeste
{
[Tracked]
public class Player : Actor
{
#region Constants
public static ParticleType P_DashA;
public static ParticleType P_DashB;
public static ParticleType P_CassetteFly;
public static ParticleType P_Split;
public static ParticleType P_SummitLandA;
public static ParticleType P_SummitLandB;
public static ParticleType P_SummitLandC;
public const float MaxFall = 160f;
private const float Gravity = 900f;
private const float HalfGravThreshold = 40f;
private const float FastMaxFall = 240f;
private const float FastMaxAccel = 300f;
public const float MaxRun = 90f;
public const float RunAccel = 1000f;
private const float RunReduce = 400f;
private const float AirMult = .65f;
private const float HoldingMaxRun = 70f;
private const float HoldMinTime = .35f;
private const float BounceAutoJumpTime = .1f;
private const float DuckFriction = 500f;
private const int DuckCorrectCheck = 4;
private const float DuckCorrectSlide = 50f;
private const float DodgeSlideSpeedMult = 1.2f;
private const float DuckSuperJumpXMult = 1.25f;
private const float DuckSuperJumpYMult = .5f;
private const float JumpGraceTime = 0.1f;
private const float JumpSpeed = -105f;
private const float JumpHBoost = 40f;
private const float VarJumpTime = .2f;
private const float CeilingVarJumpGrace = .05f;
private const int UpwardCornerCorrection = 4;
private const float WallSpeedRetentionTime = .06f;
private const int WallJumpCheckDist = 3;
private const float WallJumpForceTime = .16f;
private const float WallJumpHSpeed = MaxRun + JumpHBoost;
public const float WallSlideStartMax = 20f;
private const float WallSlideTime = 1.2f;
private const float BounceVarJumpTime = .2f;
private const float BounceSpeed = -140f;
private const float SuperBounceVarJumpTime = .2f;
private const float SuperBounceSpeed = -185f;
private const float SuperJumpSpeed = JumpSpeed;
private const float SuperJumpH = 260f;
private const float SuperWallJumpSpeed = -160f;
private const float SuperWallJumpVarTime = .25f;
private const float SuperWallJumpForceTime = .2f;
private const float SuperWallJumpH = MaxRun + JumpHBoost * 2;
private const float DashSpeed = 240f;
private const float EndDashSpeed = 160f;
private const float EndDashUpMult = .75f;
private const float DashTime = .15f;
private const float DashCooldown = .2f;
private const float DashRefillCooldown = .1f;
private const int DashHJumpThruNudge = 6;
private const int DashCornerCorrection = 4;
private const int DashVFloorSnapDist = 3;
private const float DashAttackTime = .3f;
private const float BoostMoveSpeed = 80f;
public const float BoostTime = .25f;
private const float DuckWindMult = 0f;
private const int WindWallDistance = 3;
private const float ReboundSpeedX = 120f;
private const float ReboundSpeedY = -120f;
private const float ReboundVarJumpTime = .15f;
private const float ReflectBoundSpeed = 220f;
private const float DreamDashSpeed = DashSpeed;
private const int DreamDashEndWiggle = 5;
private const float DreamDashMinTime = .1f;
public const float ClimbMaxStamina = 110;
private const float ClimbUpCost = 100 / 2.2f;
private const float ClimbStillCost = 100 / 10f;
private const float ClimbJumpCost = 110 / 4f;
private const int ClimbCheckDist = 2;
private const int ClimbUpCheckDist = 2;
private const float ClimbNoMoveTime = .1f;
public const float ClimbTiredThreshold = 20f;
private const float ClimbUpSpeed = -45f;
private const float ClimbDownSpeed = 80f;
private const float ClimbSlipSpeed = 30f;
private const float ClimbAccel = 900f;
private const float ClimbGrabYMult = .2f;
private const float ClimbHopY = -120f;
private const float ClimbHopX = 100f;
private const float ClimbHopForceTime = .2f;
private const float ClimbJumpBoostTime = .2f;
private const float ClimbHopNoWindTime = .3f;
private const float LaunchSpeed = 280f;
private const float LaunchCancelThreshold = 220f;
private const float LiftYCap = -130f;
private const float LiftXCap = 250f;
private const float JumpThruAssistSpeed = -40f;
private const float InfiniteDashesTime = 2f;
private const float InfiniteDashesFirstTime = .5f;
private const float FlyPowerFlashTime = .5f;
private const float ThrowRecoil = 80f;
private static readonly Vector2 CarryOffsetTarget = new Vector2(0, -12);
private const float ChaserStateMaxTime = 4f;
public const float WalkSpeed = 64f;
public const int StNormal = 0;
public const int StClimb = 1;
public const int StDash = 2;
public const int StSwim = 3;
public const int StBoost = 4;
public const int StRedDash = 5;
public const int StHitSquash = 6;
public const int StLaunch = 7;
public const int StPickup = 8;
public const int StDreamDash = 9;
public const int StSummitLaunch = 10;
public const int StDummy = 11;
public const int StIntroWalk = 12;
public const int StIntroJump = 13;
public const int StIntroRespawn = 14;
public const int StIntroWakeUp = 15;
public const int StBirdDashTutorial = 16;
public const int StFrozen = 17;
public const int StReflectionFall = 18;
public const int StStarFly = 19;
public const int StTempleFall = 20;
public const int StCassetteFly = 21;
public const int StAttract = 22;
public const string TalkSfx = "player_talk";
#endregion
#region Vars
public Vector2 Speed;
public Facings Facing;
public PlayerSprite Sprite;
public PlayerHair Hair;
public StateMachine StateMachine;
public Vector2 CameraAnchor;
public bool CameraAnchorIgnoreX;
public bool CameraAnchorIgnoreY;
public Vector2 CameraAnchorLerp;
public bool ForceCameraUpdate;
public Leader Leader;
public VertexLight Light;
public int Dashes;
public float Stamina = ClimbMaxStamina;
public bool StrawberriesBlocked;
public Vector2 PreviousPosition;
public bool DummyAutoAnimate = true;
public Vector2 ForceStrongWindHair;
public Vector2? OverrideDashDirection;
public bool FlipInReflection = false;
public bool JustRespawned; // True if the player hasn't moved since respawning
public bool Dead { get; private set; }
private Level level;
private Collision onCollideH;
private Collision onCollideV;
private bool onGround;
private bool wasOnGround;
private int moveX;
private bool flash;
private bool wasDucking;
private float idleTimer;
private static Chooser<string> idleColdOptions = new Chooser<string>().Add("idleA", 5f).Add("idleB", 3f).Add("idleC", 1f);
private static Chooser<string> idleNoBackpackOptions = new Chooser<string>().Add("idleA", 1f).Add("idleB", 3f).Add("idleC", 3f);
private static Chooser<string> idleWarmOptions = new Chooser<string>().Add("idleA", 5f).Add("idleB", 3f);
public int StrawberryCollectIndex = 0;
public float StrawberryCollectResetTimer = 0f;
private Hitbox hurtbox;
private float jumpGraceTimer;
public bool AutoJump;
public float AutoJumpTimer;
private float varJumpSpeed;
private float varJumpTimer;
private int forceMoveX;
private float forceMoveXTimer;
private int hopWaitX; // If you climb hop onto a moving solid, snap to beside it until you get above it
private float hopWaitXSpeed;
private Vector2 lastAim;
private float dashCooldownTimer;
private float dashRefillCooldownTimer;
public Vector2 DashDir;
private float wallSlideTimer = WallSlideTime;
private int wallSlideDir;
private float climbNoMoveTimer;
private Vector2 carryOffset;
private Vector2 deadOffset;
private float introEase;
private float wallSpeedRetentionTimer; // If you hit a wall, start this timer. If coast is clear within this timer, retain h-speed
private float wallSpeedRetained;
private int wallBoostDir;
private float wallBoostTimer; // If you climb jump and then do a sideways input within this timer, switch to wall jump
private float maxFall;
private float dashAttackTimer;
private List<ChaserState> chaserStates;
private bool wasTired;
private HashSet<Trigger> triggersInside;
private float highestAirY;
private bool dashStartedOnGround;
private bool fastJump;
private int lastClimbMove;
private float noWindTimer;
private float dreamDashCanEndTimer;
private Solid climbHopSolid;
private Vector2 climbHopSolidPosition;
private SoundSource wallSlideSfx;
private SoundSource swimSurfaceLoopSfx;
private float playFootstepOnLand;
private float minHoldTimer;
public Booster CurrentBooster;
private Booster lastBooster;
private bool calledDashEvents;
private int lastDashes;
private Sprite sweatSprite;
private int startHairCount;
private bool launched;
private float launchedTimer;
private float dashTrailTimer;
private List<ChaserStateSound> activeSounds = new List<ChaserStateSound>();
private FMOD.Studio.EventInstance idleSfx;
private readonly Hitbox normalHitbox = new Hitbox(8, 11, -4, -11);
private readonly Hitbox duckHitbox = new Hitbox(8, 6, -4, -6);
private readonly Hitbox normalHurtbox = new Hitbox(8, 9, -4, -11);
private readonly Hitbox duckHurtbox = new Hitbox(8, 4, -4, -6);
private readonly Hitbox starFlyHitbox = new Hitbox(8, 8, -4, -10);
private readonly Hitbox starFlyHurtbox = new Hitbox(6, 6, -3, -9);
private Vector2 normalLightOffset = new Vector2(0, -8);
private Vector2 duckingLightOffset = new Vector2(0, -3);
private List<Entity> temp = new List<Entity>();
// hair
public static readonly Color NormalHairColor = Calc.HexToColor("AC3232");
public static readonly Color FlyPowerHairColor = Calc.HexToColor("F2EB6D");
public static readonly Color UsedHairColor = Calc.HexToColor("44B7FF");
public static readonly Color FlashHairColor = Color.White;
public static readonly Color TwoDashesHairColor = Calc.HexToColor("ff6def");
private float hairFlashTimer;
public Color? OverrideHairColor;
private Vector2 windDirection;
private float windTimeout;
private float windHairTimer;
// level-start intro
public enum IntroTypes { Transition, Respawn, WalkInRight, WalkInLeft, Jump, WakeUp, Fall, TempleMirrorVoid, None }
public IntroTypes IntroType;
private MirrorReflection reflection;
#endregion
#region constructor / added / removed
public Player(Vector2 position, PlayerSpriteMode spriteMode)
: base(new Vector2((int)position.X, (int)position.Y))
{
Depth = Depths.Player;
Tag = Tags.Persistent;
// sprite
Sprite = new PlayerSprite(spriteMode);
Add(Hair = new PlayerHair(Sprite));
Add(Sprite);
Hair.Color = NormalHairColor;
startHairCount = Sprite.HairCount;
// sweat sprite
sweatSprite = GFX.SpriteBank.Create("player_sweat");
Add(sweatSprite);
// physics
Collider = normalHitbox;
hurtbox = normalHurtbox;
onCollideH = OnCollideH;
onCollideV = OnCollideV;
// states
StateMachine = new StateMachine(23);
StateMachine.SetCallbacks(StNormal, NormalUpdate, null, NormalBegin, NormalEnd);
StateMachine.SetCallbacks(StClimb, ClimbUpdate, null, ClimbBegin, ClimbEnd);
StateMachine.SetCallbacks(StDash, DashUpdate, DashCoroutine, DashBegin, DashEnd);
StateMachine.SetCallbacks(StSwim, SwimUpdate, null, SwimBegin, null);
StateMachine.SetCallbacks(StBoost, BoostUpdate, BoostCoroutine, BoostBegin, BoostEnd);
StateMachine.SetCallbacks(StRedDash, RedDashUpdate, RedDashCoroutine, RedDashBegin, RedDashEnd);
StateMachine.SetCallbacks(StHitSquash, HitSquashUpdate, null, HitSquashBegin, null);
StateMachine.SetCallbacks(StLaunch, LaunchUpdate, null, LaunchBegin, null);
StateMachine.SetCallbacks(StPickup, null, PickupCoroutine, null, null);
StateMachine.SetCallbacks(StDreamDash, DreamDashUpdate, null, DreamDashBegin, DreamDashEnd);
StateMachine.SetCallbacks(StSummitLaunch, SummitLaunchUpdate, null, SummitLaunchBegin, null);
StateMachine.SetCallbacks(StDummy, DummyUpdate, null, DummyBegin, null);
StateMachine.SetCallbacks(StIntroWalk, null, IntroWalkCoroutine, null, null);
StateMachine.SetCallbacks(StIntroJump, null, IntroJumpCoroutine, null, null);
StateMachine.SetCallbacks(StIntroRespawn, null, null, IntroRespawnBegin, IntroRespawnEnd);
StateMachine.SetCallbacks(StIntroWakeUp, null, IntroWakeUpCoroutine, null, null);
StateMachine.SetCallbacks(StTempleFall, TempleFallUpdate, TempleFallCoroutine);
StateMachine.SetCallbacks(StReflectionFall, ReflectionFallUpdate, ReflectionFallCoroutine, ReflectionFallBegin, ReflectionFallEnd);
StateMachine.SetCallbacks(StBirdDashTutorial, BirdDashTutorialUpdate, BirdDashTutorialCoroutine, BirdDashTutorialBegin, null);
StateMachine.SetCallbacks(StFrozen, FrozenUpdate, null, null, null);
StateMachine.SetCallbacks(StStarFly, StarFlyUpdate, StarFlyCoroutine, StarFlyBegin, StarFlyEnd);
StateMachine.SetCallbacks(StCassetteFly, CassetteFlyUpdate, CassetteFlyCoroutine, CassetteFlyBegin, CassetteFlyEnd);
StateMachine.SetCallbacks(StAttract, AttractUpdate, null, AttractBegin, AttractEnd);
Add(StateMachine);
// other stuff
Add(Leader = new Leader(new Vector2(0, -8)));
lastAim = Vector2.UnitX;
Facing = Facings.Right;
chaserStates = new List<ChaserState>();
triggersInside = new HashSet<Trigger>();
Add(Light = new VertexLight(normalLightOffset, Color.White, 1f, 32, 64));
Add(new WaterInteraction(() => { return StateMachine.State == StDash || StateMachine.State == StReflectionFall; }));
//Wind
Add(new WindMover(WindMove));
Add(wallSlideSfx = new SoundSource());
Add(swimSurfaceLoopSfx = new SoundSource());
Sprite.OnFrameChange = (anim) =>
{
if (Scene != null && !Dead)
{
// footsteps
var frame = Sprite.CurrentAnimationFrame;
if ((anim.Equals(PlayerSprite.RunCarry) && (frame == 0 || frame == 6)) ||
(anim.Equals(PlayerSprite.RunFast) && (frame == 0 || frame == 6)) ||
(anim.Equals(PlayerSprite.RunSlow) && (frame == 0 || frame == 6)) ||
(anim.Equals(PlayerSprite.Walk) && (frame == 0 || frame == 6)) ||
(anim.Equals(PlayerSprite.RunStumble) && frame == 6) ||
(anim.Equals(PlayerSprite.Flip) && frame == 4) ||
(anim.Equals(PlayerSprite.RunWind) && (frame == 0 || frame == 6)) ||
(anim.Equals("idleC") && Sprite.Mode == PlayerSpriteMode.MadelineNoBackpack && (frame == 3 || frame == 6 || frame == 8 || frame == 11)) ||
(anim.Equals("carryTheoWalk") && (frame == 0 || frame == 6)))
{
var landed = SurfaceIndex.GetPlatformByPriority(CollideAll<Platform>(Position + Vector2.UnitY, temp));
if (landed != null)
Play(Sfxs.char_mad_footstep, SurfaceIndex.Param, landed.GetStepSoundIndex(this));
}
// climbing (holds)
else if ((anim.Equals(PlayerSprite.ClimbUp) && (frame == 5)) ||
(anim.Equals(PlayerSprite.ClimbDown) && (frame == 5)))
{
var holding = SurfaceIndex.GetPlatformByPriority(CollideAll<Solid>(Center + Vector2.UnitX * (int)Facing, temp));
if (holding != null)
Play(Sfxs.char_mad_handhold, SurfaceIndex.Param, holding.GetWallSoundIndex(this, (int)Facing));
}
else if (anim.Equals("wakeUp") && frame == 19)
Play(Sfxs.char_mad_campfire_stand);
else if (anim.Equals("sitDown") && frame == 12)
Play(Sfxs.char_mad_summit_sit);
else if (anim.Equals("push") && (frame == 8 || frame == 15))
Dust.BurstFG(Position + new Vector2(-(int)Facing * 5, -1), new Vector2(-(int)Facing, -0.5f).Angle(), 1, 0);
}
};
Sprite.OnLastFrame = (anim) =>
{
if (Scene != null && !Dead && Sprite.CurrentAnimationID == "idle" && !level.InCutscene && idleTimer > 3f)
{
if (Calc.Random.Chance(0.2f))
{
var next = "";
if (Sprite.Mode == PlayerSpriteMode.Madeline)
next = (level.CoreMode == Session.CoreModes.Hot ? idleWarmOptions : idleColdOptions).Choose();
else
next = idleNoBackpackOptions.Choose();
if (!string.IsNullOrEmpty(next))
{
Sprite.Play(next);
if (Sprite.Mode == PlayerSpriteMode.Madeline)
{
if (next == "idleB")
idleSfx = Play(Sfxs.char_mad_idle_scratch);
else if (next == "idleC")
idleSfx = Play(Sfxs.char_mad_idle_sneeze);
}
else if (next == "idleA")
idleSfx = Play(Sfxs.char_mad_idle_crackknuckles);
}
}
}
};
// cancel special idle sounds if the anim changed
Sprite.OnChange = (last, next) =>
{
if ((last == "idleB" || last == "idleC") && next != null && !next.StartsWith("idle") && idleSfx != null)
Audio.Stop(idleSfx);
};
Add(reflection = new MirrorReflection());
}
public override void Added(Scene scene)
{
base.Added(scene);
level = SceneAs<Level>();
lastDashes = Dashes = MaxDashes;
if (X > level.Bounds.Center.X && IntroType != IntroTypes.None)
Facing = Facings.Left;
switch (IntroType)
{
case IntroTypes.Respawn:
StateMachine.State = StIntroRespawn;
JustRespawned = true;
break;
case IntroTypes.WalkInRight:
IntroWalkDirection = Facings.Right;
StateMachine.State = StIntroWalk;
break;
case IntroTypes.WalkInLeft:
IntroWalkDirection = Facings.Left;
StateMachine.State = StIntroWalk;
break;
case IntroTypes.Jump:
StateMachine.State = StIntroJump;
break;
case IntroTypes.WakeUp:
Sprite.Play("asleep");
Facing = Facings.Right;
StateMachine.State = StIntroWakeUp;
break;
case IntroTypes.None:
StateMachine.State = StNormal;
break;
case IntroTypes.Fall:
StateMachine.State = StReflectionFall;
break;
case IntroTypes.TempleMirrorVoid:
StartTempleMirrorVoidSleep();
break;
}
IntroType = IntroTypes.Transition;
StartHair();
PreviousPosition = Position;
}
public void StartTempleMirrorVoidSleep()
{
Sprite.Play("asleep");
Facing = Facings.Right;
StateMachine.State = StDummy;
StateMachine.Locked = true;
DummyAutoAnimate = false;
DummyGravity = false;
}
public override void Removed(Scene scene)
{
base.Removed(scene);
level = null;
Audio.Stop(conveyorLoopSfx);
foreach (var trigger in triggersInside)
{
trigger.Triggered = false;
trigger.OnLeave(this);
}
}
public override void SceneEnd(Scene scene)
{
base.SceneEnd(scene);
Audio.Stop(conveyorLoopSfx);
}
#endregion
#region Rendering
public override void Render()
{
var was = Sprite.RenderPosition;
Sprite.RenderPosition = Sprite.RenderPosition.Floor();
if (StateMachine.State == StIntroRespawn)
{
DeathEffect.Draw(Center + deadOffset, Hair.Color, introEase);
}
else
{
if (StateMachine.State != StStarFly)
{
if (IsTired && flash)
Sprite.Color = Color.Red;
else
Sprite.Color = Color.White;
}
// set up scale
if (reflection.IsRendering && FlipInReflection)
{
Facing = (Facings)(-(int)Facing);
Hair.Facing = Facing;
}
Sprite.Scale.X *= (int)Facing;
// sweat scale
if (sweatSprite.LastAnimationID == "idle")
sweatSprite.Scale = Sprite.Scale;
else
{
sweatSprite.Scale.Y = Sprite.Scale.Y;
sweatSprite.Scale.X = Math.Abs(Sprite.Scale.X) * Math.Sign(sweatSprite.Scale.X);
}
// draw
base.Render();
// star fly transform
if (Sprite.CurrentAnimationID == PlayerSprite.StartStarFly)
{
var p = (Sprite.CurrentAnimationFrame / (float)Sprite.CurrentAnimationTotalFrames);
var white = GFX.Game.GetAtlasSubtexturesAt("characters/player/startStarFlyWhite", Sprite.CurrentAnimationFrame);
white.Draw(Sprite.RenderPosition, Sprite.Origin, starFlyColor * p, Sprite.Scale, Sprite.Rotation, 0);
}
// revert scale
Sprite.Scale.X *= (int)Facing;
if (reflection.IsRendering && FlipInReflection)
{
Facing = (Facings)(-(int)Facing);
Hair.Facing = Facing;
}
}
Sprite.RenderPosition = was;
}
public override void DebugRender(Camera camera)
{
base.DebugRender(camera);
{
Collider was = Collider;
Collider = hurtbox;
Draw.HollowRect(Collider, Color.Lime);
Collider = was;
}
}
#endregion
#region Updating
public override void Update()
{
//Infinite Stamina variant
if (SaveData.Instance.AssistMode && SaveData.Instance.Assists.InfiniteStamina)
Stamina = ClimbMaxStamina;
PreviousPosition = Position;
//Vars
{
// strawb reset timer
StrawberryCollectResetTimer -= Engine.DeltaTime;
if (StrawberryCollectResetTimer <= 0)
StrawberryCollectIndex = 0;
// idle timer
idleTimer += Engine.DeltaTime;
if (level != null && level.InCutscene)
idleTimer = -5;
else if (Speed.X != 0 || Speed.Y != 0)
idleTimer = 0;
//Underwater music
if (!Dead)
Audio.MusicUnderwater = UnderwaterMusicCheck();
//Just respawned
if (JustRespawned && Speed != Vector2.Zero)
JustRespawned = false;
//Get ground
if (StateMachine.State == StDreamDash)
onGround = OnSafeGround = false;
else if (Speed.Y >= 0)
{
Platform first = CollideFirst<Solid>(Position + Vector2.UnitY);
if (first == null)
first = CollideFirstOutside<JumpThru>(Position + Vector2.UnitY);
if (first != null)
{
onGround = true;
OnSafeGround = first.Safe;
}
else
onGround = OnSafeGround = false;
}
else
onGround = OnSafeGround = false;
if (StateMachine.State == StSwim)
OnSafeGround = true;
//Safe Ground Blocked?
if (OnSafeGround)
{
foreach (SafeGroundBlocker blocker in Scene.Tracker.GetComponents<SafeGroundBlocker>())
{
if (blocker.Check(this))
{
OnSafeGround = false;
break;
}
}
}
playFootstepOnLand -= Engine.DeltaTime;
//Highest Air Y
if (onGround)
highestAirY = Y;
else
highestAirY = Math.Min(Y, highestAirY);
//Flashing
if (Scene.OnInterval(.05f))
flash = !flash;
//Wall Slide
if (wallSlideDir != 0)
{
wallSlideTimer = Math.Max(wallSlideTimer - Engine.DeltaTime, 0);
wallSlideDir = 0;
}
//Wall Boost
if (wallBoostTimer > 0)
{
wallBoostTimer -= Engine.DeltaTime;
if (moveX == wallBoostDir)
{
Speed.X = WallJumpHSpeed * moveX;
Stamina += ClimbJumpCost;
wallBoostTimer = 0;
sweatSprite.Play("idle");
}
}
//After Dash
if (onGround && StateMachine.State != StClimb)
{
AutoJump = false;
Stamina = ClimbMaxStamina;
wallSlideTimer = WallSlideTime;
}
//Dash Attack
if (dashAttackTimer > 0)
dashAttackTimer -= Engine.DeltaTime;
//Jump Grace
if (onGround)
{
dreamJump = false;
jumpGraceTimer = JumpGraceTime;
}
else if (jumpGraceTimer > 0)
jumpGraceTimer -= Engine.DeltaTime;
//Dashes
{
if (dashCooldownTimer > 0)
dashCooldownTimer -= Engine.DeltaTime;
if (dashRefillCooldownTimer > 0)
dashRefillCooldownTimer -= Engine.DeltaTime;
else if (SaveData.Instance.AssistMode && SaveData.Instance.Assists.DashMode == Assists.DashModes.Infinite && !level.InCutscene)
RefillDash();
else if (!Inventory.NoRefills)
{
if (StateMachine.State == StSwim)
RefillDash();
else if (onGround)
if (CollideCheck<Solid, NegaBlock>(Position + Vector2.UnitY) || CollideCheckOutside<JumpThru>(Position + Vector2.UnitY))
if (!CollideCheck<Spikes>(Position) || (SaveData.Instance.AssistMode && SaveData.Instance.Assists.Invincible))
RefillDash();
}
}
//Var Jump
if (varJumpTimer > 0)
varJumpTimer -= Engine.DeltaTime;
//Auto Jump
if (AutoJumpTimer > 0)
{
if (AutoJump)
{
AutoJumpTimer -= Engine.DeltaTime;
if (AutoJumpTimer <= 0)
AutoJump = false;
}
else
AutoJumpTimer = 0;
}
//Force Move X
if (forceMoveXTimer > 0)
{
forceMoveXTimer -= Engine.DeltaTime;
moveX = forceMoveX;
}
else
{
moveX = Input.MoveX.Value;
climbHopSolid = null;
}
//Climb Hop Solid Movement
if (climbHopSolid != null && !climbHopSolid.Collidable)
climbHopSolid = null;
else if (climbHopSolid != null && climbHopSolid.Position != climbHopSolidPosition)
{
var move = climbHopSolid.Position - climbHopSolidPosition;
climbHopSolidPosition = climbHopSolid.Position;
MoveHExact((int)move.X);
MoveVExact((int)move.Y);
}
//Wind
if (noWindTimer > 0)
noWindTimer -= Engine.DeltaTime;
//Facing
if (moveX != 0 && InControl
&& StateMachine.State != StClimb && StateMachine.State != StPickup && StateMachine.State != StRedDash && StateMachine.State != StHitSquash)
{
var to = (Facings)moveX;
if (to != Facing && Ducking)
Sprite.Scale = new Vector2(0.8f, 1.2f);
Facing = to;
}
//Aiming
lastAim = Input.GetAimVector(Facing);
//Wall Speed Retention
if (wallSpeedRetentionTimer > 0)
{
if (Math.Sign(Speed.X) == -Math.Sign(wallSpeedRetained))
wallSpeedRetentionTimer = 0;
else if (!CollideCheck<Solid>(Position + Vector2.UnitX * Math.Sign(wallSpeedRetained)))
{
Speed.X = wallSpeedRetained;
wallSpeedRetentionTimer = 0;
}
else
wallSpeedRetentionTimer -= Engine.DeltaTime;
}
//Hop Wait X
if (hopWaitX != 0)
{
if (Math.Sign(Speed.X) == -hopWaitX || Speed.Y > 0)
hopWaitX = 0;
else if (!CollideCheck<Solid>(Position + Vector2.UnitX * hopWaitX))
{
Speed.X = hopWaitXSpeed;
hopWaitX = 0;
}
}
// Wind Timeout
if (windTimeout > 0)
windTimeout -= Engine.DeltaTime;
// Hair
{
var windDir = windDirection;
if (ForceStrongWindHair.Length() > 0)
windDir = ForceStrongWindHair;
if (windTimeout > 0 && windDir.X != 0)
{
windHairTimer += Engine.DeltaTime * 8f;
Hair.StepPerSegment = new Vector2(windDir.X * 5f, (float)Math.Sin(windHairTimer));
Hair.StepInFacingPerSegment = 0f;
Hair.StepApproach = 128f;
Hair.StepYSinePerSegment = 0;
}
else if (Dashes > 1)
{
Hair.StepPerSegment = new Vector2((float)Math.Sin(Scene.TimeActive * 2) * 0.7f - (int)Facing * 3, (float)Math.Sin(Scene.TimeActive * 1f));
Hair.StepInFacingPerSegment = 0f;
Hair.StepApproach = 90f;
Hair.StepYSinePerSegment = 1f;
Hair.StepPerSegment.Y += windDir.Y * 2f;
}
else
{
Hair.StepPerSegment = new Vector2(0, 2f);
Hair.StepInFacingPerSegment = 0.5f;
Hair.StepApproach = 64f;
Hair.StepYSinePerSegment = 0;
Hair.StepPerSegment.Y += windDir.Y * 0.5f;
}
}
if (StateMachine.State == StRedDash)
Sprite.HairCount = 1;
else if (StateMachine.State != StStarFly)
Sprite.HairCount = (Dashes > 1 ? 5 : startHairCount);
//Min Hold Time
if (minHoldTimer > 0)
minHoldTimer -= Engine.DeltaTime;
//Launch Particles
if (launched)
{
var sq = Speed.LengthSquared();
if (sq < LaunchedMinSpeedSq)
launched = false;
else
{
var was = launchedTimer;
launchedTimer += Engine.DeltaTime;
if (launchedTimer >= .5f)
{
launched = false;
launchedTimer = 0;
}
else if (Calc.OnInterval(launchedTimer, was, .15f))
level.Add(Engine.Pooler.Create<SpeedRing>().Init(Center, Speed.Angle(), Color.White));
}
}
else
launchedTimer = 0;
}
if (IsTired)
{
Input.Rumble(RumbleStrength.Light, RumbleLength.Short);
if (!wasTired)
{
wasTired = true;
}
}
else
wasTired = false;
base.Update();
//Light Offset
if (Ducking)
Light.Position = duckingLightOffset;
else
Light.Position = normalLightOffset;
//Jump Thru Assist
if (!onGround && Speed.Y <= 0 && (StateMachine.State != StClimb || lastClimbMove == -1) && CollideCheck<JumpThru>() && !JumpThruBoostBlockedCheck())
MoveV(JumpThruAssistSpeed * Engine.DeltaTime);
//Dash Floor Snapping
if (!onGround && DashAttacking && DashDir.Y == 0)
{
if (CollideCheck<Solid>(Position + Vector2.UnitY * DashVFloorSnapDist) || CollideCheckOutside<JumpThru>(Position + Vector2.UnitY * DashVFloorSnapDist))
MoveVExact(DashVFloorSnapDist);
}
//Falling unducking
if (Speed.Y > 0 && CanUnDuck && Collider != starFlyHitbox && !onGround)
Ducking = false;
//Physics
if (StateMachine.State != StDreamDash && StateMachine.State != StAttract)
MoveH(Speed.X * Engine.DeltaTime, onCollideH);
if (StateMachine.State != StDreamDash && StateMachine.State != StAttract)
MoveV(Speed.Y * Engine.DeltaTime, onCollideV);
//Swimming
if (StateMachine.State == StSwim)
{
//Stay at water surface
if (Speed.Y < 0 && Speed.Y >= SwimMaxRise)
{
while (!SwimCheck())
{
Speed.Y = 0;
if (MoveVExact(1))
break;
}
}
}
else if (StateMachine.State == StNormal && SwimCheck())
StateMachine.State = StSwim;
else if (StateMachine.State == StClimb && SwimCheck())
{
var water = CollideFirst<Water>(Position);
if (water != null && Center.Y < water.Center.Y)
{
while (SwimCheck())
if (MoveVExact(-1))
break;
if (SwimCheck())
StateMachine.State = StSwim;
}
else
StateMachine.State = StSwim;
}
// wall slide SFX
{
var isSliding = Sprite.CurrentAnimationID != null && Sprite.CurrentAnimationID.Equals(PlayerSprite.WallSlide) && Speed.Y > 0;
if (isSliding)
{
if (!wallSlideSfx.Playing)
Loop(wallSlideSfx, Sfxs.char_mad_wallslide);
var platform = SurfaceIndex.GetPlatformByPriority(CollideAll<Solid>(Center + Vector2.UnitX * (int)Facing, temp));
if (platform != null)
wallSlideSfx.Param(SurfaceIndex.Param, platform.GetWallSoundIndex(this, (int)Facing));
}
else
Stop(wallSlideSfx);
}
// update sprite
UpdateSprite();
//Carry held item
UpdateCarry();
//Triggers
if (StateMachine.State != StReflectionFall)
{
foreach (Trigger trigger in Scene.Tracker.GetEntities<Trigger>())
{
if (CollideCheck(trigger))
{
if (!trigger.Triggered)
{
trigger.Triggered = true;
triggersInside.Add(trigger);
trigger.OnEnter(this);
}
trigger.OnStay(this);
}
else if (trigger.Triggered)
{
triggersInside.Remove(trigger);
trigger.Triggered = false;
trigger.OnLeave(this);
}
}
}
//Strawberry Block
StrawberriesBlocked = CollideCheck<BlockField>();
// Camera (lerp by distance using delta-time)
if (InControl || ForceCameraUpdate)
{
if (StateMachine.State == StReflectionFall)
{
level.Camera.Position = CameraTarget;
}
else
{
var from = level.Camera.Position;
var target = CameraTarget;
var multiplier = StateMachine.State == StTempleFall ? 8 : 1f;
level.Camera.Position = from + (target - from) * (1f - (float)Math.Pow(0.01f / multiplier, Engine.DeltaTime));
}
}
//Player Colliders
if (!Dead && StateMachine.State != StCassetteFly)
{
Collider was = Collider;
Collider = hurtbox;
foreach (PlayerCollider pc in Scene.Tracker.GetComponents<PlayerCollider>())
{
if (pc.Check(this) && Dead)
{
Collider = was;
return;
}
}
// If the current collider is not the hurtbox we set it to, that means a collision callback changed it. Keep the new one!
bool keepNew = (Collider != hurtbox);
if (!keepNew)
Collider = was;
}
//Bounds
if (InControl && !Dead && StateMachine.State != StDreamDash)
level.EnforceBounds(this);
UpdateChaserStates();
UpdateHair(true);
//Sounds on ducking state change
if (wasDucking != Ducking)
{
wasDucking = Ducking;
if (wasDucking)
Play(Sfxs.char_mad_duck);
else if (onGround)
Play(Sfxs.char_mad_stand);
}
// shallow swim sfx
if (Speed.X != 0 && ((StateMachine.State == StSwim && !SwimUnderwaterCheck()) || (StateMachine.State == StNormal && CollideCheck<Water>(Position))))
{
if (!swimSurfaceLoopSfx.Playing)
swimSurfaceLoopSfx.Play(Sfxs.char_mad_water_move_shallow);
}
else
swimSurfaceLoopSfx.Stop();
wasOnGround = onGround;
}
private void CreateTrail()
{
TrailManager.Add(this, wasDashB ? NormalHairColor : UsedHairColor);
}
public void CleanUpTriggers()
{
if (triggersInside.Count > 0)
{
foreach (var trigger in triggersInside)
{
trigger.OnLeave(this);
trigger.Triggered = false;
}
triggersInside.Clear();
}
}
private void UpdateChaserStates()
{
while (chaserStates.Count > 0 && Scene.TimeActive - chaserStates[0].TimeStamp > ChaserStateMaxTime)
chaserStates.RemoveAt(0);
chaserStates.Add(new ChaserState(this));
activeSounds.Clear();
}
#endregion
#region Hair & Sprite
private void StartHair()
{
Hair.Facing = Facing;
Hair.Start();
UpdateHair(true);
}
public void UpdateHair(bool applyGravity)
{
// color
if (StateMachine.State == StStarFly)
{
Hair.Color = Sprite.Color;
applyGravity = false;
}
else if (Dashes == 0 && Dashes < MaxDashes)
Hair.Color = Color.Lerp(Hair.Color, UsedHairColor, 6f * Engine.DeltaTime);
else
{
Color color;
if (lastDashes != Dashes)
{
color = FlashHairColor;
hairFlashTimer = .12f;
}
else if (hairFlashTimer > 0)
{
color = FlashHairColor;
hairFlashTimer -= Engine.DeltaTime;
}
else if (Dashes == 2)
color = TwoDashesHairColor;
else
color = NormalHairColor;
Hair.Color = color;
}
if (OverrideHairColor != null)
Hair.Color = OverrideHairColor.Value;
Hair.Facing = Facing;
Hair.SimulateMotion = applyGravity;
lastDashes = Dashes;
}
private void UpdateSprite()
{
//Tween
Sprite.Scale.X = Calc.Approach(Sprite.Scale.X, 1f, 1.75f * Engine.DeltaTime);
Sprite.Scale.Y = Calc.Approach(Sprite.Scale.Y, 1f, 1.75f * Engine.DeltaTime);
//Animation
if (InControl && Sprite.CurrentAnimationID != PlayerSprite.Throw && StateMachine.State != StTempleFall &&
StateMachine.State != StReflectionFall && StateMachine.State != StStarFly && StateMachine.State != StCassetteFly)
{
if (StateMachine.State == StAttract)
{
Sprite.Play(PlayerSprite.FallFast);
}
else if (StateMachine.State == StSummitLaunch)
{
Sprite.Play(PlayerSprite.Launch);
}
// picking up
else if (StateMachine.State == StPickup)
{
Sprite.Play(PlayerSprite.PickUp);
}
// swiming
else if (StateMachine.State == StSwim)
{
if (Input.MoveY.Value > 0)
Sprite.Play(PlayerSprite.SwimDown);
else if (Input.MoveY.Value < 0)
Sprite.Play(PlayerSprite.SwimUp);
else
Sprite.Play(PlayerSprite.SwimIdle);
}
// dream dashing
else if (StateMachine.State == StDreamDash)
{
if (Sprite.CurrentAnimationID != PlayerSprite.DreamDashIn && Sprite.CurrentAnimationID != PlayerSprite.DreamDashLoop)
Sprite.Play(PlayerSprite.DreamDashIn);
}
else if (Sprite.DreamDashing && Sprite.LastAnimationID != PlayerSprite.DreamDashOut)
{
Sprite.Play(PlayerSprite.DreamDashOut);
}
else if (Sprite.CurrentAnimationID != PlayerSprite.DreamDashOut)
{
// during dash
if (DashAttacking)
{
if (onGround && DashDir.Y == 0 && !Ducking && Speed.X != 0 && moveX == -Math.Sign(Speed.X))
{
if (Scene.OnInterval(.02f))
Dust.Burst(Position, Calc.Up, 1);
Sprite.Play(PlayerSprite.Skid);
}
else
Sprite.Play(PlayerSprite.Dash);
}
// climbing
else if (StateMachine.State == StClimb)
{
if (lastClimbMove < 0)
Sprite.Play(PlayerSprite.ClimbUp);
else if (lastClimbMove > 0)
Sprite.Play(PlayerSprite.WallSlide);
else if (!CollideCheck<Solid>(Position + new Vector2((int)Facing, 6)))
Sprite.Play(PlayerSprite.Dangling);
else if (Input.MoveX == -(int)Facing)
{
if (Sprite.CurrentAnimationID != PlayerSprite.ClimbLookBack)
Sprite.Play(PlayerSprite.ClimbLookBackStart);
}
else
Sprite.Play(PlayerSprite.WallSlide);
}
// ducking
else if (Ducking && StateMachine.State == StNormal)
{
Sprite.Play(PlayerSprite.Duck);
}
else if (onGround)
{
fastJump = false;
if (Holding == null && moveX != 0 && CollideCheck<Solid>(Position + Vector2.UnitX * moveX))
{
Sprite.Play("push");
}
else if (Math.Abs(Speed.X) <= RunAccel / 40f && moveX == 0)
{
if (Holding != null)
{
Sprite.Play(PlayerSprite.IdleCarry);
}
else if (!Scene.CollideCheck<Solid>(Position + new Vector2((int)Facing * 1, 2)) && !Scene.CollideCheck<Solid>(Position + new Vector2((int)Facing * 4, 2)) && !CollideCheck<JumpThru>(Position + new Vector2((int)Facing * 4, 2)))
{
Sprite.Play(PlayerSprite.FrontEdge);
}
else if (!Scene.CollideCheck<Solid>(Position + new Vector2(-(int)Facing * 1, 2)) && !Scene.CollideCheck<Solid>(Position + new Vector2(-(int)Facing * 4, 2)) && !CollideCheck<JumpThru>(Position + new Vector2(-(int)Facing * 4, 2)))
{
Sprite.Play("edgeBack");
}
else if (Input.MoveY.Value == -1)
{
if (Sprite.LastAnimationID != PlayerSprite.LookUp)
Sprite.Play(PlayerSprite.LookUp);
}
else
{
if (Sprite.CurrentAnimationID != null && !Sprite.CurrentAnimationID.Contains("idle"))
Sprite.Play(PlayerSprite.Idle);
}
}
else if (Holding != null)
{
Sprite.Play(PlayerSprite.RunCarry);
}
else if (Math.Sign(Speed.X) == -moveX && moveX != 0)
{
if (Math.Abs(Speed.X) > MaxRun)
Sprite.Play(PlayerSprite.Skid);
else if (Sprite.CurrentAnimationID != PlayerSprite.Skid)
Sprite.Play(PlayerSprite.Flip);
}
else if (windDirection.X != 0 && windTimeout > 0f && (int)Facing == -Math.Sign(windDirection.X))
{
Sprite.Play(PlayerSprite.RunWind);
}
else if (!Sprite.Running)
{
if (Math.Abs(Speed.X) < MaxRun * .5f)
Sprite.Play(PlayerSprite.RunSlow);
else
Sprite.Play(PlayerSprite.RunFast);
}
}
// wall sliding
else if (wallSlideDir != 0 && Holding == null)
{
Sprite.Play(PlayerSprite.WallSlide);
}
// jumping up
else if (Speed.Y < 0)
{
if (Holding != null)
{
Sprite.Play(PlayerSprite.JumpCarry);
}
else if (fastJump || Math.Abs(Speed.X) > MaxRun)
{
fastJump = true;
Sprite.Play(PlayerSprite.JumpFast);
}
else
Sprite.Play(PlayerSprite.JumpSlow);
}
// falling down
else
{
if (Holding != null)
{
Sprite.Play(PlayerSprite.FallCarry);
}
else if (fastJump || Speed.Y >= MaxFall || level.InSpace)
{
fastJump = true;
if (Sprite.LastAnimationID != PlayerSprite.FallFast)
Sprite.Play(PlayerSprite.FallFast);
}
else
Sprite.Play(PlayerSprite.FallSlow);
}
}
}
if (StateMachine.State != Player.StDummy)
{
if (level.InSpace)
Sprite.Rate = .5f;
else
Sprite.Rate = 1f;
}
}
public void CreateSplitParticles()
{
level.Particles.Emit(P_Split, 16, Center, Vector2.One * 6);
}
#endregion
#region Getters
public Vector2 CameraTarget
{
get
{
Vector2 at = new Vector2();
Vector2 target = new Vector2(X - Celeste.GameWidth / 2, Y - Celeste.GameHeight / 2);
if (StateMachine.State != StReflectionFall)
target += new Vector2(level.CameraOffset.X, level.CameraOffset.Y);
if (StateMachine.State == StStarFly)
{
target.X += .2f * Speed.X;
target.Y += .2f * Speed.Y;
}
else if (StateMachine.State == StRedDash)
{
target.X += 48 * Math.Sign(Speed.X);
target.Y += 48 * Math.Sign(Speed.Y);
}
else if (StateMachine.State == StSummitLaunch)
{
target.Y -= 64;
}
else if (StateMachine.State == StReflectionFall)
{
target.Y += 32;
}
if (CameraAnchorLerp.Length() > 0)
{
if (CameraAnchorIgnoreX && !CameraAnchorIgnoreY)
target.Y = MathHelper.Lerp(target.Y, CameraAnchor.Y, CameraAnchorLerp.Y);
else if (!CameraAnchorIgnoreX && CameraAnchorIgnoreY)
target.X = MathHelper.Lerp(target.X, CameraAnchor.X, CameraAnchorLerp.X);
else if (CameraAnchorLerp.X == CameraAnchorLerp.Y)
target = Vector2.Lerp(target, CameraAnchor, CameraAnchorLerp.X);
else
{
target.X = MathHelper.Lerp(target.X, CameraAnchor.X, CameraAnchorLerp.X);
target.Y = MathHelper.Lerp(target.Y, CameraAnchor.Y, CameraAnchorLerp.Y);
}
}
at.X = MathHelper.Clamp(target.X, level.Bounds.Left, level.Bounds.Right - Celeste.GameWidth);
at.Y = MathHelper.Clamp(target.Y, level.Bounds.Top, level.Bounds.Bottom - Celeste.GameHeight);
if (level.CameraLockMode != Level.CameraLockModes.None)
{
var locker = Scene.Tracker.GetComponent<CameraLocker>();
//X Snapping
if (level.CameraLockMode != Level.CameraLockModes.BoostSequence)
{
at.X = Math.Max(at.X, level.Camera.X);
if (locker != null)
at.X = Math.Min(at.X, Math.Max(level.Bounds.Left, locker.Entity.X - locker.MaxXOffset));
}
//Y Snapping
if (level.CameraLockMode == Level.CameraLockModes.FinalBoss)
{
at.Y = Math.Max(at.Y, level.Camera.Y);
if (locker != null)
at.Y = Math.Min(at.Y, Math.Max(level.Bounds.Top, locker.Entity.Y - locker.MaxYOffset));
}
else if (level.CameraLockMode == Level.CameraLockModes.BoostSequence)
{
level.CameraUpwardMaxY = Math.Min(level.Camera.Y + CameraLocker.UpwardMaxYOffset, level.CameraUpwardMaxY);
at.Y = Math.Min(at.Y, level.CameraUpwardMaxY);
if (locker != null)
at.Y = Math.Max(at.Y, Math.Min(level.Bounds.Bottom - 180, locker.Entity.Y - locker.MaxYOffset));
}
}
// snap above killboxes
var killboxes = Scene.Tracker.GetEntities<Killbox>();
foreach (var box in killboxes)
{
if (!box.Collidable)
continue;
if (Top < box.Bottom && Right > box.Left && Left < box.Right)
at.Y = Math.Min(at.Y, box.Top - 180);
}
return at;
}
}
public bool GetChasePosition(float sceneTime, float timeAgo, out ChaserState chaseState)
{
if (!Dead)
{
bool tooLongAgoFound = false;
foreach (var state in chaserStates)
{
float time = sceneTime - state.TimeStamp;
if (time <= timeAgo)
{
if (tooLongAgoFound || timeAgo - time < .02f)
{
chaseState = state;
return true;
}
else
{
chaseState = new ChaserState();
return false;
}
}
else
tooLongAgoFound = true;
}
}
chaseState = new ChaserState();
return false;
}
public bool CanRetry
{
get
{
switch (StateMachine.State)
{
default:
return true;
case StIntroJump:
case StIntroWalk:
case StIntroWakeUp:
case StIntroRespawn:
case StReflectionFall:
return false;
}
}
}
public bool TimePaused
{
get
{
if (Dead)
return true;
switch (StateMachine.State)
{
default:
return false;
case StIntroJump:
case StIntroWalk:
case StIntroWakeUp:
case StIntroRespawn:
case StSummitLaunch:
return true;
}
}
}
public bool InControl
{
get
{
switch (StateMachine.State)
{
default:
return true;
case StIntroJump:
case StIntroWalk:
case StIntroWakeUp:
case StIntroRespawn:
case StDummy:
case StFrozen:
case StBirdDashTutorial:
return false;
}
}
}
public PlayerInventory Inventory
{
get
{
if (level != null && level.Session != null)
return level.Session.Inventory;
return PlayerInventory.Default;
}
}
#endregion
#region Transitions
public void OnTransition()
{
wallSlideTimer = WallSlideTime;
jumpGraceTimer = 0;
forceMoveXTimer = 0;
chaserStates.Clear();
RefillDash();
RefillStamina();
Leader.TransferFollowers();
}
public bool TransitionTo(Vector2 target, Vector2 direction)
{
MoveTowardsX(target.X, 60f * Engine.DeltaTime);
MoveTowardsY(target.Y, 60f * Engine.DeltaTime);
//Update hair because the normal update loop is not firing right now!
UpdateHair(false);
UpdateCarry();
//Returns true when transition is complete
if (Position == target)
{
ZeroRemainderX();
ZeroRemainderY();
Speed.X = (int)Math.Round(Speed.X);
Speed.Y = (int)Math.Round(Speed.Y);
return true;
}
else
return false;
}
public void BeforeSideTransition()
{
}
public void BeforeDownTransition()
{
if (StateMachine.State != StRedDash && StateMachine.State != StReflectionFall && StateMachine.State != StStarFly)
{
StateMachine.State = StNormal;
Speed.Y = Math.Max(0, Speed.Y);
AutoJump = false;
varJumpTimer = 0;
}
foreach (var platform in Scene.Tracker.GetEntities<Platform>())
if (!(platform is SolidTiles) && CollideCheckOutside(platform, Position + Vector2.UnitY * Height))
platform.Collidable = false;
}
public void BeforeUpTransition()
{
Speed.X = 0;
if (StateMachine.State != StRedDash && StateMachine.State != StReflectionFall && StateMachine.State != StStarFly)
{
varJumpSpeed = Speed.Y = JumpSpeed;
if (StateMachine.State == StSummitLaunch)
StateMachine.State = StIntroJump;
else
StateMachine.State = StNormal;
AutoJump = true;
AutoJumpTimer = 0;
varJumpTimer = VarJumpTime;
}
dashCooldownTimer = 0.2f;
}
#endregion
#region Jumps 'n' Stuff
public bool OnSafeGround
{
get; private set;
}
public bool LoseShards
{
get
{
return onGround;
}
}
private const float LaunchedBoostCheckSpeedSq = 100 * 100;
private const float LaunchedJumpCheckSpeedSq = 220 * 220;
private const float LaunchedMinSpeedSq = 140 * 140;
private const float LaunchedDoubleSpeedSq = 150 * 150;
private bool LaunchedBoostCheck()
{
if (LiftBoost.LengthSquared() >= LaunchedBoostCheckSpeedSq && Speed.LengthSquared() >= LaunchedJumpCheckSpeedSq)
{
launched = true;
return true;
}
else
{
launched = false;
return false;
}
}
public void Jump(bool particles = true, bool playSfx = true)
{
Input.Jump.ConsumeBuffer();
jumpGraceTimer = 0;
varJumpTimer = VarJumpTime;
AutoJump = false;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
Speed.X += JumpHBoost * moveX;
Speed.Y = JumpSpeed;
Speed += LiftBoost;
varJumpSpeed = Speed.Y;
LaunchedBoostCheck();
if (playSfx)
{
if (launched)
Play(Sfxs.char_mad_jump_assisted);
if (dreamJump)
Play(Sfxs.char_mad_jump_dreamblock);
else
Play(Sfxs.char_mad_jump);
}
Sprite.Scale = new Vector2(.6f, 1.4f);
if (particles)
Dust.Burst(BottomCenter, Calc.Up, 4);
SaveData.Instance.TotalJumps++;
}
private void SuperJump()
{
Input.Jump.ConsumeBuffer();
jumpGraceTimer = 0;
varJumpTimer = VarJumpTime;
AutoJump = false;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
Speed.X = SuperJumpH * (int)Facing;
Speed.Y = JumpSpeed;
Speed += LiftBoost;
Play(Sfxs.char_mad_jump);
if (Ducking)
{
Ducking = false;
Speed.X *= DuckSuperJumpXMult;
Speed.Y *= DuckSuperJumpYMult;
Play(Sfxs.char_mad_jump_superslide);
}
else
Play(Sfxs.char_mad_jump_super);
varJumpSpeed = Speed.Y;
launched = true;
Sprite.Scale = new Vector2(.6f, 1.4f);
Dust.Burst(BottomCenter, Calc.Up, 4);
SaveData.Instance.TotalJumps++;
}
private bool WallJumpCheck(int dir)
{
return ClimbBoundsCheck(dir) && CollideCheck<Solid>(Position + Vector2.UnitX * dir * WallJumpCheckDist);
}
private void WallJump(int dir)
{
Ducking = false;
Input.Jump.ConsumeBuffer();
jumpGraceTimer = 0;
varJumpTimer = VarJumpTime;
AutoJump = false;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
if (moveX != 0)
{
forceMoveX = dir;
forceMoveXTimer = WallJumpForceTime;
}
//Get lift of wall jumped off of
if (LiftSpeed == Vector2.Zero)
{
Solid wall = CollideFirst<Solid>(Position + Vector2.UnitX * WallJumpCheckDist);
if (wall != null)
LiftSpeed = wall.LiftSpeed;
}
Speed.X = WallJumpHSpeed * dir;
Speed.Y = JumpSpeed;
Speed += LiftBoost;
varJumpSpeed = Speed.Y;
LaunchedBoostCheck();
// wall-sound?
var pushOff = SurfaceIndex.GetPlatformByPriority(CollideAll<Platform>(Position - Vector2.UnitX * dir * 4, temp));
if (pushOff != null)
Play(Sfxs.char_mad_land, SurfaceIndex.Param, pushOff.GetWallSoundIndex(this, -dir));
// jump sfx
Play(dir < 0 ? Sfxs.char_mad_jump_wall_right : Sfxs.char_mad_jump_wall_left);
Sprite.Scale = new Vector2(.6f, 1.4f);
if (dir == -1)
Dust.Burst(Center + Vector2.UnitX * 2, Calc.UpLeft, 4);
else
Dust.Burst(Center + Vector2.UnitX * -2, Calc.UpRight, 4);
SaveData.Instance.TotalWallJumps++;
}
private void SuperWallJump(int dir)
{
Ducking = false;
Input.Jump.ConsumeBuffer();
jumpGraceTimer = 0;
varJumpTimer = SuperWallJumpVarTime;
AutoJump = false;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
Speed.X = SuperWallJumpH * dir;
Speed.Y = SuperWallJumpSpeed;
Speed += LiftBoost;
varJumpSpeed = Speed.Y;
launched = true;
Play(dir < 0 ? Sfxs.char_mad_jump_wall_right : Sfxs.char_mad_jump_wall_left);
Play(Sfxs.char_mad_jump_superwall);
Sprite.Scale = new Vector2(.6f, 1.4f);
if (dir == -1)
Dust.Burst(Center + Vector2.UnitX * 2, Calc.UpLeft, 4);
else
Dust.Burst(Center + Vector2.UnitX * -2, Calc.UpRight, 4);
SaveData.Instance.TotalWallJumps++;
}
private void ClimbJump()
{
if (!onGround)
{
Stamina -= ClimbJumpCost;
sweatSprite.Play("jump", true);
Input.Rumble(RumbleStrength.Light, RumbleLength.Medium);
}
dreamJump = false;
Jump(false, false);
if (moveX == 0)
{
wallBoostDir = -(int)Facing;
wallBoostTimer = ClimbJumpBoostTime;
}
if (Facing == Facings.Right)
{
Play(Sfxs.char_mad_jump_climb_right);
Dust.Burst(Center + Vector2.UnitX * 2, Calc.UpLeft, 4);
}
else
{
Play(Sfxs.char_mad_jump_climb_left);
Dust.Burst(Center + Vector2.UnitX * -2, Calc.UpRight, 4);
}
}
public void Bounce(float fromY)
{
if (StateMachine.State == StBoost && CurrentBooster != null)
{
CurrentBooster.PlayerReleased();
CurrentBooster = null;
}
var was = Collider;
Collider = normalHitbox;
{
MoveVExact((int)(fromY - Bottom));
if (!Inventory.NoRefills)
RefillDash();
RefillStamina();
StateMachine.State = StNormal;
jumpGraceTimer = 0;
varJumpTimer = BounceVarJumpTime;
AutoJump = true;
AutoJumpTimer = BounceAutoJumpTime;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
varJumpSpeed = Speed.Y = BounceSpeed;
launched = false;
Input.Rumble(RumbleStrength.Light, RumbleLength.Medium);
Sprite.Scale = new Vector2(.6f, 1.4f);
}
Collider = was;
}
public void SuperBounce(float fromY)
{
if (StateMachine.State == StBoost && CurrentBooster != null)
{
CurrentBooster.PlayerReleased();
CurrentBooster = null;
}
var was = Collider;
Collider = normalHitbox;
{
MoveV(fromY - Bottom);
if (!Inventory.NoRefills)
RefillDash();
RefillStamina();
StateMachine.State = StNormal;
jumpGraceTimer = 0;
varJumpTimer = SuperBounceVarJumpTime;
AutoJump = true;
AutoJumpTimer = 0;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
Speed.X = 0;
varJumpSpeed = Speed.Y = SuperBounceSpeed;
launched = false;
level.DirectionalShake(-Vector2.UnitY, .1f);
Input.Rumble(RumbleStrength.Medium, RumbleLength.Medium);
Sprite.Scale = new Vector2(.5f, 1.5f);
}
Collider = was;
}
private const float SideBounceSpeed = 240;
private const float SideBounceForceMoveXTime = .3f;
public void SideBounce(int dir, float fromX, float fromY)
{
var was = Collider;
Collider = normalHitbox;
{
MoveV(Calc.Clamp(fromY - Bottom, -4, 4));
if (dir > 0)
MoveH(fromX - Left);
else if (dir < 0)
MoveH(fromX - Right);
if (!Inventory.NoRefills)
RefillDash();
RefillStamina();
StateMachine.State = StNormal;
jumpGraceTimer = 0;
varJumpTimer = BounceVarJumpTime;
AutoJump = true;
AutoJumpTimer = 0;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
forceMoveX = dir;
forceMoveXTimer = SideBounceForceMoveXTime;
wallBoostTimer = 0;
launched = false;
Speed.X = SideBounceSpeed * dir;
varJumpSpeed = Speed.Y = BounceSpeed;
level.DirectionalShake(Vector2.UnitX * dir, .1f);
Input.Rumble(RumbleStrength.Medium, RumbleLength.Medium);
Sprite.Scale = new Vector2(1.5f, .5f);
}
Collider = was;
}
public void Rebound(int direction = 0)
{
Speed.X = direction * ReboundSpeedX;
Speed.Y = ReboundSpeedY;
varJumpSpeed = Speed.Y;
varJumpTimer = ReboundVarJumpTime;
AutoJump = true;
AutoJumpTimer = 0;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
launched = false;
dashAttackTimer = 0;
forceMoveXTimer = 0;
StateMachine.State = StNormal;
}
public void ReflectBounce(Vector2 direction)
{
if (direction.X != 0)
Speed.X = direction.X * ReflectBoundSpeed;
if (direction.Y != 0)
Speed.Y = direction.Y * ReflectBoundSpeed;
AutoJumpTimer = 0;
dashAttackTimer = 0;
wallSlideTimer = WallSlideTime;
wallBoostTimer = 0;
launched = false;
dashAttackTimer = 0;
forceMoveXTimer = 0;
StateMachine.State = StNormal;
}
public int MaxDashes
{
get
{
if (SaveData.Instance.AssistMode && SaveData.Instance.Assists.DashMode != Assists.DashModes.Normal && !level.InCutscene)
return 2;
else
return Inventory.Dashes;
}
}
public bool RefillDash()
{
if (Dashes < MaxDashes)
{
Dashes = MaxDashes;
return true;
}
else
return false;
}
public bool UseRefill()
{
if (Dashes < MaxDashes || Stamina < ClimbTiredThreshold)
{
Dashes = MaxDashes;
RefillStamina();
return true;
}
else
return false;
}
public void RefillStamina()
{
Stamina = ClimbMaxStamina;
}
public PlayerDeadBody Die(Vector2 direction, bool evenIfInvincible = false, bool registerDeathInStats = true)
{
var session = level.Session;
bool invincible = (!evenIfInvincible && SaveData.Instance.AssistMode && SaveData.Instance.Assists.Invincible);
if (!Dead && !invincible && StateMachine.State != StReflectionFall)
{
Stop(wallSlideSfx);
if (registerDeathInStats)
{
session.Deaths++;
session.DeathsInCurrentLevel++;
SaveData.Instance.AddDeath(session.Area);
}
// has gold strawb?
Strawberry goldenStrawb = null;
foreach (var strawb in Leader.Followers)
if (strawb.Entity is Strawberry && (strawb.Entity as Strawberry).Golden && !(strawb.Entity as Strawberry).Winged)
goldenStrawb = (strawb.Entity as Strawberry);
Dead = true;
Leader.LoseFollowers();
Depth = Depths.Top;
Speed = Vector2.Zero;
StateMachine.Locked = true;
Collidable = false;
Drop();
if (lastBooster != null)
lastBooster.PlayerDied();
level.InCutscene = false;
level.Shake();
Input.Rumble(RumbleStrength.Light, RumbleLength.Medium);
var body = new PlayerDeadBody(this, direction);
if (goldenStrawb != null)
{
body.DeathAction = () =>
{
var exit = new LevelExit(LevelExit.Mode.GoldenBerryRestart, session);
exit.GoldenStrawberryEntryLevel = goldenStrawb.ID.Level;
Engine.Scene = exit;
};
}
Scene.Add(body);
Scene.Remove(this);
var lookout = Scene.Tracker.GetEntity<Lookout>();
if (lookout != null)
lookout.StopInteracting();
return body;
}
return null;
}
private Vector2 LiftBoost
{
get
{
Vector2 val = LiftSpeed;
if (Math.Abs(val.X) > LiftXCap)
val.X = LiftXCap * Math.Sign(val.X);
if (val.Y > 0)
val.Y = 0;
else if (val.Y < LiftYCap)
val.Y = LiftYCap;
return val;
}
}
#endregion
#region Ducking
public bool Ducking
{
get
{
return Collider == duckHitbox || Collider == duckHurtbox;
}
set
{
if (value)
{
Collider = duckHitbox;
hurtbox = duckHurtbox;
}
else
{
Collider = normalHitbox;
hurtbox = normalHurtbox;
}
}
}
public bool CanUnDuck
{
get
{
if (!Ducking)
return true;
Collider was = Collider;
Collider = normalHitbox;
bool ret = !CollideCheck<Solid>();
Collider = was;
return ret;
}
}
public bool CanUnDuckAt(Vector2 at)
{
Vector2 was = Position;
Position = at;
bool ret = CanUnDuck;
Position = was;
return ret;
}
public bool DuckFreeAt(Vector2 at)
{
Vector2 oldP = Position;
Collider oldC = Collider;
Position = at;
Collider = duckHitbox;
bool ret = !CollideCheck<Solid>();
Position = oldP;
Collider = oldC;
return ret;
}
private void Duck()
{
Collider = duckHitbox;
}
private void UnDuck()
{
Collider = normalHitbox;
}
#endregion
#region Holding
public Holdable Holding
{
get; set;
}
public void UpdateCarry()
{
if (Holding != null)
{
// don't hold stuff that doesn't exist anymore
if (Holding.Scene == null)
Holding = null;
else
Holding.Carry(Position + carryOffset + Vector2.UnitY * Sprite.CarryYOffset);
}
}
public void Swat(int dir)
{
if (Holding != null)
{
Holding.Release(new Vector2(.8f * dir, -.25f));
Holding = null;
}
}
private bool Pickup(Holdable pickup)
{
if (pickup.Pickup(this))
{
Ducking = false;
Holding = pickup;
minHoldTimer = HoldMinTime;
return true;
}
else
return false;
}
private void Throw()
{
if (Holding != null)
{
if (Input.MoveY.Value == 1)
Drop();
else
{
Input.Rumble(RumbleStrength.Strong, RumbleLength.Short);
Holding.Release(Vector2.UnitX * (int)Facing);
Speed.X += ThrowRecoil * -(int)Facing;
}
Play(Sfxs.char_mad_crystaltheo_throw);
Sprite.Play("throw");
Holding = null;
}
}
private void Drop()
{
if (Holding != null)
{
Input.Rumble(RumbleStrength.Light, RumbleLength.Short);
Holding.Release(Vector2.Zero);
Holding = null;
}
}
#endregion
#region Physics
public void StartJumpGraceTime()
{
jumpGraceTimer = JumpGraceTime;
}
public override bool IsRiding(Solid solid)
{
if (StateMachine.State == StDreamDash)
return CollideCheck(solid);
if (StateMachine.State == StClimb || StateMachine.State == StHitSquash)
return CollideCheck(solid, Position + Vector2.UnitX * (int)Facing);
return base.IsRiding(solid);
}
public override bool IsRiding(JumpThru jumpThru)
{
if (StateMachine.State == StDreamDash)
return false;
return StateMachine.State != StClimb && Speed.Y >= 0 && base.IsRiding(jumpThru);
}
public bool BounceCheck(float y)
{
return Bottom <= y + 3;
}
public void PointBounce(Vector2 from)
{
const float BounceSpeed = 200f;
const float MinX = 120f;
if (StateMachine.State == StBoost && CurrentBooster != null)
CurrentBooster.PlayerReleased();
RefillDash();
RefillStamina();
Speed = (Center - from).SafeNormalize(BounceSpeed);
Speed.X *= 1.2f;
if (Math.Abs(Speed.X) < MinX)
{
if (Speed.X == 0)
Speed.X = -(int)Facing * MinX;
else
Speed.X = Math.Sign(Speed.X) * MinX;
}
}
private void WindMove(Vector2 move)
{
if (!JustRespawned && noWindTimer <= 0 && InControl && StateMachine.State != StBoost && StateMachine.State != StDash && StateMachine.State != StSummitLaunch)
{
//Horizontal
if (move.X != 0 && StateMachine.State != StClimb)
{
windTimeout = 0.2f;
windDirection.X = Math.Sign(move.X);
if (!CollideCheck<Solid>(Position + Vector2.UnitX * -Math.Sign(move.X) * WindWallDistance))
{
if (Ducking && onGround)
move.X *= DuckWindMult;
if (move.X < 0)
move.X = Math.Max(move.X, level.Bounds.Left - (ExactPosition.X + Collider.Left));
else
move.X = Math.Min(move.X, level.Bounds.Right - (ExactPosition.X + Collider.Right));
MoveH(move.X);
}
}
//Vertical
if (move.Y != 0)
{
windTimeout = 0.2f;
windDirection.Y = Math.Sign(move.Y);
if (Speed.Y < 0 || !OnGround())
{
if (StateMachine.State == StClimb)
{
if (move.Y > 0 && climbNoMoveTimer <= 0)
move.Y *= .4f;
else
return;
}
MoveV(move.Y);
}
}
}
}
private void OnCollideH(CollisionData data)
{
if (StateMachine.State == StStarFly)
{
if (starFlyTimer < StarFlyEndNoBounceTime)
Speed.X = 0;
else
{
Play(Sfxs.game_06_feather_state_bump);
Input.Rumble(RumbleStrength.Light, RumbleLength.Medium);
Speed.X *= StarFlyWallBounce;
}
return;
}
if (StateMachine.State == StDreamDash)
return;
//Dash Blocks
if (DashAttacking && data.Hit != null && data.Hit.OnDashCollide != null && data.Direction.X == Math.Sign(DashDir.X))
{
var result = data.Hit.OnDashCollide(this, data.Direction);
if (StateMachine.State == StRedDash)
result = DashCollisionResults.Ignore;
if (result == DashCollisionResults.Rebound)
{
Rebound(-Math.Sign(Speed.X));
return;
}
else if (result == DashCollisionResults.Bounce)
{
ReflectBounce(new Vector2(-Math.Sign(Speed.X), 0));
return;
}
else if (result == DashCollisionResults.Ignore)
return;
}
//Dash corner correction
if (StateMachine.State == StDash || StateMachine.State == StRedDash)
{
if (onGround && DuckFreeAt(Position + Vector2.UnitX * Math.Sign(Speed.X)))
{
Ducking = true;
return;
}
else if (Speed.Y == 0 && Speed.X != 0)
{
for (int i = 1; i <= DashCornerCorrection; i++)
{
for (int j = 1; j >= -1; j -= 2)
{
if (!CollideCheck<Solid>(Position + new Vector2(Math.Sign(Speed.X), i * j)))
{
MoveVExact(i * j);
MoveHExact(Math.Sign(Speed.X));
return;
}
}
}
}
}
//Dream Dash
if (DreamDashCheck(Vector2.UnitX * Math.Sign(Speed.X)))
{
StateMachine.State = StDreamDash;
dashAttackTimer = 0;
return;
}
//Speed retention
if (wallSpeedRetentionTimer <= 0)
{
wallSpeedRetained = Speed.X;
wallSpeedRetentionTimer = WallSpeedRetentionTime;
}
//Collide event
if (data.Hit != null && data.Hit.OnCollide != null)
data.Hit.OnCollide(data.Direction);
Speed.X = 0;
dashAttackTimer = 0;
if (StateMachine.State == StRedDash)
{
Input.Rumble(RumbleStrength.Medium, RumbleLength.Short);
level.Displacement.AddBurst(Center, .5f, 8, 48, .4f, Ease.QuadOut, Ease.QuadOut);
StateMachine.State = StHitSquash;
}
}
private void OnCollideV(CollisionData data)
{
if (StateMachine.State == StStarFly)
{
if (starFlyTimer < StarFlyEndNoBounceTime)
Speed.Y = 0;
else
{
Play(Sfxs.game_06_feather_state_bump);
Input.Rumble(RumbleStrength.Light, RumbleLength.Medium);
Speed.Y *= StarFlyWallBounce;
}
return;
}
else if (StateMachine.State == StSwim)
{
Speed.Y = 0;
return;
}
else if (StateMachine.State == StDreamDash)
return;
//Dash Blocks
if (data.Hit != null && data.Hit.OnDashCollide != null)
{
if (DashAttacking && data.Direction.Y == Math.Sign(DashDir.Y))
{
var result = data.Hit.OnDashCollide(this, data.Direction);
if (StateMachine.State == StRedDash)
result = DashCollisionResults.Ignore;
if (result == DashCollisionResults.Rebound)
{
Rebound(0);
return;
}
else if (result == DashCollisionResults.Bounce)
{
ReflectBounce(new Vector2(0, -Math.Sign(Speed.Y)));
return;
}
else if (result == DashCollisionResults.Ignore)
return;
}
else if (StateMachine.State == StSummitLaunch)
{
data.Hit.OnDashCollide(this, data.Direction);
return;
}
}
if (Speed.Y > 0)
{
//Dash corner correction
if ((StateMachine.State == StDash || StateMachine.State == StRedDash) && !dashStartedOnGround)
{
if (Speed.X <= 0)
{
for (int i = -1; i >= -DashCornerCorrection; i--)
{
if (!OnGround(Position + new Vector2(i, 0)))
{
MoveHExact(i);
MoveVExact(1);
return;
}
}
}
if (Speed.X >= 0)
{
for (int i = 1; i <= DashCornerCorrection; i++)
{
if (!OnGround(Position + new Vector2(i, 0)))
{
MoveHExact(i);
MoveVExact(1);
return;
}
}
}
}
//Dream Dash
if (DreamDashCheck(Vector2.UnitY * Math.Sign(Speed.Y)))
{
StateMachine.State = StDreamDash;
dashAttackTimer = 0;
return;
}
//Dash Slide
if (DashDir.X != 0 && DashDir.Y > 0 && Speed.Y > 0)
{
DashDir.X = Math.Sign(DashDir.X);
DashDir.Y = 0;
Speed.Y = 0;
Speed.X *= DodgeSlideSpeedMult;
Ducking = true;
}
//Landed on ground effects
if (StateMachine.State != StClimb)
{
float squish = Math.Min(Speed.Y / FastMaxFall, 1);
Sprite.Scale.X = MathHelper.Lerp(1, 1.6f, squish);
Sprite.Scale.Y = MathHelper.Lerp(1, .4f, squish);
if (Speed.Y >= MaxFall / 2)
Dust.Burst(Position, Calc.Angle(new Vector2(0, -1)), 8);
if (highestAirY < Y - 50 && Speed.Y >= MaxFall && Math.Abs(Speed.X) >= MaxRun)
Sprite.Play(PlayerSprite.RunStumble);
Input.Rumble(RumbleStrength.Light, RumbleLength.Short);
// landed SFX
var platform = SurfaceIndex.GetPlatformByPriority(CollideAll<Platform>(Position + new Vector2(0, 1), temp));
if (platform != null)
{
var surface = platform.GetLandSoundIndex(this);
if (surface >= 0)
Play(playFootstepOnLand > 0f ? Sfxs.char_mad_footstep : Sfxs.char_mad_land, SurfaceIndex.Param, surface);
if (platform is DreamBlock)
(platform as DreamBlock).FootstepRipple(Position);
}
playFootstepOnLand = 0f;
}
}
else
{
if (Speed.Y < 0)
{
//Corner Correction
{
if (Speed.X <= 0)
{
for (int i = 1; i <= UpwardCornerCorrection; i++)
{
if (!CollideCheck<Solid>(Position + new Vector2(-i, -1)))
{
Position += new Vector2(-i, -1);
return;
}
}
}
if (Speed.X >= 0)
{
for (int i = 1; i <= UpwardCornerCorrection; i++)
{
if (!CollideCheck<Solid>(Position + new Vector2(i, -1)))
{
Position += new Vector2(i, -1);
return;
}
}
}
}
if (varJumpTimer < VarJumpTime - CeilingVarJumpGrace)
varJumpTimer = 0;
}
//Dream Dash
if (DreamDashCheck(Vector2.UnitY * Math.Sign(Speed.Y)))
{
StateMachine.State = StDreamDash;
dashAttackTimer = 0;
return;
}
}
//Collide event
if (data.Hit != null && data.Hit.OnCollide != null)
data.Hit.OnCollide(data.Direction);
dashAttackTimer = 0;
Speed.Y = 0;
if (StateMachine.State == StRedDash)
{
Input.Rumble(RumbleStrength.Medium, RumbleLength.Short);
level.Displacement.AddBurst(Center, .5f, 8, 48, .4f, Ease.QuadOut, Ease.QuadOut);
StateMachine.State = StHitSquash;
}
}
private bool DreamDashCheck(Vector2 dir)
{
if (Inventory.DreamDash && DashAttacking && (dir.X == Math.Sign(DashDir.X) || dir.Y == Math.Sign(DashDir.Y)))
{
var block = CollideFirst<DreamBlock>(Position + dir);
if (block != null)
{
if (CollideCheck<Solid, DreamBlock>(Position + dir))
{
Vector2 side = new Vector2(Math.Abs(dir.Y), Math.Abs(dir.X));
bool checkNegative, checkPositive;
if (dir.X != 0)
{
checkNegative = Speed.Y <= 0;
checkPositive = Speed.Y >= 0;
}
else
{
checkNegative = Speed.X <= 0;
checkPositive = Speed.X >= 0;
}
if (checkNegative)
{
for (int i = -1; i >= -DashCornerCorrection; i--)
{
var at = Position + dir + side * i;
if (!CollideCheck<Solid, DreamBlock>(at))
{
Position += side * i;
dreamBlock = block;
return true;
}
}
}
if (checkPositive)
{
for (int i = 1; i <= DashCornerCorrection; i++)
{
var at = Position + dir + side * i;
if (!CollideCheck<Solid, DreamBlock>(at))
{
Position += side * i;
dreamBlock = block;
return true;
}
}
}
return false;
}
else
{
dreamBlock = block;
return true;
}
}
}
return false;
}
public void OnBoundsH()
{
Speed.X = 0;
if (StateMachine.State == StRedDash)
StateMachine.State = StNormal;
}
public void OnBoundsV()
{
Speed.Y = 0;
if (StateMachine.State == StRedDash)
StateMachine.State = StNormal;
}
override protected void OnSquish(CollisionData data)
{
bool ducked = false;
if (!Ducking)
{
ducked = true;
Ducking = true;
data.Pusher.Collidable = true;
if (!CollideCheck<Solid>())
{
data.Pusher.Collidable = false;
return;
}
var was = Position;
Position = data.TargetPosition;
if (!CollideCheck<Solid>())
{
data.Pusher.Collidable = false;
return;
}
Position = was;
data.Pusher.Collidable = false;
}
if (!TrySquishWiggle(data))
Die(Vector2.Zero);
else if (ducked && CanUnDuck)
Ducking = false;
}
#endregion
#region Normal State
private void NormalBegin()
{
maxFall = MaxFall;
}
private void NormalEnd()
{
wallBoostTimer = 0;
wallSpeedRetentionTimer = 0;
hopWaitX = 0;
}
public bool ClimbBoundsCheck(int dir)
{
return Left + dir * ClimbCheckDist >= level.Bounds.Left && Right + dir * ClimbCheckDist < level.Bounds.Right;
}
public bool ClimbCheck(int dir, int yAdd = 0)
{
return ClimbBoundsCheck(dir) && !ClimbBlocker.Check(Scene, this, Position + Vector2.UnitY * yAdd + Vector2.UnitX * ClimbCheckDist * (int)Facing) && CollideCheck<Solid>(Position + new Vector2(dir * ClimbCheckDist, yAdd));
}
private const float SpacePhysicsMult = .6f;
private int NormalUpdate()
{
//Use Lift Boost if walked off platform
if (LiftBoost.Y < 0 && wasOnGround && !onGround && Speed.Y >= 0)
Speed.Y = LiftBoost.Y;
if (Holding == null)
{
if (Input.Grab.Check && !IsTired && !Ducking)
{
//Grabbing Holdables
foreach (Holdable hold in Scene.Tracker.GetComponents<Holdable>())
if (hold.Check(this) && Pickup(hold))
return StPickup;
//Climbing
if (Speed.Y >= 0 && Math.Sign(Speed.X) != -(int)Facing)
{
if (ClimbCheck((int)Facing))
{
Ducking = false;
return StClimb;
}
if (Input.MoveY < 1 && level.Wind.Y <= 0)
{
for (int i = 1; i <= ClimbUpCheckDist; i++)
{
if (!CollideCheck<Solid>(Position + Vector2.UnitY * -i) && ClimbCheck((int)Facing, -i))
{
MoveVExact(-i);
Ducking = false;
return StClimb;
}
}
}
}
}
//Dashing
if (CanDash)
{
Speed += LiftBoost;
return StartDash();
}
//Ducking
if (Ducking)
{
if (onGround && Input.MoveY != 1)
{
if (CanUnDuck)
{
Ducking = false;
Sprite.Scale = new Vector2(.8f, 1.2f);
}
else if (Speed.X == 0)
{
for (int i = DuckCorrectCheck; i > 0; i--)
{
if (CanUnDuckAt(Position + Vector2.UnitX * i))
{
MoveH(DuckCorrectSlide * Engine.DeltaTime);
break;
}
else if (CanUnDuckAt(Position - Vector2.UnitX * i))
{
MoveH(-DuckCorrectSlide * Engine.DeltaTime);
break;
}
}
}
}
}
else if(onGround && Input.MoveY == 1 && Speed.Y >= 0)
{
Ducking = true;
Sprite.Scale = new Vector2(1.4f, .6f);
}
}
else
{
//Throw
if (!Input.Grab.Check && minHoldTimer <= 0)
Throw();
//Ducking
if (!Ducking && onGround && Input.MoveY == 1 && Speed.Y >= 0)
{
Drop();
Ducking = true;
Sprite.Scale = new Vector2(1.4f, .6f);
}
}
//Running and Friction
if (Ducking && onGround)
Speed.X = Calc.Approach(Speed.X, 0, DuckFriction * Engine.DeltaTime);
else
{
float mult = onGround ? 1 : AirMult;
if (onGround && level.CoreMode == Session.CoreModes.Cold)
mult *= .3f;
float max = Holding == null ? MaxRun : HoldingMaxRun;
if (level.InSpace)
max *= SpacePhysicsMult;
if (Math.Abs(Speed.X) > max && Math.Sign(Speed.X) == moveX)
Speed.X = Calc.Approach(Speed.X, max * moveX, RunReduce * mult * Engine.DeltaTime); //Reduce back from beyond the max speed
else
Speed.X = Calc.Approach(Speed.X, max * moveX, RunAccel * mult * Engine.DeltaTime); //Approach the max speed
}
//Vertical
{
//Calculate current max fall speed
{
float mf = MaxFall;
float fmf = FastMaxFall;
if (level.InSpace)
{
mf *= SpacePhysicsMult;
fmf *= SpacePhysicsMult;
}
//Fast Fall
if (Input.MoveY == 1 && Speed.Y >= mf)
{
maxFall = Calc.Approach(maxFall, fmf, FastMaxAccel * Engine.DeltaTime);
float half = mf + (fmf - mf) * .5f;
if (Speed.Y >= half)
{
float spriteLerp = Math.Min(1f, (Speed.Y - half) / (fmf - half));
Sprite.Scale.X = MathHelper.Lerp(1f, .5f, spriteLerp);
Sprite.Scale.Y = MathHelper.Lerp(1f, 1.5f, spriteLerp);
}
}
else
maxFall = Calc.Approach(maxFall, mf, FastMaxAccel * Engine.DeltaTime);
}
//Gravity
if (!onGround)
{
float max = maxFall;
//Wall Slide
if ((moveX == (int)Facing || (moveX == 0 && Input.Grab.Check)) && Input.MoveY.Value != 1)
{
if (Speed.Y >= 0 && wallSlideTimer > 0 && Holding == null && ClimbBoundsCheck((int)Facing) && CollideCheck<Solid>(Position + Vector2.UnitX * (int)Facing) && CanUnDuck)
{
Ducking = false;
wallSlideDir = (int)Facing;
}
if (wallSlideDir != 0)
{
if (wallSlideTimer > WallSlideTime * .5f && ClimbBlocker.Check(level, this, Position + Vector2.UnitX * wallSlideDir))
wallSlideTimer = WallSlideTime * .5f;
max = MathHelper.Lerp(MaxFall, WallSlideStartMax, wallSlideTimer / WallSlideTime);
if (wallSlideTimer / WallSlideTime > .65f)
CreateWallSlideParticles(wallSlideDir);
}
}
float mult = (Math.Abs(Speed.Y) < HalfGravThreshold && (Input.Jump.Check || AutoJump)) ? .5f : 1f;
if (level.InSpace)
mult *= SpacePhysicsMult;
Speed.Y = Calc.Approach(Speed.Y, max, Gravity * mult * Engine.DeltaTime);
}
//Variable Jumping
if (varJumpTimer > 0)
{
if (AutoJump || Input.Jump.Check)
Speed.Y = Math.Min(Speed.Y, varJumpSpeed);
else
varJumpTimer = 0;
}
//Jumping
if (Input.Jump.Pressed)
{
Water water = null;
if (jumpGraceTimer > 0)
{
Jump();
}
else if (CanUnDuck)
{
bool canUnduck = CanUnDuck;
if (canUnduck && WallJumpCheck(1))
{
if (Facing == Facings.Right && Input.Grab.Check && Stamina > 0 && Holding == null && !ClimbBlocker.Check(Scene, this, Position + Vector2.UnitX * WallJumpCheckDist))
ClimbJump();
else if (DashAttacking && DashDir.X == 0 && DashDir.Y == -1)
SuperWallJump(-1);
else
WallJump(-1);
}
else if (canUnduck && WallJumpCheck(-1))
{
if (Facing == Facings.Left && Input.Grab.Check && Stamina > 0 && Holding == null && !ClimbBlocker.Check(Scene, this, Position + Vector2.UnitX * -WallJumpCheckDist))
ClimbJump();
else if (DashAttacking && DashDir.X == 0 && DashDir.Y == -1)
SuperWallJump(1);
else
WallJump(1);
}
else if ((water = CollideFirst<Water>(Position + Vector2.UnitY * 2)) != null)
{
Jump();
water.TopSurface.DoRipple(Position, 1);
}
}
}
}
return StNormal;
}
public void CreateWallSlideParticles(int dir)
{
if (Scene.OnInterval(.01f))
{
Vector2 at = Center;
if (dir == 1)
at += new Vector2(5, 4);
else
at += new Vector2(-5, 4);
Dust.Burst(at, Calc.Up, 1);
}
}
#endregion
#region Climb State
private bool IsTired
{
get
{
return CheckStamina < ClimbTiredThreshold;
}
}
private float CheckStamina
{
get
{
if (wallBoostTimer > 0)
return Stamina + ClimbJumpCost;
else
return Stamina;
}
}
private void PlaySweatEffectDangerOverride(string state)
{
if (Stamina <= ClimbTiredThreshold)
sweatSprite.Play("danger");
else
sweatSprite.Play(state);
}
private FMOD.Studio.EventInstance conveyorLoopSfx;
private void ClimbBegin()
{
AutoJump = false;
Speed.X = 0;
Speed.Y *= ClimbGrabYMult;
wallSlideTimer = WallSlideTime;
climbNoMoveTimer = ClimbNoMoveTime;
wallBoostTimer = 0;
lastClimbMove = 0;
Input.Rumble(RumbleStrength.Medium, RumbleLength.Short);
for (int i = 0; i < ClimbCheckDist; i++)
if (!CollideCheck<Solid>(Position + Vector2.UnitX * (int)Facing))
Position += Vector2.UnitX * (int)Facing;
else
break;
// tell the thing we grabbed it
var platform = SurfaceIndex.GetPlatformByPriority(CollideAll<Solid>(Position + Vector2.UnitX * (int)Facing, temp));
if (platform != null)
Play(Sfxs.char_mad_grab, SurfaceIndex.Param, platform.GetWallSoundIndex(this, (int)Facing));
}
private void ClimbEnd()
{
if (conveyorLoopSfx != null)
{
conveyorLoopSfx.setParameterValue("end", 1);
conveyorLoopSfx.release();
conveyorLoopSfx = null;
}
wallSpeedRetentionTimer = 0;
if (sweatSprite != null && sweatSprite.CurrentAnimationID != "jump")
sweatSprite.Play("idle");
}
private const float WallBoosterSpeed = -160f;
private const float WallBoosterLiftSpeed = -80;
private const float WallBoosterAccel = 600f;
private const float WallBoostingHopHSpeed = 100f;
private const float WallBoosterOverTopSpeed = -180f;
private const float IceBoosterSpeed = 40;
private const float IceBoosterAccel = 300f;
private bool wallBoosting;
private int ClimbUpdate()
{
climbNoMoveTimer -= Engine.DeltaTime;
//Refill stamina on ground
if (onGround)
Stamina = ClimbMaxStamina;
//Wall Jump
if (Input.Jump.Pressed && (!Ducking || CanUnDuck))
{
if (moveX == -(int)Facing)
WallJump(-(int)Facing);
else
ClimbJump();
return StNormal;
}
//Dashing
if (CanDash)
{
Speed += LiftBoost;
return StartDash();
}
//Let go
if (!Input.Grab.Check)
{
Speed += LiftBoost;
Play(Sfxs.char_mad_grab_letgo);
return StNormal;
}
//No wall to hold
if (!CollideCheck<Solid>(Position + Vector2.UnitX * (int)Facing))
{
//Climbed over ledge?
if (Speed.Y < 0)
{
if (wallBoosting)
{
Speed += LiftBoost;
Play(Sfxs.char_mad_grab_letgo);
}
else
ClimbHop();
}
return StNormal;
}
var booster = WallBoosterCheck();
if (climbNoMoveTimer <= 0 && booster != null)
{
//Wallbooster
wallBoosting = true;
if (conveyorLoopSfx == null)
conveyorLoopSfx = Audio.Play(Sfxs.game_09_conveyor_activate, Position, "end", 0);
Audio.Position(conveyorLoopSfx, Position);
Speed.Y = Calc.Approach(Speed.Y, WallBoosterSpeed, WallBoosterAccel * Engine.DeltaTime);
LiftSpeed = Vector2.UnitY * Math.Max(Speed.Y, WallBoosterLiftSpeed);
Input.Rumble(RumbleStrength.Light, RumbleLength.Short);
}
else
{
wallBoosting = false;
if (conveyorLoopSfx != null)
{
conveyorLoopSfx.setParameterValue("end", 1);
conveyorLoopSfx.release();
conveyorLoopSfx = null;
}
//Climbing
float target = 0;
bool trySlip = false;
if (climbNoMoveTimer <= 0)
{
if (ClimbBlocker.Check(Scene, this, Position + Vector2.UnitX * (int)Facing))
trySlip = true;
else if (Input.MoveY.Value == -1)
{
target = ClimbUpSpeed;
//Up Limit
if (CollideCheck<Solid>(Position - Vector2.UnitY) || (ClimbHopBlockedCheck() && SlipCheck(-1)))
{
if (Speed.Y < 0)
Speed.Y = 0;
target = 0;
trySlip = true;
}
else if (SlipCheck())
{
//Hopping
ClimbHop();
return StNormal;
}
}
else if (Input.MoveY.Value == 1)
{
target = ClimbDownSpeed;
if (onGround)
{
if (Speed.Y > 0)
Speed.Y = 0;
target = 0;
}
else
CreateWallSlideParticles((int)Facing);
}
else
trySlip = true;
}
else
trySlip = true;
lastClimbMove = Math.Sign(target);
//Slip down if hands above the ledge and no vertical input
if (trySlip && SlipCheck())
target = ClimbSlipSpeed;
//Set Speed
Speed.Y = Calc.Approach(Speed.Y, target, ClimbAccel * Engine.DeltaTime);
}
//Down Limit
if (Input.MoveY.Value != 1 && Speed.Y > 0 && !CollideCheck<Solid>(Position + new Vector2((int)Facing, 1)))
Speed.Y = 0;
//Stamina
if (climbNoMoveTimer <= 0)
{
if (lastClimbMove == -1)
{
Stamina -= ClimbUpCost * Engine.DeltaTime;
if (Stamina <= ClimbTiredThreshold)
sweatSprite.Play("danger");
else if (sweatSprite.CurrentAnimationID != "climbLoop")
sweatSprite.Play("climb");
if (Scene.OnInterval(.2f))
Input.Rumble(RumbleStrength.Climb, RumbleLength.Short);
}
else
{
if (lastClimbMove == 0)
Stamina -= ClimbStillCost * Engine.DeltaTime;
if (!onGround)
{
PlaySweatEffectDangerOverride("still");
if (Scene.OnInterval(.8f))
Input.Rumble(RumbleStrength.Climb, RumbleLength.Short);
}
else
PlaySweatEffectDangerOverride("idle");
}
}
else
PlaySweatEffectDangerOverride("idle");
//Too tired
if (Stamina <= 0)
{
Speed += LiftBoost;
return StNormal;
}
return StClimb;
}
private WallBooster WallBoosterCheck()
{
if (ClimbBlocker.Check(Scene, this, Position + Vector2.UnitX * (int)Facing))
return null;
foreach (WallBooster booster in Scene.Tracker.GetEntities<WallBooster>())
if (booster.Facing == Facing && CollideCheck(booster))
return booster;
return null;
}
private void ClimbHop()
{
climbHopSolid = CollideFirst<Solid>(Position + Vector2.UnitX * (int)Facing);
playFootstepOnLand = 0.5f;
if (climbHopSolid != null)
{
climbHopSolidPosition = climbHopSolid.Position;
hopWaitX = (int)Facing;
hopWaitXSpeed = (int)Facing * ClimbHopX;
}
else
{
hopWaitX = 0;
Speed.X = (int)Facing * ClimbHopX;
}
Speed.Y = Math.Min(Speed.Y, ClimbHopY);
forceMoveX = 0;
forceMoveXTimer = ClimbHopForceTime;
fastJump = false;
noWindTimer = ClimbHopNoWindTime;
Play(Sfxs.char_mad_climb_ledge);
}
private bool SlipCheck(float addY = 0)
{
Vector2 at;
if (Facing == Facings.Right)
at = TopRight + Vector2.UnitY * (4 + addY);
else
at = TopLeft - Vector2.UnitX + Vector2.UnitY * (4 + addY);
return !Scene.CollideCheck<Solid>(at) && !Scene.CollideCheck<Solid>(at + Vector2.UnitY * (-4 + addY));
}
private bool ClimbHopBlockedCheck()
{
//If you have a cassette note, you're blocked
foreach (Follower follower in Leader.Followers)
if (follower.Entity is StrawberrySeed)
return true;
//If you hit a ledge blocker, you're blocked
foreach (LedgeBlocker blocker in Scene.Tracker.GetComponents<LedgeBlocker>())
if (blocker.HopBlockCheck(this))
return true;