Created
July 27, 2018 10:02
-
-
Save Srekel/1a3788f9651946ec29ae4ea95040379a to your computer and use it in GitHub Desktop.
The jumping state for characters in The Showdown Effect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- This is the jumping state for characters in The Showdown Effect (Paradox/Arrowhead/Pixeldiet). | |
| -- https://store.steampowered.com/app/204080/The_Showdown_Effect/ | |
| -- Uploaded to show how complex player control code can be. | |
| -- In addition to this file there are 34 other states the player character can be in. Thirty four! | |
| -- Judging by file size the jumping state is the third most complicated. | |
| -- | |
| -- The purpose of these states is to read the player's input, check the character's current state, and evaluate | |
| -- what should happen next (either do something specific to the state or switch to another state) | |
| -- | |
| -- There may be bugs here - this is potentially not the very last version shipped in the game, and the | |
| -- game certainly had bugs, though it's character control was nifty :) | |
| -- Note that none of the "physics" is done in the state, that's done in a completely different system. | |
| -- | |
| -- You can direct questions to @srekel :) | |
| local EntityAux_set_input = EntityAux.set_input | |
| local EntityAux_state = EntityAux.state | |
| local math_abs = math.abs | |
| local math_sign = math.sign | |
| local Quaternion_forward = Quaternion.forward | |
| local Unit_animation_event = Unit.animation_event | |
| local Unit_local_rotation = Unit.local_rotation | |
| local Vector3_unbox = Vector3.unbox | |
| CharacterStateJumping = class(CharacterStateJumping, CharacterStateBase) | |
| function CharacterStateJumping:init(context) | |
| CharacterStateBase.init(self, context, "jumping") | |
| end | |
| function CharacterStateJumping:on_enter(context) | |
| local unit = self._unit | |
| local state = context.state | |
| local input = context.input | |
| local input_data = input.input_data | |
| local network_transport = context.network_transport | |
| local char_state_input = context.char_state_input | |
| local movement_settings = MovementSettings.get_movement_settings(unit) | |
| StateContext.event():trigger("character_voice_event", unit, "jump") | |
| if char_state_input.from_sliding then | |
| network_transport:send_animation_event(unit, "slide_jump") | |
| Unit.animation_event(unit, "slide_jump") | |
| elseif char_state_input.from_rolling then | |
| network_transport:send_animation_event(unit, "jump") | |
| Unit.animation_event(unit, "jump") | |
| elseif char_state_input.from_walking then | |
| network_transport:send_animation_event(unit, "jump_forward") | |
| Unit.animation_event(unit, "jump_forward") | |
| else | |
| network_transport:send_animation_event(unit, "jump") | |
| Unit.animation_event(unit, "jump") | |
| end | |
| EntityAux.state(unit, "locomotion").touch_ground = false | |
| EntityAux_set_input(unit, "locomotion", "force_in_air", true) | |
| EntityAux_set_input(unit, "locomotion", "max_velocity", nil) | |
| EntityAux_set_input(unit, "locomotion", "max_velocity", movement_settings.large_velocity) | |
| -- Flow event for playing effects | |
| Unit.flow_event(unit, "jump") | |
| network_transport:send_flow_event(unit, "jump") | |
| -- Hold down jump and you jump farther, simply tap it for a microsecond and you do a tiny skip | |
| self.wanted_velocity = char_state_input.wanted_velocity | |
| if math.abs(input_data.move.x) < 0.2 and self.wanted_velocity then | |
| self.wanted_velocity = table.clone(self.wanted_velocity) | |
| self.wanted_velocity[1] = 0 | |
| end | |
| self.max_jump_time = 1 | |
| self.jump_timer = 0.0 | |
| context.state.can_block = true | |
| context.state.can_enter_elevator = false | |
| if char_state_input.forced_direction then | |
| EntityAux.set_input(unit, "locomotion", "auto_rotate", false) | |
| else | |
| EntityAux.set_input(unit, "locomotion", "auto_rotate", "air") | |
| end | |
| if input.aiming then | |
| Unit.animation_event(unit, "aim") | |
| context.network_transport:send_animation_event(unit, "aim") | |
| EntityAux.set_input(unit, "locomotion", "auto_rotate", "aim") | |
| elseif input.blocking then | |
| EntityAux.set_input(unit, "locomotion", "auto_rotate", false) | |
| else | |
| local direction = char_state_input.forced_direction or math.sign(input_data.move.x) | |
| char_state_input.forced_direction = nil | |
| if direction == 0 then | |
| direction = state.direction | |
| end | |
| if direction == 1 and state.direction ~= 1 then | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_left) | |
| elseif direction == -1 and state.direction ~= -1 then | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_right) | |
| end | |
| end | |
| if self.wanted_velocity then | |
| EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", nil) | |
| EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", { wanted_velocity=self.wanted_velocity, time=0.05 }) | |
| self.up_acceleration = 100 | |
| self.up_acc = {0,0,self.up_acceleration} | |
| else | |
| self.up_acceleration = nil | |
| end | |
| self.jump_boost_timer = 0 | |
| self.from_ropeclimbing = context.char_state_input.from_ropeclimbing | |
| if context.char_state_input.from_ladderclimbing then | |
| self.ladderclimbing_timer = 0.5 | |
| else | |
| self.ladderclimbing_timer = nil | |
| end | |
| self.climb_unit = state.climb_focus | |
| self.enable_wallslide = not context.char_state_input.disable_wallslide | |
| self.wallslide_timer = movement_settings.jump_wallslide_timer | |
| self.air_control_acceleration = {0,0,0} | |
| self.from_sprinting = context.char_state_input.from_sprinting | |
| self.matrix_jump = context.char_state_input.from_sliding | |
| self.has_been_moved = false | |
| self.ignore_air_control = context.char_state_input.ignore_air_control == true | |
| self.ledge_climb_buffered = false | |
| state.last_z_ground_pos = UnitCache_get_position(unit).z | |
| self.acceleration_set = false | |
| self.going_upwards = true | |
| end | |
| function CharacterStateJumping:on_exit(context) | |
| local unit = self._unit | |
| context.state.can_block = false | |
| context.state.can_enter_elevator = true | |
| EntityAux.append_input(unit, "locomotion", "remove_timed_acceleration", "character_state_jumping") | |
| EntityAux.set_input(unit, "locomotion", "timed_wanted_velocity", nil) | |
| -- EntityAux.set_input(unit, "locomotion", "clear_max", true) | |
| end | |
| function CharacterStateJumping:update(context) | |
| local dt = context.dt | |
| local input = context.input | |
| local input_data = input.input_data | |
| local internal = context.internal | |
| local state = context.state | |
| local em = context.entity_manager | |
| local unit = context.unit | |
| local csm = self._csm | |
| local movement_settings = MovementSettings.get_movement_settings(unit) | |
| local loco_ext = internal.loco_ext | |
| local velocity = Vector3.unbox(loco_ext.state.velocity) | |
| self.jump_timer = self.jump_timer + dt | |
| self.jump_boost_timer = self.jump_boost_timer + dt | |
| if self.up_acceleration ~= nil and input.held_time.jump ~= 0 and self.jump_timer <= 0.2 and not self.acceleration_set then | |
| self.up_acc[3] = self.up_acceleration | |
| EntityAux.set_input(unit, "locomotion", "force_in_air", true) | |
| EntityAux.append_input(unit, "locomotion", "timed_acceleration", { acceleration=self.up_acc, id="character_state_jumping", time=0.2 }) | |
| self.acceleration_set = true | |
| self.going_upwards = true | |
| elseif self.jump_timer <= 0.2 and input_data.jump < 0.1 then | |
| EntityAux.append_input(unit, "locomotion", "remove_timed_acceleration", "character_state_jumping") | |
| self.going_upwards = false | |
| elseif self.jump_timer > 0.2 then | |
| self.going_upwards = false | |
| end | |
| if input_data.move.y > 0 and not input.attacking then | |
| local found = nil | |
| local d_swing = state.swing_colliders.up | |
| for k,v in pairs(d_swing) do | |
| found = v | |
| break | |
| end | |
| if found then | |
| context.swing_info = found | |
| csm:change_state(context, "swinging") | |
| return | |
| end | |
| end | |
| local can_not_air_control = self.ignore_air_control or math.sign(input_data.move.x) == math.sign(velocity.x) and math.abs(velocity.x) > 7 | |
| if not can_not_air_control then -- eew | |
| Vector3.box(self.air_control_acceleration, 40 * Vector3(math.sign(input_data.move.x), 0, 0)) | |
| EntityAux.append_input(unit, "locomotion", "acceleration", self.air_control_acceleration) | |
| end | |
| if loco_ext.state.touch_left then | |
| if input.justpressed.jump then | |
| EntityAux.state(unit, "locomotion").in_air = true | |
| EntityAux.state(unit, "locomotion").touch_left = false | |
| local jump_velocity = movement_settings.walljump_velocity | |
| jump_velocity[1] = math.abs(jump_velocity[1]) * 1 | |
| EntityAux.set_input(unit, "locomotion", "wanted_velocity", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_velocity", jump_velocity) | |
| context.char_state_input.forced_direction = 1 | |
| context.char_state_input.ignore_air_control = true | |
| end | |
| elseif loco_ext.state.touch_right then | |
| if input.justpressed.jump then | |
| EntityAux.state(unit, "locomotion").in_air = true | |
| EntityAux.state(unit, "locomotion").touch_right = false | |
| local jump_velocity = movement_settings.walljump_velocity | |
| jump_velocity[1] = math.abs(jump_velocity[1]) * -1 | |
| EntityAux.set_input(unit, "locomotion", "wanted_velocity", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_velocity", jump_velocity) | |
| context.char_state_input.forced_direction = -1 | |
| context.char_state_input.ignore_air_control = true | |
| end | |
| end | |
| local char_state_input = context.char_state_input | |
| if char_state_input.forced_direction then | |
| EntityAux.set_input(unit, "locomotion", "auto_rotate", false) | |
| local direction = char_state_input.forced_direction or math.sign(input_data.move.x) | |
| char_state_input.forced_direction = nil | |
| if direction == 0 then | |
| direction = state.direction | |
| end | |
| if direction == 1 and state.direction ~= 1 then | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_left) | |
| elseif direction == -1 and state.direction ~= -1 then | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", nil) | |
| EntityAux.set_input(unit, "locomotion", "wanted_rotation", movement_settings.rotation_right) | |
| end | |
| end | |
| if input.justpressed.jump then | |
| self.ledge_climb_buffered = true | |
| end | |
| if loco_ext.state.touch_ground and self.jump_timer > 0.2 then | |
| if math.abs(input_data.move.x) ~= 0 then | |
| csm:change_state(context, "walking") | |
| elseif loco_ext.state.roof_available then | |
| csm:change_state(context, "crouching") | |
| else | |
| csm:change_state(context, "standing") | |
| end | |
| return | |
| end | |
| if state.rope_colliding_grab and input_data.move.y ~= 0 then | |
| if self.from_ropeclimbing then | |
| if self.climb_unit ~= state.climb_focus then | |
| csm:change_state(context, "ropeclimbing") | |
| return | |
| end | |
| else | |
| csm:change_state(context, "ropeclimbing") | |
| return | |
| end | |
| end | |
| if self.ladderclimbing_timer and self.ladderclimbing_timer > 0.0 then | |
| self.ladderclimbing_timer = self.ladderclimbing_timer - dt | |
| elseif state.ladder_colliding_head and state.ladder_colliding_feet and input_data.move.y > 0 and not input.firing_heavy_weapon then | |
| context.char_state_input.from_jumping = true | |
| csm:change_state(context, "ladderclimbing") | |
| return | |
| end | |
| if loco_ext.state.moved_this_frame then | |
| self.has_been_moved = true | |
| end | |
| local ls = loco_ext.state | |
| local ledge_available = ls.ledge_valid and ls.ledge_available and not ls.roof_available | |
| if (input_data.jump > 0.2 or input_data.move.y > 0.2) and ledge_available and self.has_been_moved then | |
| local dir_x = math.sign(Quaternion.forward(Unit.local_rotation(unit, 0)).x) | |
| local left_check = ls.ledge_available_left and input_data.move.x < -0.75 | |
| local right_check = ls.ledge_available_right and input_data.move.x > 0.75 | |
| if left_check or right_check then | |
| context.ledge_position = ls.ledge_position | |
| context.char_state_input.from_jumping = true | |
| context.char_state_input.ledge_climb_buffered = self.ledge_climb_buffered | |
| csm:change_state(context, "ledgeclimbing") | |
| return | |
| end | |
| end | |
| local velocity = Vector3.unbox(loco_ext.state.velocity) | |
| if velocity.z < 0 and not self.going_upwards then | |
| context.char_state_input.allow_air_control = true | |
| context.char_state_input.matrix_jump = self.matrix_jump | |
| context.char_state_input.from_sprinting = self.from_sprinting | |
| context.char_state_input.ledge_climb_buffered = self.ledge_climb_buffered | |
| csm:change_state(context, "in_air") | |
| return | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment