Player.gd 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. extends "res://entities/GridBasedMovable.gd"
  2. @export var can_pull = true
  3. @export var tool_change_time = 0.08
  4. @onready var ptm = get_node("RotationPoint/PlayerToolManager")
  5. @onready var audio_player = $AudioStreamPlayer3D
  6. @onready var bomb_counter = $BombCounter
  7. #var active_tool: PLAYERTOOLS = get_node("RotationPoint/PlayerToolManager").active_tool
  8. var requested_mesh_direction
  9. var distant_object_to_take = null
  10. var distant_tool_to_place = null
  11. var distant_bomb_to_place = false
  12. var enable_input = true
  13. var last_keys_pressed_amount = 0
  14. var first_move_started = false
  15. enum PLAYERTOOLS {NONE, CLEANING_TOOL, PUSH_TOOL, PULL_TOOL, GRINDING_TOOL}
  16. var tool_changing = false
  17. func _ready() -> void:
  18. super._ready()
  19. ptm.set_active_tool(PLAYERTOOLS.NONE)
  20. update_rotation_point(DIRECTIONS.UP)
  21. func _process(delta):
  22. handle_distant_request()
  23. handle_player_input()
  24. if ray_ground.is_colliding():
  25. var groundObject = ray_ground.get_collider()
  26. if groundObject != null:
  27. if groundObject.is_in_group("enemy"): #spiky things are done in the gridbasedmovable base class
  28. hit()
  29. func get_keys_pressed_amount():
  30. var keys = 0
  31. if Input.is_action_pressed("ui_right"):
  32. keys += 1
  33. if Input.is_action_pressed("ui_left"):
  34. keys += 1
  35. if Input.is_action_pressed("ui_up"):
  36. keys += 1
  37. if Input.is_action_pressed("ui_down"):
  38. keys += 1
  39. return keys
  40. func handle_distant_request():
  41. if is_grid_aligned():
  42. if distant_object_to_take != null:
  43. if distant_object_to_take.is_in_group("tool") and distant_object_to_take.has_method("take_tool"):
  44. distant_object_to_take = null
  45. update_tool()
  46. elif distant_object_to_take.is_in_group("bomb_item"):
  47. distant_object_to_take = null
  48. try_take_bomb()
  49. elif distant_bomb_to_place == true:
  50. distant_bomb_to_place = false
  51. put_bomb_down()
  52. elif distant_tool_to_place != null:
  53. update_tool()
  54. distant_tool_to_place = null
  55. #ptm.set_active_tool(PLAYERTOOLS.NONE)
  56. func hit():
  57. if health_node.invulnerable == false:
  58. audio_player.stream = load("res://sfx/ncl_player_hit.ogg")
  59. #audio_player.volume_db = -1
  60. audio_player.pitch_scale = randf_range(0.86, 0.93)
  61. audio_player.play()
  62. health_node.hurt()
  63. SceneManager.game_scene.update_labels()
  64. func kill():
  65. pass
  66. #health_node.kill()
  67. func movement_started():
  68. first_move_started = true
  69. if animation_player != null:
  70. animation_player.play("Player_Move", -1, 1.5 * (1/speed_modifier))
  71. func put_bomb_down():
  72. if protection_area.has_overlapping_areas():
  73. #if a movable has it's PlacementProtectionArea (at front) at Monitorable, then no item can be placed
  74. #default false for this area, but used for enemies
  75. return
  76. if tool_changing == true:
  77. return
  78. if !is_grid_aligned():
  79. distant_bomb_to_place = true
  80. return
  81. if requested_direction != DIRECTIONS.NONE or ray_front.is_colliding():
  82. return
  83. if bomb_counter.has_bombs():
  84. bomb_counter.use_bomb()
  85. else:
  86. print ("no bombs left to use!!!")
  87. return
  88. enable_movement = false
  89. await get_tree().create_timer(0.1).timeout
  90. var Bomb_Node
  91. #if put_down != PLAYERTOOLS.NONE:
  92. # if put_down == PLAYERTOOLS.CLEANING_TOOL:
  93. Bomb_Node = load("res://entities/objects/bomb/Bomb.tscn")
  94. var bomb_instance = Bomb_Node.instantiate()
  95. audio_player.stream = load("res://sfx/ncl_bomb_drop.ogg")
  96. audio_player.volume_db = -3
  97. audio_player.pitch_scale = randf_range(0.86, 0.93)
  98. audio_player.play()
  99. level.add_child(bomb_instance)
  100. var movedir = direction_to_vector(mesh_direction)
  101. bomb_instance.position = position + movedir * step_size
  102. bomb_instance.position.y = position.y
  103. bomb_instance._ready()
  104. bomb_instance.local_tween_position = bomb_instance.position
  105. enable_movement = true
  106. func put_tool_down(put_down):
  107. tool_changing = true
  108. enable_movement = false
  109. await get_tree().create_timer(tool_change_time).timeout
  110. tool_changing = false
  111. var Tool_Node
  112. if put_down != PLAYERTOOLS.NONE:
  113. if put_down == PLAYERTOOLS.CLEANING_TOOL:
  114. Tool_Node = load("res://entities/objects/tools/CleaningToolObject.tscn")
  115. elif put_down == PLAYERTOOLS.PUSH_TOOL:
  116. Tool_Node = load("res://entities/objects/tools/PushToolObject.tscn")
  117. elif put_down == PLAYERTOOLS.PULL_TOOL:
  118. Tool_Node = load("res://entities/objects/tools/PullToolObject.tscn")
  119. elif put_down == PLAYERTOOLS.GRINDING_TOOL:
  120. Tool_Node = load("res://entities/objects/tools/GrindingToolObject.tscn")
  121. else:
  122. print ("error: no tool???, put_down == " + str(put_down))
  123. return
  124. var tool_instance = Tool_Node.instantiate()
  125. audio_player.stream = load("res://sfx/ncl_tool.ogg")
  126. audio_player.volume_db = 6
  127. audio_player.pitch_scale = randf_range(0.86, 0.93)
  128. audio_player.play()
  129. #var game = SceneManager.game_scene
  130. #var level = game.level_scene_instance
  131. level.add_child(tool_instance)
  132. var movedir = direction_to_vector(mesh_direction)
  133. tool_instance.position = position + movedir * step_size
  134. tool_instance._ready()
  135. tool_instance.position.y = position.y
  136. tool_instance.local_tween_position = tool_instance.position
  137. enable_movement = true
  138. func try_take_bomb():
  139. if is_grid_aligned():
  140. var frontObject = ray_front.get_collider()
  141. if frontObject != null and frontObject.is_in_group("bomb_item"):
  142. frontObject.take_bomb()
  143. bomb_counter.add_bomb()
  144. func update_tool():
  145. if protection_area.has_overlapping_areas():
  146. #if a movable has it's PlacementProtectionArea (at front) at Monitorable, then no item can be placed
  147. #default false for this area, but used for enemies
  148. return
  149. if tool_changing == true:
  150. return
  151. var lying_tool = null
  152. var put_down = ptm.active_tool
  153. var can_put_down = true
  154. #do it with the old way when grid aligned
  155. if is_grid_aligned():
  156. if ray_front.is_colliding():
  157. var collided_with = ray_front.get_collider()
  158. if collided_with.is_class("GridMap"):
  159. return
  160. #step 1: check if there is a tool detected lying on the ground
  161. if ray_front.is_colliding():
  162. var frontObject = ray_front.get_collider()
  163. if frontObject != null: #fixme: maybe check if it is really a tool, has tool_type
  164. if frontObject.is_in_group("tool") and frontObject.has_method("take_tool"):
  165. lying_tool = frontObject
  166. elif frontObject.get_parent().is_in_group("tool") and frontObject.get_parent().has_method("take_tool"):
  167. lying_tool = frontObject.get_parent()
  168. else:
  169. can_put_down = false
  170. elif ray_downramp.is_colliding():
  171. var frontObject = ray_downramp.get_collider()
  172. if frontObject != null: #fixme: maybe check if it is really a tool, has tool_type
  173. if frontObject.is_in_group("tool"):
  174. lying_tool = frontObject
  175. elif frontObject.get_parent().is_in_group("tool") and frontObject.get_parent().has_method("take_tool"):
  176. lying_tool = frontObject.get_parent()
  177. if put_down == PLAYERTOOLS.NONE: #player has no tool to put down
  178. if lying_tool != null:
  179. lying_tool.take_tool()
  180. ptm.set_active_tool(lying_tool.tool_type + 1)
  181. else: #player has a tool:
  182. if can_put_down == false or (requested_direction != DIRECTIONS.NONE and lying_tool == null):
  183. return
  184. if lying_tool == null:
  185. put_tool_down(put_down)
  186. ptm.set_active_tool(PLAYERTOOLS.NONE)
  187. else:
  188. lying_tool.take_tool()
  189. ptm.set_active_tool(lying_tool.tool_type + 1)
  190. put_tool_down(put_down)
  191. else:
  192. var distant_object = level.find_object_at_position(self, distant_target_position)
  193. if distant_object != null:
  194. if distant_object.is_in_group("tool") and distant_object.has_method("take_tool") and distant_object_to_take == null:
  195. distant_object_to_take = distant_object
  196. if put_down != PLAYERTOOLS.NONE:
  197. distant_tool_to_place = put_down
  198. func _try_push(object, direction):
  199. if push(object, direction) == true:
  200. audio_player.stream = load("res://sfx/ncl_object_move.ogg")
  201. audio_player.volume_db = -9
  202. audio_player.pitch_scale = randf_range(0.8, 0.8)
  203. #audio_player.play() #fixme improve movement sfx
  204. func _handle_short_direction_change_impulse():
  205. if tool_changing:
  206. return
  207. if is_grid_aligned() and last_keys_pressed_amount == 0:
  208. if Input.is_action_just_pressed("ui_right"):
  209. requested_mesh_direction = DIRECTIONS.RIGHT
  210. elif Input.is_action_just_pressed("ui_left"):
  211. requested_mesh_direction = DIRECTIONS.LEFT
  212. elif Input.is_action_just_pressed("ui_up"):
  213. requested_mesh_direction = DIRECTIONS.UP
  214. elif Input.is_action_just_pressed("ui_down"):
  215. requested_mesh_direction = DIRECTIONS.DOWN
  216. else:
  217. requested_mesh_direction = DIRECTIONS.NONE
  218. if requested_mesh_direction != DIRECTIONS.NONE:
  219. if mesh_direction == requested_mesh_direction:
  220. #the player is facing in the direction it moves, skip short direction change impulse
  221. return
  222. enable_movement = false
  223. await get_tree().create_timer(0.06).timeout
  224. enable_movement = true
  225. update_rotation_point(requested_mesh_direction)
  226. #requested_direction = requested_mesh_direction
  227. func _handle_movement_input():
  228. if !enable_movement or tool_changing:
  229. return
  230. if Input.is_action_just_pressed("ui_right"):
  231. requested_direction = DIRECTIONS.RIGHT
  232. elif Input.is_action_just_pressed("ui_left"):
  233. requested_direction = DIRECTIONS.LEFT
  234. elif Input.is_action_just_pressed("ui_up"):
  235. requested_direction = DIRECTIONS.UP
  236. elif Input.is_action_just_pressed("ui_down"):
  237. requested_direction = DIRECTIONS.DOWN
  238. elif Input.is_action_pressed("ui_right") and requested_direction == DIRECTIONS.RIGHT:
  239. requested_direction = DIRECTIONS.RIGHT
  240. elif Input.is_action_pressed("ui_left") and requested_direction == DIRECTIONS.LEFT:
  241. requested_direction = DIRECTIONS.LEFT
  242. elif Input.is_action_pressed("ui_up") and requested_direction == DIRECTIONS.UP:
  243. requested_direction = DIRECTIONS.UP
  244. elif Input.is_action_pressed("ui_down") and requested_direction == DIRECTIONS.DOWN:
  245. requested_direction = DIRECTIONS.DOWN
  246. elif Input.is_action_pressed("ui_right"):
  247. requested_direction = DIRECTIONS.RIGHT
  248. elif Input.is_action_pressed("ui_left"):
  249. requested_direction = DIRECTIONS.LEFT
  250. elif Input.is_action_pressed("ui_up"):
  251. requested_direction = DIRECTIONS.UP
  252. elif Input.is_action_pressed("ui_down"):
  253. requested_direction = DIRECTIONS.DOWN
  254. else:
  255. requested_direction = DIRECTIONS.NONE
  256. func _handle_action_input():
  257. if !enable_movement:
  258. return
  259. if Input.is_action_just_pressed("change_tool"):
  260. update_tool()
  261. if Input.is_action_just_pressed("bomb"):
  262. put_bomb_down()
  263. if OS.is_debug_build():
  264. if Input.is_action_just_pressed("change_tool_debug"):
  265. if ptm.active_tool == PLAYERTOOLS.NONE:
  266. ptm.set_active_tool(PLAYERTOOLS.CLEANING_TOOL)
  267. elif ptm.active_tool == PLAYERTOOLS.CLEANING_TOOL:
  268. ptm.set_active_tool(PLAYERTOOLS.PUSH_TOOL)
  269. elif ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
  270. ptm.set_active_tool(PLAYERTOOLS.PULL_TOOL)
  271. elif ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
  272. ptm.set_active_tool(PLAYERTOOLS.GRINDING_TOOL)
  273. elif ptm.active_tool == PLAYERTOOLS.GRINDING_TOOL:
  274. ptm.set_active_tool(PLAYERTOOLS.NONE)
  275. else:
  276. ptm.set_active_tool(PLAYERTOOLS.CLEANING_TOOL)
  277. if Input.is_action_just_pressed("debug_add_bomb"):
  278. bomb_counter.add_bombs(5)
  279. #pushing and other front collision handling (like running into enemies)
  280. if requested_direction != DIRECTIONS.NONE and ray_front.is_colliding() and is_grid_aligned():
  281. var frontObject = ray_front.get_collider()
  282. var local_movedir = direction_to_vector(requested_direction)
  283. var next_target_pos = position + local_movedir * step_size
  284. if frontObject != null:
  285. var _distance = (next_target_pos - frontObject.position).length()
  286. #tool or regular + push tool
  287. if frontObject.is_in_group("tool") or ((frontObject.is_in_group("pushable") and !frontObject.is_in_group("heavy") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL)):
  288. if _distance < 1:
  289. modify_speed_timed(1.15)
  290. _try_push(frontObject, requested_direction)
  291. #regular
  292. elif frontObject.is_in_group("pushable") and !frontObject.is_in_group("heavy"):
  293. if _distance < 1:
  294. modify_speed_timed(1.45)
  295. _try_push(frontObject, requested_direction)
  296. #heavy and push tool
  297. elif frontObject.is_in_group("pushable") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
  298. if _distance < 1:
  299. modify_speed_timed(1.75)
  300. _try_push(frontObject, requested_direction)
  301. #tool or regular + push tool
  302. elif frontObject.get_parent().is_in_group("tool") or ((frontObject.get_parent().is_in_group("pushable") and !frontObject.get_parent().is_in_group("heavy") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL)):
  303. if _distance < 1:
  304. modify_speed_timed(1.15)
  305. _try_push(frontObject.get_parent(), requested_direction)
  306. #regular
  307. elif frontObject.get_parent().is_in_group("pushable") and !frontObject.is_in_group("heavy"):
  308. if _distance < 1:
  309. modify_speed_timed(1.45)
  310. _try_push(frontObject.get_parent(), requested_direction)
  311. #heavy and push tool
  312. elif frontObject.get_parent().is_in_group("pushable") and ptm.active_tool == PLAYERTOOLS.PUSH_TOOL:
  313. if _distance < 1:
  314. modify_speed_timed(1.75)
  315. _try_push(frontObject.get_parent(), requested_direction)
  316. elif frontObject.is_in_group("enemy") and _distance < 1:
  317. hit()
  318. elif frontObject.get_parent().is_in_group("grindable") and ptm.active_tool == PLAYERTOOLS.GRINDING_TOOL:
  319. ptm.play_animation()
  320. frontObject.get_parent().call_deferred("queue_free")
  321. else:
  322. print ("error: front raycast hit null object")
  323. elif requested_direction != DIRECTIONS.NONE and is_grid_aligned():
  324. var push_down = get_push_down_ramp_object()
  325. if push_down != null:
  326. var local_movedir = direction_to_vector(requested_direction)
  327. var next_target_pos = position + local_movedir * step_size
  328. var _distance = (next_target_pos - push_down.position).length()
  329. if _distance < 1:
  330. modify_speed_timed(1.15)
  331. _try_push(push_down, requested_direction)
  332. if requested_direction != DIRECTIONS.NONE and ray_back.is_colliding() and is_grid_aligned():
  333. if Input.is_action_pressed("action") and can_move_to(requested_direction) and can_pull:
  334. var backObject = ray_back.get_collider()
  335. if backObject != null:
  336. if backObject.is_in_group("pullable") and ptm.active_tool == PLAYERTOOLS.PULL_TOOL and backObject.can_be_pulled_to(requested_direction):
  337. #modify_speed_timed(2) #this is buggy right now, so I disabled it
  338. backObject.print_pull_collision()
  339. backObject.move_to(requested_direction, true)
  340. ptm.play_start_animation()
  341. #audio_player.stream = load("res://sfx/ncl_object_move.ogg")
  342. #audio_player.volume_db = -3
  343. #audio_player.pitch_scale = randf_range(0.86, 0.93)
  344. #audio_player.play()
  345. requested_mesh_direction = get_opposite_direction(requested_direction)
  346. else:
  347. print ("error: back raycast hit null object")
  348. if Input.is_action_just_released("action") and ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
  349. var backObject = ray_back.get_collider()
  350. if backObject != null and backObject.is_in_group("pullable"):
  351. ptm.play_end_animation()
  352. if Input.is_action_just_pressed("action") and ptm.active_tool == PLAYERTOOLS.PULL_TOOL:
  353. var backObject = ray_back.get_collider()
  354. if backObject != null and backObject.is_in_group("pullable"):
  355. ptm.play_start_animation()
  356. #audio_player.stream = load("res://sfx/ncl_object_move.ogg")
  357. #audio_player.volume_db = -3
  358. #audio_player.pitch_scale = randf_range(0.86, 0.93)
  359. #audio_player.play()
  360. if ray_front.is_colliding() and (Input.is_action_just_pressed("change_tool") or Input.is_action_just_pressed("action")):
  361. if is_grid_aligned():
  362. var frontObject = ray_front.get_collider()
  363. if frontObject != null and frontObject.is_in_group("bomb_item"):
  364. frontObject.take_bomb()
  365. bomb_counter.add_bomb()
  366. else:
  367. var distant_object = level.find_object_at_position(self, distant_target_position)
  368. if distant_object != null:
  369. if distant_object.is_in_group("bomb_item") and distant_object.has_method("take_bomb") and distant_object_to_take == null:
  370. distant_object_to_take = distant_object
  371. func handle_player_input():
  372. if !enable_input:
  373. return
  374. #when a key is only pressed for a short time, no movement is applied by blocking movement with enable_movement = false, only direction changes
  375. _handle_short_direction_change_impulse()
  376. _handle_movement_input()
  377. _handle_action_input()
  378. last_keys_pressed_amount = get_keys_pressed_amount()
  379. func _on_Health_no_health() -> void:
  380. if SceneManager.game_scene.is_finished:
  381. #no gameover when the level is already finished :-)
  382. return
  383. print ("you died")
  384. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  385. get_tree().change_scene_to_file("res://UI/GameOverScreen.tscn")
  386. func wait_after_puddle_cleaned():
  387. await get_tree().create_timer(0.0666666666667).timeout
  388. enable_movement = true