extends Node3D class_name Race # in order of initialization var car_scene: PackedScene var track_loader_scene: PackedScene var ghost_scene: PackedScene var track_res: TrackResource var track: TrackLoader var data: Array[GhostData] var best_time_data: GhostData var cars: Array[Car] var cars_needed: PackedInt32Array = [] var finished_cars := 0 @onready var mp := cars_needed.size() > 1 var ghost: GhostCar var start_frame: int var current_lap: PackedInt32Array = [] var playing := false var timers: Array[GameTimer] var distances: PackedFloat32Array = [] signal next_lap signal created_car(car: Car, n: int) signal created_ghost(ghost: GhostCar) signal finished(time: float, prev_time: float, n: int) signal all_finished signal split(time: float, prev_time: float, n: int) signal did_reset func _init(t: TrackResource, ghost_data: GhostData, _car_scene, _ghost_scene, _track_loader_scene) -> void: car_scene = _car_scene ghost_scene = _ghost_scene track_loader_scene = _track_loader_scene track_res = t best_time_data = ghost_data func mkghost() -> void: ghost = ghost_scene.instantiate() add_child(ghost) reset_ghost() created_ghost.emit(ghost) func reset_ghost() -> void: if best_time_data: ghost.update(best_time_data.load_snap(0), -1) ghost.engine.volume = .2 else: ghost.engine.volume = 0 ghost.global_position = track.start_transf.origin + Vector3(0, 1, -2) ghost.rotation = track.start_transf.basis.get_euler()# + Vector3(0, PI, -PI/2) ghost.reset() ghost.hide() func reset_car(i: int) -> void: await get_tree().physics_frame cars[i].rotation = track.start_transf.basis.get_euler()# + Vector3(0, PI, -PI/2) cars[i].global_position = track.start_transf.origin + Vector3(0, 1, -2) cars[i].linear_velocity = Vector3.ZERO cars[i].angular_velocity = Vector3.ZERO cars[i].current_gear = 0 cars[i].reset() func mkcar(i: int, dev: int) -> void: cars[i] = HumanCar.attach(car_scene, dev) add_child(cars[i]) reset_car(i) created_car.emit(cars[i], i) if !mp: cars[i].collision_mask = 2 ^ 1 func _ready() -> void: set_physics_process(false) track = track_loader_scene.instantiate() track.track = track_res add_child(track) data.resize(cars_needed.size()) cars.resize(cars_needed.size()) current_lap.resize(cars_needed.size()) current_lap.fill(0) timers.resize(cars_needed.size()) if mp: distances.resize(cars_needed.size()) distances.fill(0) for i in cars_needed.size(): data[i] = GhostData.new(track.checkpoints.size(), track_res.laps) timers[i] = GameTimer.new() add_child(timers[i]) mkcar(i, cars_needed[i]) mkghost() connect_checkpoints() func _input(event: InputEvent) -> void: if event.is_action("reset") and playing: reset() func reset() -> void: playing = false finished_cars = 0 if best_time_data: if ghost: reset_ghost() distances.fill(0) for car in cars.size(): await reset_car(car) current_lap[car] = 0 set_physics_process(false) for d in data: d.clear() for timer in timers: timer.reset() did_reset.emit() func fcar(c: Car) -> int: return cars.find(c) func connect_checkpoints() -> void: for i in len(track.checkpoints): track.checkpoints[i].collected.connect(func(c: Car): passed_cp(i, fcar(c))) track.finish.collected.connect(func(c: Car): passed_finish(fcar(c))) func passed_cp(cp: int, car: int) -> void: if playing and !data[car].has_collected(current_lap[car], cp): collect(cp, car) func passed_finish(car: int) -> void: if !playing: return for i in len(data[car].checkpoints[current_lap[car]]) - 1: if data[car].checkpoints[current_lap[car]][i] < 0: return collect(-1, car) if track_res.laps - 1 == current_lap[car]: timers[car].stop() cars[car].reset() finished_cars += 1 finished.emit(data[car].time, best_time_data.time if best_time_data else -1.0, car) if finished_cars > cars.size() - 1: # all finished var best = {time = INF} for datapoint in data: if best.time > datapoint.time: best = datapoint if not best_time_data or best.time < best_time_data.time: best.save(Globals.SAVES % track_res.name) best_time_data = best all_finished.emit() playing = false else: current_lap[car] += 1 next_lap.emit() func _physics_process(delta: float) -> void: if mp: for i in cars_needed.size(): if distances[i] < 0: continue var flag := true for j in cars_needed.size(): if j == i: continue var dist := cars[i].global_position.distance_squared_to(cars[j].global_position) if dist > 50: continue flag = false break # gdscript cant label loops if flag: distances[i] = distances[i] + delta if distances[i] > 1.5: # when apart for longer than 1.5 seconds, enable collisions distances[i] = -1 cars[i].collision_mask = 1 ^ 2 # world & players for i in data.size(): data[i].snapshot(cars[i]) if best_time_data: if best_time_data.snap_count - 1 < Engine.get_physics_frames() - start_frame: if ghost.visible: ghost.hide() ghost.engine.volume = 0 return ghost.update(best_time_data.load_snap(Engine.get_physics_frames() - start_frame), delta) ghost.visible = (ghost.global_position.distance_squared_to(cars[0].global_position) > 10) ghost.engine.volume = lerpf(ghost.engine.volume, .7, delta * 3) if (ghost.global_position.distance_squared_to(cars[0].global_position) > 20) else lerpf(ghost.engine.volume, .2, delta * 3) func collect(cp: int, car: int) -> void: if cp != -1: cars[car].checkpoint_sound.play() var time := best_time_data.get_time(current_lap[car], cp) if best_time_data else -1.0 time = best_time_data.time if (not track_res.laps or track_res.laps == current_lap[car] + 1) and cp == -1 and time != -1.0 else time split.emit(snappedf(timers[car].now(), .001), time, car) data[car].collect(current_lap[car], cp, snappedf(timers[car].now(), .001)) func start() -> void: for timer in timers: timer.start() start_frame = Engine.get_physics_frames() playing = true set_physics_process(true) for car in cars: car.start()