summaryrefslogtreecommitdiffstats
path: root/player.gd
blob: 738b5ddf06d7fed52ff2a9ed37f5206b967e1ec2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
class_name Player
extends CharacterBody2D

@export var speed: float
@export var jump_strength: float
@export var coyote_max_ms: float
@export var jump_held_coef: float
@export var hook_distance_max: float

## float (-1.0 .. 1.0) or null
var forced_input_x: Variant

var curve_t: float = 0
var curve_target: Curve2D
var curve_speed: float
var curve_dir: int

var hanging_target: HookHanging
var hanging_t: float
var hanging_L: float
var hanging_theta0: float
var hanging_line2D: Line2D

enum MovementType {
	NORMAL,
	FORCED,
	CURVE,
	HANGING,
}
var movement_type = MovementType.NORMAL
var movement_handler: Callable = _movement_normal
var movement_handlers = {
	MovementType.NORMAL: _movement_normal,
	MovementType.FORCED: _movement_forced,
	MovementType.CURVE: _movement_curve,
}

var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var prev_dir: float = 0.0
var jump_was_released: bool = true

# coyote = 0 indicates on floor
# coyote > 0 indicates seconds since on floor
var coyote: float = 0

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	pass
	
func get_facing() -> int:
	if $AnimatedSprite2D.flip_h:
		return -1
	else:
		return 1

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta: float) -> void:
	if velocity.x > 0:
		$AnimatedSprite2D.flip_h = false
	elif velocity.x < 0:
		$AnimatedSprite2D.flip_h = true
	
	if not is_on_floor():
		$AnimatedSprite2D.set_animation("jump")
	elif abs(velocity.x) > 0.0:
		$AnimatedSprite2D.set_animation("walk")
	else:
		$AnimatedSprite2D.set_animation("idle")

func _physics_process(delta: float):
	movement_handler.call(delta)

#region movement type setters
func set_movement_normal():
	movement_type = MovementType.NORMAL
	movement_handler = _movement_normal
	
func set_movement_forced(dir: String):
	if movement_type == MovementType.FORCED: return

	movement_type = MovementType.FORCED
	movement_handler = _movement_forced

	var jump_coef = 1.3

	if dir == "LEFT":
		forced_input_x = -1.0
	elif dir == "JUMP_LEFT":
		velocity.y = -jump_coef * jump_strength
		forced_input_x = -1.0
	elif dir == "RIGHT":
		forced_input_x = 1.0
	elif dir == "JUMP_RIGHT":
		velocity.y = -jump_coef * jump_strength
		forced_input_x = 1.0
	elif dir == "JUMP":
		velocity.y = -jump_strength
		forced_input_x = 0.0
		gravity = 0.0
	elif dir == "DROP":
		forced_input_x = 0.0

func set_movement_curve(curve: Curve2D, speed: float, reverse: bool):
	if movement_type == MovementType.CURVE: return

	movement_type = MovementType.CURVE
	movement_handler = _movement_curve

	curve_target = curve
	curve_speed = speed
	if reverse:
		curve_dir = -1
		curve_t = 1
	else:
		curve_dir = 1
		curve_t = 0

func set_movement_hanging(target: Node2D, hang_dist: float):
	if movement_type == MovementType.HANGING: return
	
	movement_type = MovementType.HANGING
	movement_handler = _movement_hanging
	
	hanging_t = 0
	hanging_L = hang_dist
	hanging_target = target
	
	var hyp = target.global_position - global_position
	# if player is above the hook, we need to snap to horizontal
	if hyp.y > 0: hyp.y = 0

	hanging_theta0 = atan2(-hyp.y, hyp.x) - PI/2
	if hanging_theta0 < -PI: hanging_theta0 += 2*PI
	print_debug("hyp: ", hyp, ", hanging_theta0: ", hanging_theta0)
	
	hanging_line2D = Line2D.new()
	hanging_line2D.width = 2
	hanging_line2D.add_point(Vector2.ZERO)
	hanging_line2D.add_point(Vector2.ZERO) # set in movement handler
	target.add_child(hanging_line2D)
#endregion

#region movement handlers
func _movement_normal(delta: float):
	var grav_coef = 1.0
	if velocity.y < 0 and Input.is_action_pressed("jump"):
		grav_coef = 1.0 + (jump_held_coef * velocity.y / jump_strength)
	
	velocity.y += grav_coef * gravity * delta
	if velocity.y > jump_strength:
		velocity.y = jump_strength

	if is_on_floor():
		coyote = 0
	else:
		coyote += delta

	var can_jump = coyote < (coyote_max_ms * 0.001) and jump_was_released
	if Input.is_action_pressed("jump") and can_jump:
		velocity.y = -jump_strength
		# Force coyote time to expire to forbid double jumps
		coyote = coyote_max_ms
		jump_was_released = false

	if not Input.is_action_pressed("jump"):
		jump_was_released = true
		
	if Input.is_action_just_pressed("action"):
		handle_action_input()
	
	var direction: float
	if Input.is_action_pressed("move_left") and Input.is_action_pressed("move_right"):
		# This is a fix to the stalled movement issue
		# where the player stops if both x inputs are pressed at the same time.
		# To fix it, we store the previous direction the player was moving in, and change
		# the direction when both buttons are held. This is what the player wants nearly always.
		direction = -prev_dir
	else:
		direction = Input.get_axis("move_left", "move_right")
		prev_dir = direction
	
	velocity.x = direction * speed

	move_and_slide()

func _movement_forced(delta: float):
	velocity.y += gravity * delta
	if velocity.y > jump_strength:
		velocity.y = jump_strength

	velocity.x = forced_input_x * speed

	move_and_slide()

func _movement_curve(delta: float):
	curve_t += curve_dir * curve_speed * delta
	var finished: bool
	if curve_dir == 1:
		finished = curve_t >= 1.0
	else:
		finished = curve_t <= 0.0
	
	var new_position = curve_target.sample_baked(curve_t * curve_target.get_baked_length(), true)
	velocity = (new_position - position) / delta

	if finished:
		set_movement_normal()
		curve_target = null
		return

	position = new_position
	
func _movement_hanging(delta: float):
	hanging_t += delta

	if Input.is_action_just_pressed("jump"):
		velocity.y = -jump_strength
		# Force coyote time to expire to forbid double jumps
		coyote = coyote_max_ms
		jump_was_released = false
		hanging_line2D.queue_free()
		set_movement_normal()

	var omega = sqrt(gravity / hanging_L)
	var theta = hanging_theta0 * cos(hanging_t * omega)
	global_position.x = hanging_target.global_position.x + hanging_L * sin(theta)
	global_position.y = hanging_target.global_position.y + hanging_L * cos(theta)
	
	hanging_line2D.points[1] = global_position - hanging_target.global_position
#endregion

func handle_action_input():
	var hooks = get_tree().get_nodes_in_group("hook_hanging")
	for hook_any in hooks:
		if not hook_any is HookHanging:
			print_debug("hook is not HookHanging!", hook_any)
			continue

		var hook: HookHanging = hook_any
		var dist = (hook.global_position - global_position).length()
		print_debug("found hook: ", hook, ", dist: ", dist)
		if dist < hook_distance_max:
			set_movement_hanging(hook, hook.hang_distance)