private void LadderAnim(IMyEntity ent, LadderAnimation anim) { if(lastLadderAnim == anim) return; lastLadderAnim = anim; var skinned = ent as MySkinnedEntity; if(skinned.UseNewAnimationSystem) { // TODO how does this even... /* var character = ent as IMyCharacter; character.TriggerCharacterAnimationEvent("SMBody_WalkRun".ToLower(), true); character.TriggerCharacterAnimationEvent("Sprint".ToLower(), true); */ } else { skinned.AddCommand(new MyAnimationCommand() { AnimationSubtypeName = ladderAnimations[(int)anim], FrameOption = MyFrameOption.Loop, PlaybackCommand = MyPlaybackCommand.Play, TimeScale = (anim == LadderAnimation.MOUNTING ? 1.5f : 1f), KeepContinuingAnimations = false, BlendOption = MyBlendOption.Immediate, BlendTime = 0.1f, }, true); } }
private void PlayerUpdate() { var playerControlled = MyAPIGateway.Session.ControlledObject; if(playerControlled != null) { if(playerControlled.Entity is IMyCharacter) { SetCharacterReference(playerControlled.Entity); } else if(playerControlled.Entity is IMyCockpit) // in a seat, certainly not gonna climb ladders { SetCharacterReference(null); } // other cases depend on having the character controlled for a bit to get the reference else if(character != null && character.Closed) { SetCharacterReference(null); } } else { character = null; } if(character != null) { var cb = MyCubeBuilder.Static; // Dynamically enable/disable UseModelIntersection on ladder blocks that you hold to have the useful effect // of being able the block when another entity is blocking the grid space but not the blocks's physical shape. // This will still have the side effect issue if you aim at a ladder block with the same ladder block. if(cb.IsActivated && cb.CubeBuilderState != null && cb.CubeBuilderState.CurrentBlockDefinition != null && LadderLogic.ladderIds.Contains(cb.CubeBuilderState.CurrentBlockDefinition.Id.SubtypeName)) { if(prevCubeBuilderDefinition == null || prevCubeBuilderDefinition.Id != cb.CubeBuilderState.CurrentBlockDefinition.Id) { if(prevCubeBuilderDefinition != null) prevCubeBuilderDefinition.UseModelIntersection = false; prevCubeBuilderDefinition = cb.CubeBuilderState.CurrentBlockDefinition; cb.CubeBuilderState.CurrentBlockDefinition.UseModelIntersection = true; } } else if(prevCubeBuilderDefinition != null) { prevCubeBuilderDefinition.UseModelIntersection = false; prevCubeBuilderDefinition = null; } var charCtrl = character as IMyControllableEntity; bool controllingCharacter = (playerControlled != null && playerControlled.Entity is IMyCharacter); IMyTerminalBlock ladder = null; MyCubeBlock ladderInternal = null; MatrixD ladderMatrix = character.WorldMatrix; // temporarily using it for character matrix, then used for ladder matrix var charPos = ladderMatrix.Translation + ladderMatrix.Up * 0.05; var charPos2 = ladderMatrix.Translation + ladderMatrix.Up * RAY_HEIGHT; var charRay = new RayD(charPos, ladderMatrix.Up); if(dismounting <= 1) // relative top dismount sequence { if(usingLadder == null) { dismounting = 2; return; } ladder = usingLadder; ladderInternal = ladder as MyCubeBlock; dismounting *= ALIGN_MUL; if(settings.clientPrediction) { ladderMatrix = ladder.WorldMatrix; var charOnLadder = ladderMatrix.Translation + ladderMatrix.Forward * (ladderInternal.BlockDefinition.ModelOffset.Z + EXTRA_OFFSET_Z); if(ladder.CubeGrid.GridSizeEnum == MyCubeSize.Large) charOnLadder += ladderMatrix.Backward; var topDir = Vector3D.Dot(character.WorldMatrix.Up, ladderMatrix.Up); var matrix = character.WorldMatrix; var halfY = ((ladderInternal.BlockDefinition.Size.Y * ladder.CubeGrid.GridSize) / 2); matrix.Translation = charOnLadder + (topDir > 0 ? ladderMatrix.Up : ladderMatrix.Down) * (halfY + 0.1f) + ladderMatrix.Backward * 0.75; character.SetWorldMatrix(MatrixD.SlerpScale(character.WorldMatrix, matrix, MathHelper.Clamp(dismounting, 0.0f, 1.0f))); character.Physics.LinearVelocity = ladder.CubeGrid.Physics.GetVelocityAtPoint(character.WorldMatrix.Translation); // sync velocity with the ladder } SetLadderStatus("Dismounting ladder...", MyFontEnum.White); if(dismounting > 1f) ExitLadder(false); return; } // UNDONE DEBUG //{ // var c = Color.Blue.ToVector4(); // MySimpleObjectDraw.DrawLine(charPos, charPos2, "WeaponLaserIgnoreDepth", ref c, 0.5f); //} // find a ladder foreach(var l in ladders.Values) { if(l.Closed || l.MarkedForClose || !l.IsFunctional) continue; if(Vector3D.DistanceSquared(l.WorldMatrix.Translation, charPos) <= UPDATE_RADIUS) { ladderInternal = l as MyCubeBlock; ladderMatrix = l.WorldMatrix; // update ladder oriented box to find character in it accurately Quaternion.CreateFromRotationMatrix(ref ladderMatrix, out ladderBox.Orientation); if(l is MyAdvancedDoor && !(l as MyAdvancedDoor).FullyOpen) { ladderBox.HalfExtent = (ladderInternal.BlockDefinition.Size * l.CubeGrid.GridSize) / 2; var offset = ladderInternal.BlockDefinition.ModelOffset; ladderBox.Center = ladderMatrix.Translation + ladderMatrix.Up * 1.125f + ladderMatrix.Forward * (offset.Z + EXTRA_OFFSET_Z); ladderBox.HalfExtent.Y = 0.25 + 0.06; // 6mm offset to avoid some inaccuracies ladderBox.HalfExtent.Z = 0.5; if(l.CubeGrid.GridSizeEnum == MyCubeSize.Large) ladderBox.Center += ladderMatrix.Backward; } else { ladderBox.HalfExtent = (ladderInternal.BlockDefinition.Size * l.CubeGrid.GridSize) / 2; ladderBox.HalfExtent.Y += 0.06; // 6mm offset to avoid some inaccuracies ladderBox.HalfExtent.Z = 0.5; var offset = ladderInternal.BlockDefinition.ModelOffset; ladderBox.Center = ladderMatrix.Translation + ladderMatrix.Forward * (offset.Z + EXTRA_OFFSET_Z); if(l.CubeGrid.GridSizeEnum == MyCubeSize.Large) ladderBox.Center += ladderMatrix.Backward; } if(!ladderBox.Contains(ref charPos) && !ladderBox.Contains(ref charPos2)) { var intersect = ladderBox.Intersects(ref charRay); if(!intersect.HasValue || intersect.Value < 0 || intersect.Value > RAY_HEIGHT) continue; } // UNDONE DEBUG //{ // { // var c = Color.Red.ToVector4(); // MySimpleObjectDraw.DrawLine(ladderBox.Center + ladderMatrix.Down * ladderBox.HalfExtent.Y, ladderBox.Center + ladderMatrix.Up * ladderBox.HalfExtent.Y, "WeaponLaserIgnoreDepth", ref c, 0.01f); // } // // if(debugBox == null) // { // debugBox = new MyEntity(); // debugBox.Init(null, @"Models\Debug\Error.mwm", null, null, null); // debugBox.PositionComp.LocalMatrix = Matrix.Identity; // debugBox.Flags = EntityFlags.Visible | EntityFlags.NeedsDraw | EntityFlags.NeedsDrawFromParent | EntityFlags.InvalidateOnMove; // debugBox.OnAddedToScene(null); // debugBox.Render.Transparency = 0.5f; // debugBox.Render.RemoveRenderObjects(); // debugBox.Render.AddRenderObjects(); // } // var matrix = MatrixD.CreateWorld(ladderBox.Center, ladderMatrix.Forward, ladderMatrix.Up); // var scale = ladderBox.HalfExtent * 2; // MatrixD.Rescale(ref matrix, ref scale); // debugBox.PositionComp.SetWorldMatrix(matrix); //} ladder = l; ladderInternal = l as MyCubeBlock; break; } } if(ladder != null) { var offset = ladderInternal.BlockDefinition.ModelOffset; var charOnLadder = ladderMatrix.Translation + ladderMatrix.Forward * (offset.Z + EXTRA_OFFSET_Z); if(ladder.CubeGrid.GridSizeEnum == MyCubeSize.Large) charOnLadder += ladderMatrix.Backward; if(++skipRetryGravity >= GRAVITY_UPDATERATE) { skipRetryGravity = 0; alignedToGravity = true; var gravity = MyParticlesManager.CalculateGravityInPoint(character.WorldMatrix.Translation); var gravityLength = gravity.Normalize(); if(gravityLength > 0) { float gravDot = Vector3.Dot(gravity, ladderMatrix.Down); if(!(gravDot >= 0.9f || gravDot <= -0.9f)) { alignedToGravity = false; } } } //bool readInput = InputHandler.IsInputReadable(); // HACK use before GetPressedOr() once ActiveGameplayScreen's NRE is resolved if(!alignedToGravity) { bool pressed = InputHandler.GetPressedOr(settings.useLadder1, settings.useLadder2, false, false) && InputHandler.IsInputReadable(); // needs to support hold-press, so don't set JustPressed to true! if(pressed) SetLadderStatus("Gravity not parallel to ladder!", MyFontEnum.Red, 1000); if(usingLadder != null) ExitLadder(false); return; } if(usingLadder == null) // first ladder interaction { //var controlUse = MyAPIGateway.Input.GetGameControl(MyControlsSpace.USE); // //if(!controlUse.IsPressed()) //{ // string assigned = (controlUse.GetKeyboardControl() != MyKeys.None ? MyAPIGateway.Input.GetKeyName(controlUse.GetKeyboardControl()) : (controlUse.GetMouseControl() != MyMouseButtonsEnum.None ? MyAPIGateway.Input.GetName(controlUse.GetMouseControl()) : "(NONE)")) + (controlUse.GetSecondKeyboardControl() != MyKeys.None ? " or " + MyAPIGateway.Input.GetKeyName(controlUse.GetSecondKeyboardControl()) : null); // SetLadderStatus("Press "+assigned+" to use the ladder.", MyFontEnum.White); // return; //} if(grabOnLoad) { grabOnLoad = false; } else { aimingAtLadder = true; if(settings.useLadder1 == null && settings.useLadder2 == null) { SetLadderStatus("Ladder interaction is unassigned, edit the settings.cfg file!\nFor now you can use the USE key.", MyFontEnum.Red); if(!MyAPIGateway.Input.IsGameControlPressed(MyControlsSpace.USE)) return; } else { bool pressed = InputHandler.GetPressedOr(settings.useLadder1, settings.useLadder2, false, false) && InputHandler.IsInputReadable(); // needs to support hold-press, so don't set JustPressed to true! if(!pressed) { #if !STABLE // STABLE CONDITION if(!highlightedLadders.Contains(ladder.EntityId)) { if(highlightedLadders.Count > 0) { foreach(var id in highlightedLadders) { MyVisualScriptLogicProvider.SetHighlight(LADDER_NAME_PREFIX + id, false); } highlightedLadders.Clear(); } var envDef = MyDefinitionManager.Static.EnvironmentDefinition; var color = envDef.ContourHighlightColor; var thick = (int)envDef.ContourHighlightThickness; highlightedLadders.Add(ladder.EntityId); MyVisualScriptLogicProvider.SetHighlight(ladder.Name, true, thick, LADDER_HIGHLIGHT_PULSE, color); var ladderGrid = ladder.CubeGrid; for(int i = 1; i <= 10; i++) { var slim = ladderGrid.GetCubeBlock(ladderGrid.WorldToGridInteger(ladderMatrix.Translation + ladderMatrix.Up * i * ladderGrid.GridSize)); if(slim?.FatBlock?.GameLogic?.GetAs<LadderLogic>() == null) break; var id = slim.FatBlock.EntityId; highlightedLadders.Add(id); MyVisualScriptLogicProvider.SetHighlight(LADDER_NAME_PREFIX + id, true, thick, LADDER_HIGHLIGHT_PULSE, color); } for(int i = 1; i <= 10; i++) { var slim = ladderGrid.GetCubeBlock(ladderGrid.WorldToGridInteger(ladderMatrix.Translation + ladderMatrix.Down * i * ladderGrid.GridSize)); if(slim?.FatBlock?.GameLogic?.GetAs<LadderLogic>() == null) break; var id = slim.FatBlock.EntityId; highlightedLadders.Add(id); MyVisualScriptLogicProvider.SetHighlight(LADDER_NAME_PREFIX + id, true, thick, LADDER_HIGHLIGHT_PULSE, color); } } #endif SetLadderStatus("Press " + InputHandler.GetFriendlyStringOr(settings.useLadder1, settings.useLadder2) + " to use the ladder.", MyFontEnum.White); return; } } } skipRefreshAnim = 60; mounting = (controllingCharacter ? ALIGN_STEP : 2); usingLadder = ladder; SendLadderData(LadderAction.MOUNT, entId: ladder.EntityId); LadderAnim(character, LadderAnimation.MOUNTING); } if(usingLadder != ladder) { usingLadder = ladder; SendLadderData(LadderAction.CHANGE_LADDER, entId: ladder.EntityId); } if(charCtrl.Entity.Physics == null) { ExitLadder(false); return; } if(settings.clientPrediction) character.Physics.LinearVelocity = ladder.CubeGrid.Physics.GetVelocityAtPoint(character.WorldMatrix.Translation); // sync velocity with the ladder if(skipRefreshAnim > 0 && --skipRefreshAnim == 0) // force refresh animation after mounting due to an issue { var anim = lastLadderAnim; lastLadderAnim = LadderAnimation.NONE; LadderAnim(character, anim); } if(mounting <= 1) // mounting on ladder sequence { mounting *= ALIGN_MUL; if(settings.clientPrediction) { float align = Vector3.Dot(ladderMatrix.Up, character.WorldMatrix.Up); var matrix = MatrixD.CreateFromDir(ladderMatrix.Backward, (align > 0 ? ladderMatrix.Up : ladderMatrix.Down)); var halfY = ((ladderInternal.BlockDefinition.Size.Y * ladder.CubeGrid.GridSize) / 2); var diff = Vector3D.Dot(character.WorldMatrix.Translation, ladderMatrix.Up) - Vector3D.Dot(charOnLadder, ladderMatrix.Up); matrix.Translation = charOnLadder + ladderMatrix.Up * MathHelper.Clamp(diff, -halfY, halfY); character.SetWorldMatrix(MatrixD.SlerpScale(character.WorldMatrix, matrix, MathHelper.Clamp(mounting, 0.0f, 1.0f))); } if(mounting >= 0.75f && charCtrl.EnabledThrusts) // delayed turning off thrusts because gravity aligns you faster and can make you fail to attach to the ladder charCtrl.SwitchThrusts(); SetLadderStatus("Mounting ladder...", MyFontEnum.White); return; } // TODO jetpack assited climb/descend ? / gravity assisted descend ? if(charCtrl.EnabledThrusts) { if(!learned[4]) learned[4] = true; ExitLadder(false); // leave ladder if jetpack is turned on return; } // HACK use once input reading NRE is fixed //if(!controllingCharacter) // disable ladder control if not controlling character // readInput = false; bool movingSideways = false; var analogInput = MyAPIGateway.Input.GetPositionDelta(); // HACK use in-line once NRE is fixed if(!controllingCharacter) analogInput = Vector3.Zero; else if(analogInput.LengthSquared() > 0 && !InputHandler.IsInputReadable()) analogInput = Vector3.Zero; if(analogInput.Y < 0) // crouch { if(!learned[4]) learned[4] = true; ExitLadder(false); return; } float move = MathHelper.Clamp((float)Math.Round(-analogInput.Z, 1), -1, 1); // forward/backward float side = MathHelper.Clamp((float)Math.Round(analogInput.X, 1), -1, 1); // left/right //float move = readInput ? MathHelper.Clamp(MyAPIGateway.Input.GetGameControlAnalogState(MyControlsSpace.FORWARD) - MyAPIGateway.Input.GetGameControlAnalogState(MyControlsSpace.BACKWARD), -1, 1) : 0; //float side = readInput ? MathHelper.Clamp(MyAPIGateway.Input.GetGameControlAnalogState(MyControlsSpace.STRAFE_RIGHT) - MyAPIGateway.Input.GetGameControlAnalogState(MyControlsSpace.STRAFE_LEFT), -1, 1) : 0; var alignVertical = ladderMatrix.Up.Dot(character.WorldMatrix.Up); if(!loadedAllLearned) { bool allLearned = (learned[0] && learned[1] && learned[2] && learned[3] && learned[4]); for(int i = 0; i < learned.Length; i++) { if(learnNotify[i] == null) learnNotify[i] = MyAPIGateway.Utilities.CreateNotification(""); learnNotify[i].Text = (learned[i] ? LEARN_CHECK : LEARN_UNCHECK) + learnText[i]; learnNotify[i].Font = (learned[i] ? MyFontEnum.White : MyFontEnum.DarkBlue); learnNotify[i].AliveTime = (allLearned ? 1000 : 100); learnNotify[i].Show(); } if(allLearned) { SaveLearn(); loadedAllLearned = true; } } var view = MyAPIGateway.Session.ControlledObject.GetHeadMatrix(false, true); float lookVertical = Vector3.Dot(character.WorldMatrix.Up, view.Forward); if(settings.relativeControls) // climbing relative to camera { float verticalModifier = MathHelper.Clamp((lookVertical + 0.65f) / 0.5f, -0.5f, 1.0f); if(verticalModifier < 0) verticalModifier *= 2; move = (float)Math.Round(move * verticalModifier, 1); } if(analogInput.Y > 0) // jump { if(characterMovementState == MyCharacterMovementEnum.Jump) // this is still fine for avoiding jump as the character still is able to jump without needing feet on the ground { ExitLadder(false); // only release if on the floor as the character will jump regardless return; } if(settings.clientPrediction) character.Physics.LinearVelocity += view.Forward * (characterDefinition == null ? VEL_JUMP : 200 * characterDefinition.JumpForce) * TICKRATE; SendLadderData(LadderAction.JUMP_OFF, vec: view.Forward); LadderAnim(character, LadderAnimation.JUMP_OFF); if(!learned[3]) learned[3] = true; ExitLadder(false); return; } bool sprint = (characterDefinition != null && characterDefinition.Jetpack != null && MyAPIGateway.Input.IsGameControlPressed(MyControlsSpace.SPRINT)); if(Math.Abs(side) > 0.0001f) { if(settings.relativeControls) // side dismounting relative to camera { var alignForward = ladderMatrix.Backward.Dot(character.WorldMatrix.Forward); if(alignForward < 0) side = -side; } float speed = (characterDefinition == null ? (sprint ? VEL_SIDE : VEL_CLIMB) : CHAR_SPEED_MUL * (sprint ? characterDefinition.MaxSprintSpeed : characterDefinition.MaxRunStrafingSpeed)); if(settings.clientPrediction) character.Physics.LinearVelocity += side * (alignVertical > 0 ? ladderMatrix.Left : ladderMatrix.Right) * speed * TICKRATE; LadderAnim(character, (side > 0 ? LadderAnimation.DISMOUNT_LEFT : LadderAnimation.DISMOUNT_RIGHT)); movingSideways = true; if(!learned[2]) learned[2] = true; } else { // aligning player to ladder if(settings.clientPrediction) { Vector3 dir = charOnLadder - charPos; Vector3 vel = dir - (ladderMatrix.Up * Vector3D.Dot(dir, ladderMatrix.Up)); // projecting up/down direction to ignore it float len = vel.Normalize(); if(len >= ALIGN_ACCURACY) { float speed = (characterDefinition == null ? (sprint ? VEL_SIDE : VEL_CLIMB) : CHAR_SPEED_MUL * (sprint ? characterDefinition.MaxRunStrafingSpeed : characterDefinition.MaxWalkStrafingSpeed)); len = MathHelper.Clamp(len, 0.1f, 1); character.Physics.LinearVelocity += vel * len * speed * TICKRATE; } } } // TODO find a way to control view { //var ctrl2 = (Sandbox.Game.Entities.IMyControllableEntity)character; // //ctrl2.HeadLocalYAngle = 0; //ctrl2.HeadLocalXAngle = 0; // // or MoveAndRotate() ? // or... angularvelocity ? // or I dunno! } if(Math.Abs(move) > 0.0001f) { if(!learned[0]) learned[0] = true; var halfY = ((ladderInternal.BlockDefinition.Size.Y * ladder.CubeGrid.GridSize) / 2); var edge = charOnLadder + ((alignVertical > 0 ? ladderMatrix.Up : ladderMatrix.Down) * halfY); // climb over at the end when climbing up if(move > 0 && Vector3D.DistanceSquared(charPos, edge) <= 0.0625) // 0.25 squared { var nextBlockWorldPos = ladderMatrix.Translation + ladderMatrix.Forward * offset.Z + ((alignVertical > 0 ? ladderMatrix.Up : ladderMatrix.Down) * (halfY + 0.1f)); var nextBlockPos = ladder.CubeGrid.WorldToGridInteger(nextBlockWorldPos); var slim = ladder.CubeGrid.GetCubeBlock(nextBlockPos); // if the next block is not a ladder, dismount if(slim == null || !(slim.FatBlock is IMyTerminalBlock) || !LadderLogic.ladderIds.Contains(slim.FatBlock.BlockDefinition.SubtypeId)) { dismounting = ALIGN_STEP; SendLadderData(LadderAction.DISMOUNT); return; } } // on the floor and moving backwards makes you dismount if(move < 0) { var feetStart = character.WorldMatrix.Translation + character.WorldMatrix.Up * 0.2; // a bit higher because the floor might clip through the character var feetTarget = feetStart + character.WorldMatrix.Down * 0.3; IHitInfo hit; if(MyAPIGateway.Physics.CastRay(feetStart, feetTarget, out hit, COLLISSIONLAYER_NOCHARACTER)) { // need to check the block under the ladder if it's anything but a ladder because "standing" stance occurs when character rests on its chest-sphere collision mesh too var prevBlockWorldPos = ladderMatrix.Translation + ladderMatrix.Forward * offset.Z + (alignVertical > 0 ? ladderMatrix.Down : ladderMatrix.Up) * (halfY + 0.1f); var prevBlockPos = ladder.CubeGrid.WorldToGridInteger(prevBlockWorldPos); var slim = ladder.CubeGrid.GetCubeBlock(prevBlockPos); // if it's not a ladder, check the distance and confirm your feet are close to its edge if(slim == null || !(slim.FatBlock is IMyTerminalBlock) || !LadderLogic.ladderIds.Contains(slim.FatBlock.BlockDefinition.SubtypeId)) { // get the block's edge and the character feet position only along the ladder's up/down axis var blockPosProjectedUp = ladderMatrix.Up * Vector3D.Dot(prevBlockWorldPos, ladderMatrix.Up); var charPosProjectedUp = ladderMatrix.Up * Vector3D.Dot(character.WorldMatrix.Translation, ladderMatrix.Up); if(Vector3D.DistanceSquared(blockPosProjectedUp, charPosProjectedUp) <= 0.04) // 0.2 squared { ExitLadder(false); // to recap: if you're moving char-relative down and in "standing" stance and the block below is not a ladder and you're closer than 0.1m to its edge, then let go of the ladder. return; } } } } // climbing on the ladder if(!learned[1] && sprint) learned[1] = true; float speed = (characterDefinition == null ? (sprint ? VEL_SPRINT : VEL_CLIMB) : CHAR_SPEED_MUL * (sprint ? characterDefinition.MaxSprintSpeed : characterDefinition.MaxRunSpeed)); if(settings.clientPrediction) character.Physics.LinearVelocity += (alignVertical > 0 ? ladderMatrix.Up : ladderMatrix.Down) * move * speed * TICKRATE; if(!movingSideways) LadderAnim(character, (move > 0 ? LadderAnimation.UP : LadderAnimation.DOWN)); } else if(!movingSideways) { LadderAnim(character, LadderAnimation.IDLE); } SendLadderData(LadderAction.CLIMB, climb: (alignVertical > 0 ? move : -move), side: (alignVertical > 0 ? side : -side), sprint: sprint); return; } } ExitLadder(); }
private void ExitLadder(bool setFreeFallAnimation = true) { if(grabOnLoad) grabOnLoad = false; if(usingLadder == null) return; SendLadderData(LadderAction.LET_GO); usingLadder = null; alignedToGravity = false; skipRetryGravity = GRAVITY_UPDATERATE; if(MyAPIGateway.Session.ControlledObject is IMyCharacter && setFreeFallAnimation && character != null && lastLadderAnim != LadderAnimation.NONE) { LadderAnim(character, LadderAnimation.JUMP_OFF); } lastLadderAnim = LadderAnimation.NONE; }