SpeedrunHandler.gd 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. extends Node
  2. var timer := 0.0
  3. var best_time := 0.0
  4. var marathon_best_any_time := 0.0
  5. var marathon_best_warpless_time := 0.0
  6. var timer_active := false
  7. var show_timer := false
  8. signal level_finished
  9. var start_time := 0.0
  10. const GHOST_RECORDING_TEMPLATE := {
  11. "position": Vector2.ZERO,
  12. "character": "Mario",
  13. "power_state": "Small",
  14. "animation": "Idle",
  15. "frame": 0,
  16. "direction": 1,
  17. "level": ""
  18. }
  19. var enable_recording := false
  20. var current_recording := ""
  21. var ghost_recording := ""
  22. var ghost_active := false
  23. var ghost_idx := -1
  24. var ghost_visible := false
  25. var ghost_enabled := false
  26. var levels := []
  27. var anim_list := []
  28. var show_pb_diff := true
  29. var is_warp_run := false
  30. var ghost_path := []
  31. var best_time_campaign := ""
  32. var best_level_any_times := {}
  33. var best_level_warpless_times := [
  34. [-1, -1, -1, -1],
  35. [-1, -1, -1, -1],
  36. [-1, -1, -1, -1],
  37. [-1, -1, -1, -1],
  38. [-1, -1, -1, -1],
  39. [-1, -1, -1, -1],
  40. [-1, -1, -1, -1],
  41. [-1, -1, -1, -1]
  42. ]
  43. const GOLD_ANY_TIMES := {
  44. "SMB1": 390,
  45. "SMBLL": 660,
  46. "SMBS": 1440
  47. }
  48. const GOLD_WARPLESS_TIMES := {
  49. "SMB1": 1320,
  50. "SMBLL": 1380,
  51. "SMBS": 1440
  52. }
  53. const WARP_LEVELS := {
  54. "SMB1": SMB1_WARP_LEVELS,
  55. "SMBLL": SMBLL_WARP_LEVELS,
  56. "SMBS": SMBS_WARP_LEVELS
  57. }
  58. const LEVEL_GOLD_WARPLESS_TIMES := {
  59. "SMB1": SMB1_LEVEL_GOLD_WARPLESS_TIMES,
  60. "SMBLL": SMBLL_LEVEL_GOLD_WARPLESS_TIMES,
  61. "SMBS": SMBS_LEVEL_GOLD_TIMES
  62. }
  63. const LEVEL_GOLD_ANY_TIMES := {
  64. "SMB1": SMB1_LEVEL_GOLD_ANY_TIMES,
  65. "SMBLL": SMBLL_LEVEL_GOLD_ANY_TIMES,
  66. "SMBS": SMBS_LEVEL_GOLD_ANY_TIMES
  67. }
  68. const SMB1_LEVEL_GOLD_WARPLESS_TIMES := [
  69. [17, 24, 17, 16], # World 1
  70. [23, 38, 25, 16], # World 2
  71. [23, 23, 17, 16], # World 3
  72. [24, 25, 16, 22], # World 4
  73. [22, 22, 17, 16], # World 5
  74. [21, 25, 18, 16], # World 6
  75. [20, 38, 25, 23], # World 7
  76. [40, 24, 24, 50] # World 8
  77. ]
  78. const SMBLL_LEVEL_GOLD_WARPLESS_TIMES := [
  79. [21, 25, 19, 17],
  80. [26, 34, 21, 18],
  81. [21, 39, 21, 20],
  82. [22, 23, 21, 25],
  83. [43, 28, 25, 24],
  84. [28, 39, 23, 29],
  85. [21, 26, 32, 36],
  86. [24, 27, 25, 60],
  87. ]
  88. const SMB1_LEVEL_GOLD_ANY_TIMES := {
  89. "1-2": 25,
  90. "4-2": 26
  91. }
  92. const SMBLL_LEVEL_GOLD_ANY_TIMES := {
  93. "1-2": 40,
  94. "3-1": 22,
  95. "5-1": 52,
  96. "5-2": 35,
  97. "8-1": 44
  98. }
  99. const SMBS_LEVEL_GOLD_ANY_TIMES := {
  100. "1-2": 25,
  101. "4-2": 30
  102. }
  103. const SMBS_LEVEL_GOLD_TIMES := [
  104. [28, 21, 32, 19],
  105. [27, 40, 31, 19],
  106. [31, 11, 16, 20],
  107. [26, 30, 25, 32],
  108. [28, 26, 19, 19],
  109. [24, 21, 23, 20],
  110. [24, 40, 30, 27],
  111. [30, 35, 30, 43],
  112. ]
  113. const SMB1_WARP_LEVELS := ["1-2", "4-2"]
  114. const SMBLL_WARP_LEVELS := ["1-2", "3-1", "5-1", "5-2", "8-1"]
  115. const SMBS_WARP_LEVELS := ["4-2"]
  116. const MEDAL_CONVERSIONS := [2, 1.5, 1]
  117. func _ready() -> void:
  118. process_mode = Node.PROCESS_MODE_ALWAYS
  119. func _physics_process(_delta: float) -> void:
  120. if timer_active:
  121. timer = abs(start_time - Time.get_ticks_msec()) / 1000
  122. if enable_recording:
  123. if get_tree().get_first_node_in_group("Players") != null:
  124. record_frame(get_tree().get_first_node_in_group("Players"))
  125. Global.player_ghost.visible = ghost_visible
  126. if ghost_active and ghost_enabled:
  127. ghost_idx += 1
  128. if ghost_idx >= ghost_path.size():
  129. ghost_active = false
  130. return
  131. Global.player_ghost.apply_data(ghost_path[ghost_idx])
  132. func start_timer() -> void:
  133. timer = 0
  134. timer_active = true
  135. show_timer = true
  136. start_time = Time.get_ticks_msec()
  137. func record_frame(player: Player) -> void:
  138. var data := ""
  139. if levels.has(Global.current_level.scene_file_path) == false:
  140. levels.append(Global.current_level.scene_file_path)
  141. data += str(int(player.global_position.x)) + "="
  142. data += str(int(player.global_position.y)) + "="
  143. data += str(["Small", "Big", "Fire"].find(player.power_state.state_name)) + "="
  144. if anim_list.has(player.sprite.animation) == false:
  145. anim_list.append(player.sprite.animation)
  146. data += str(anim_list.find(player.sprite.animation)) + "="
  147. data += str(player.sprite.frame) + "="
  148. data += str(player.sprite.scale.x) + "="
  149. data += str(levels.find(Global.current_level.scene_file_path))
  150. current_recording += data + ","
  151. func format_time(time_time := 0.0) -> Dictionary:
  152. var mils = abs(fmod(time_time, 1) * 100)
  153. var secs = abs(fmod(time_time, 60))
  154. var mins = abs(time_time / 60)
  155. return {"mils": int(mils), "secs": int(secs), "mins": int(mins)}
  156. func gen_time_string(timer_dict := {}) -> String:
  157. return str(int(timer_dict["mins"])).pad_zeros(2) + ":" + str(int(timer_dict["secs"])).pad_zeros(2) + ":" + str(int(timer_dict["mils"])).pad_zeros(2)
  158. func save_recording() -> void:
  159. var recording := [timer, current_recording, levels, str(["Mario", "Luigi", "Toad", "Toadette"].find(get_tree().get_first_node_in_group("Players").character)), anim_list]
  160. var recording_dir = "user://marathon_recordings/" + Global.current_campaign
  161. DirAccess.make_dir_recursive_absolute(recording_dir)
  162. var file = FileAccess.open(recording_dir + "/" + str(Global.world_num) + "-" + str(Global.level_num) + ("warp" if is_warp_run else "") + ".json", FileAccess.WRITE)
  163. file.store_string(compress_recording(JSON.stringify(recording, "", false, true)))
  164. current_recording = ""
  165. file.close()
  166. levels.clear()
  167. func compress_recording(recording := "") -> String:
  168. print(recording)
  169. var bytes = recording.to_ascii_buffer()
  170. var compressed_bytes = bytes.compress(FileAccess.CompressionMode.COMPRESSION_DEFLATE)
  171. var b64 = Marshalls.raw_to_base64(compressed_bytes)
  172. return b64
  173. func decompress_recording(recording := "") -> Array:
  174. var compressed_bytes = Marshalls.base64_to_raw(recording)
  175. var bytes = compressed_bytes.decompress_dynamic(-1, FileAccess.COMPRESSION_DEFLATE)
  176. var string = bytes.get_string_from_ascii()
  177. var json = JSON.parse_string(string)
  178. return json
  179. func load_best_marathon() -> void:
  180. var recording = load_recording(Global.world_num, Global.level_num, not is_warp_run, Global.current_campaign)
  181. if recording == []:
  182. best_time = -1
  183. ghost_active = false
  184. ghost_recording = ""
  185. ghost_path = []
  186. levels = []
  187. anim_list = []
  188. else:
  189. ghost_active = true
  190. ghost_recording = recording[1]
  191. ghost_path = ghost_recording.split(",", false)
  192. levels = recording[2].duplicate()
  193. anim_list = recording[4].duplicate()
  194. func load_recording(world_num := 0, level_num := 0, is_warpless := true, campaign := "SMB1") -> Array:
  195. var recording_dir = "user://marathon_recordings/" + campaign
  196. var path = recording_dir + "/" + str(world_num) + "-" + str(level_num) + ("" if is_warpless else "warp") + ".json"
  197. print(path)
  198. if FileAccess.file_exists(path) == false:
  199. return []
  200. var file = FileAccess.open(path, FileAccess.READ)
  201. var text = decompress_recording(file.get_as_text())
  202. file.close()
  203. return text
  204. func load_best_times(campaign = Global.current_campaign) -> void:
  205. if best_time_campaign == campaign:
  206. return
  207. best_time_campaign = campaign
  208. best_level_any_times.clear()
  209. for world_num in 8:
  210. for level_num in 4:
  211. var path = "user://marathon_recordings/" + campaign + "/" + str(world_num + 1) + "-" + str(level_num + 1) + ".json"
  212. if FileAccess.file_exists(path):
  213. best_level_warpless_times[world_num][level_num] = load_recording(world_num + 1, level_num + 1, true, campaign)[0]
  214. else:
  215. best_level_warpless_times[world_num][level_num] = -1
  216. path = "user://marathon_recordings/" + campaign + "/" + str(world_num + 1) + "-" + str(level_num + 1) +"warp" + ".json"
  217. if FileAccess.file_exists(path):
  218. best_level_any_times[str(world_num + 1) + "-" + str(level_num + 1)] = load_recording(world_num + 1, level_num + 1, false, campaign)[0]
  219. check_for_medal_achievement()
  220. func run_finished() -> void:
  221. if timer_active == false:
  222. return
  223. SpeedrunHandler.ghost_active = false
  224. SpeedrunHandler.ghost_idx = -1
  225. SpeedrunHandler.timer_active = false
  226. if Global.current_game_mode == Global.GameMode.BOO_RACE:
  227. pass
  228. else:
  229. var best = best_level_warpless_times[Global.world_num - 1][Global.level_num - 1]
  230. if is_warp_run:
  231. best = best_level_any_times.get(str(Global.world_num) + "-" + str(Global.level_num), -1)
  232. if best <= 0 or best > timer:
  233. if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
  234. save_recording()
  235. if is_warp_run:
  236. best_level_any_times[str(Global.world_num) + "-" + str(Global.level_num)] = timer
  237. else:
  238. best_level_warpless_times[Global.world_num - 1][Global.level_num - 1] = timer
  239. else:
  240. if is_warp_run:
  241. marathon_best_any_time = timer
  242. else:
  243. marathon_best_warpless_time = timer
  244. if Global.current_game_mode == Global.GameMode.MARATHON:
  245. match Global.current_campaign:
  246. "SMB1": Global.unlock_achievement(Global.AchievementID.SMB1_RUN)
  247. "SMBLL": Global.unlock_achievement(Global.AchievementID.SMBLL_RUN)
  248. "SMBS": Global.unlock_achievement(Global.AchievementID.SMBS_RUN)
  249. check_for_medal_achievement()
  250. SaveManager.write_save(Global.current_campaign)
  251. func get_best_time() -> float:
  252. if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
  253. if is_warp_run:
  254. return best_level_any_times[str(Global.world_num) + "-" + str(Global.level_num)]
  255. else:
  256. return best_level_warpless_times[Global.world_num - 1][Global.level_num - 1]
  257. else:
  258. if is_warp_run:
  259. return marathon_best_any_time
  260. else:
  261. return marathon_best_warpless_time
  262. func check_for_medal_achievement() -> void:
  263. var has_gold_warp := true
  264. var has_gold_warpless := true
  265. var has_silver_warpless := true
  266. var has_silver_warp := true
  267. var has_bronze_warpless := true
  268. var has_bronze_warp := true
  269. var has_gold_full := false
  270. var has_silver_full := false
  271. var has_bronze_full := false
  272. if Global.current_campaign == "SMBANN":
  273. return
  274. for i in LEVEL_GOLD_ANY_TIMES[Global.current_campaign]:
  275. if best_level_any_times.has(i):
  276. if best_level_any_times[i] > LEVEL_GOLD_ANY_TIMES[Global.current_campaign][i]:
  277. has_gold_warp = false
  278. if best_level_any_times[i] > LEVEL_GOLD_ANY_TIMES[Global.current_campaign][i] * MEDAL_CONVERSIONS[1]:
  279. has_silver_warp = false
  280. if best_level_any_times[i] > LEVEL_GOLD_ANY_TIMES[Global.current_campaign][i] * MEDAL_CONVERSIONS[0]:
  281. has_bronze_warp = false
  282. var world := 0
  283. for i in best_level_warpless_times:
  284. var level := 0
  285. for x in i:
  286. if x < 0:
  287. has_gold_warpless = false
  288. has_silver_warpless = false
  289. has_bronze_warpless = false
  290. if x > LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][world][level]:
  291. has_gold_warpless = false
  292. if x > LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][world][level] * MEDAL_CONVERSIONS[1]:
  293. has_silver_warpless = false
  294. if x > LEVEL_GOLD_WARPLESS_TIMES[Global.current_campaign][world][level] * MEDAL_CONVERSIONS[0]:
  295. has_bronze_warpless = false
  296. level += 1
  297. world += 1
  298. if marathon_best_any_time <= GOLD_ANY_TIMES[Global.current_campaign] and marathon_best_warpless_time <= GOLD_WARPLESS_TIMES[Global.current_campaign]:
  299. has_gold_full = true
  300. if marathon_best_any_time <= GOLD_ANY_TIMES[Global.current_campaign] * MEDAL_CONVERSIONS[1] and marathon_best_warpless_time <= GOLD_WARPLESS_TIMES[Global.current_campaign] * MEDAL_CONVERSIONS[1]:
  301. has_silver_full = true
  302. if marathon_best_any_time <= GOLD_ANY_TIMES[Global.current_campaign] * MEDAL_CONVERSIONS[0] and marathon_best_warpless_time <= GOLD_WARPLESS_TIMES[Global.current_campaign] * MEDAL_CONVERSIONS[0]:
  303. has_bronze_full = true
  304. if has_gold_warp and has_gold_warpless and has_gold_full:
  305. match Global.current_campaign:
  306. "SMB1": Global.unlock_achievement(Global.AchievementID.SMB1_GOLD)
  307. "SMBLL": Global.unlock_achievement(Global.AchievementID.SMBLL_GOLD)
  308. "SMBS": Global.unlock_achievement(Global.AchievementID.SMBS_GOLD)
  309. if has_silver_warp and has_silver_warpless and has_silver_full:
  310. match Global.current_campaign:
  311. "SMB1": Global.unlock_achievement(Global.AchievementID.SMB1_SILVER)
  312. "SMBLL": Global.unlock_achievement(Global.AchievementID.SMBLL_SILVER)
  313. "SMBS": Global.unlock_achievement(Global.AchievementID.SMBS_SILVER)
  314. if has_bronze_warp and has_bronze_warpless and has_bronze_full:
  315. match Global.current_campaign:
  316. "SMB1": Global.unlock_achievement(Global.AchievementID.SMB1_BRONZE)
  317. "SMBLL": Global.unlock_achievement(Global.AchievementID.SMBLL_BRONZE)
  318. "SMBS": Global.unlock_achievement(Global.AchievementID.SMBS_BRONZE)