summaryrefslogtreecommitdiffstats
path: root/player.gd
blob: 2fdd1e76ab4debba2f1266411edfdc1166cfc83c (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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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,
	HANG_TWEEN,
	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
	
	hanging_t = 0
	hanging_L = hang_dist
	hanging_target = target
	
	# Add tongue line
	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)
	
	var hyp = target.global_position - global_position
	var sign_x = sign(hyp.x)
	var angle_below_horiz = 15
	var angle_below_horiz_r = deg_to_rad(angle_below_horiz)
	var tweening_target_point = hanging_target.global_position \
		- Vector2(sign_x * hanging_L * cos(angle_below_horiz_r),
				  -hanging_L * sin(angle_below_horiz_r))

	# if player is above the hook, we need to tween to horizontal
	if tweening_target_point.y - global_position.y > 0:
		movement_type = MovementType.HANG_TWEEN
		movement_handler = _movement_hang_tween
		return

	movement_type = MovementType.HANGING
	movement_handler = _movement_hanging

	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)
#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

func _movement_hang_tween(delta: float):
	var tween_speed = jump_strength
	var hyp = hanging_target.global_position - global_position
	var sign_x = sign(hyp.x)
	
	var angle_below_horiz = 15
	var angle_below_horiz_r = deg_to_rad(angle_below_horiz)
	var tweening_target_point = hanging_target.global_position \
		- Vector2(sign_x * hanging_L * cos(angle_below_horiz_r),
				  -hanging_L * sin(angle_below_horiz_r))
	
	var tween_hyp = tweening_target_point - global_position
	var tween_dir: Vector2 = tween_hyp.normalized()
	var v: Vector2 = tween_dir * tween_speed
	var s: Vector2 = v * delta

	if tween_hyp.length() < s.length():
		movement_type = MovementType.HANGING
		movement_handler = _movement_hanging
		hanging_theta0 = deg_to_rad(-sign_x * (90 - angle_below_horiz))
	else:
		position += s
	
	hanging_line2D.points[1] = global_position - hanging_target.global_position
	pass
#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)