class_name Bugbot extends CharacterBody3D ## A controllable bot that marks the location of a bug, and allows writing a bug ## report for it. ## Scene to use as the bug marker. @export_file("*.scn","*.tscn") var bug_marker : String #region InputEvent Exports @export_group("Inputs") @export_subgroup("Keyboard", "keyboard_") @export var keyboard_move_forward : InputEventKey @export var keyboard_move_backward : InputEventKey @export var keyboard_move_left : InputEventKey @export var keyboard_move_right : InputEventKey @export var keyboard_move_up : InputEventKey @export var keyboard_move_down : InputEventKey @export var keyboard_tilt_up : InputEventKey @export var keyboard_tilt_down : InputEventKey @export var keyboard_pan_left : InputEventKey @export var keyboard_pan_right : InputEventKey @export var keyboard_movement_speed_up : InputEventKey @export var keyboard_movement_speed_down : InputEventKey @export var keyboard_place_marker : InputEventKey @export var keyboard_exit_placement : InputEventKey @export_subgroup("Mouse", "mouse_") @export var mouse_movement_speed_up : InputEventMouseButton @export var mouse_movement_speed_down : InputEventMouseButton @export var mouse_place_marker : InputEventMouseButton @export var mouse_exit_placement : InputEventMouseButton @export_subgroup("Joypad", "joypad_") @export var joypad_move_forward : InputEventJoypadMotion @export var joypad_move_backward : InputEventJoypadMotion @export var joypad_move_left : InputEventJoypadMotion @export var joypad_move_right : InputEventJoypadMotion @export var joypad_move_up : InputEventJoypadMotion @export var joypad_move_down : InputEventJoypadMotion @export var joypad_tilt_up : InputEventJoypadMotion @export var joypad_tilt_down : InputEventJoypadMotion @export var joypad_pan_left : InputEventJoypadMotion @export var joypad_pan_right : InputEventJoypadMotion @export var joypad_movement_speed_up : InputEventJoypadButton @export var joypad_movement_speed_down : InputEventJoypadButton @export var joypad_place_marker : InputEventJoypadButton @export var joypad_exit_placement : InputEventJoypadButton #endregion #region Onready @onready var __laser_beam : Node3D = $LaserBeamRoot as Node3D @onready var __bug_marker_dummy : BugMarker = $LaserBeamRoot/LaserGlare/BugMarkerDummy as BugMarker @onready var __movement_speed_change_timer : Timer = $MovementSpeedChangeTimer as Timer @onready var __exit_placement_timer : Timer = $ExitPlacementTimer as Timer @onready var __camera : Camera3D = $CollisionShape3D/Camera3D #endregion #region Constants const DEFAULT_PRECISE_PLACEMENT_DISTANCE : float = 10.0 const DEFAULT_ARBITRARY_PLACEMENT_DISTANCE : float = 2.5 #endregion #region Private Properties static var __bugbot_server : BugbotServerAPI static var __bug_marker_root : Node var __bug_report_form : BugReportForm var __movement_speed : float = 5.0 var __stored_camera : Camera3D var __stored_mouse_mode : int = Input.MOUSE_MODE_CAPTURED var __stored_pause_status : bool = false var __raycast_collision : Dictionary #endregion #region Initialisation func _enter_tree() -> void: __stored_camera = get_viewport().get_camera_3d() __stored_pause_status = get_tree().paused get_tree().paused = true __stored_mouse_mode = Input.mouse_mode Input.mouse_mode = Input.MOUSE_MODE_CAPTURED __bugbot_server = BugbotServerAPI._create_new_server_api() #region Movement Configuration InputMap.add_action(&"bugbot_move_forward", 0.1) InputMap.action_add_event(&"bugbot_move_forward", keyboard_move_forward) InputMap.action_add_event(&"bugbot_move_forward", joypad_move_forward) InputMap.add_action(&"bugbot_move_backward", 0.1) InputMap.action_add_event(&"bugbot_move_backward", keyboard_move_backward) InputMap.action_add_event(&"bugbot_move_backward", joypad_move_backward) InputMap.add_action(&"bugbot_move_left", 0.1) InputMap.action_add_event(&"bugbot_move_left", keyboard_move_left) InputMap.action_add_event(&"bugbot_move_left", joypad_move_left) InputMap.add_action(&"bugbot_move_right", 0.1) InputMap.action_add_event(&"bugbot_move_right", keyboard_move_right) InputMap.action_add_event(&"bugbot_move_right", joypad_move_right) InputMap.add_action(&"bugbot_move_down", 0.1) InputMap.action_add_event(&"bugbot_move_down", keyboard_move_down) InputMap.action_add_event(&"bugbot_move_down", joypad_move_down) InputMap.add_action(&"bugbot_move_up", 0.1) InputMap.action_add_event(&"bugbot_move_up", keyboard_move_up) InputMap.action_add_event(&"bugbot_move_up", joypad_move_up) #endregion #region Camera Configuration InputMap.add_action(&"bugbot_tilt_up", 0.1) InputMap.action_add_event(&"bugbot_tilt_up", keyboard_tilt_up) InputMap.action_add_event(&"bugbot_tilt_up", joypad_tilt_up) InputMap.add_action(&"bugbot_tilt_down", 0.1) InputMap.action_add_event(&"bugbot_tilt_down", keyboard_tilt_down) InputMap.action_add_event(&"bugbot_tilt_down", joypad_tilt_down) InputMap.add_action(&"bugbot_pan_left", 0.1) InputMap.action_add_event(&"bugbot_pan_left", keyboard_pan_left) InputMap.action_add_event(&"bugbot_pan_left", joypad_pan_left) InputMap.add_action(&"bugbot_pan_right", 0.1) InputMap.action_add_event(&"bugbot_pan_right", keyboard_pan_right) InputMap.action_add_event(&"bugbot_pan_right", joypad_pan_right) #endregion #region Speed Configuration InputMap.add_action(&"bugbot_movement_speed_up") InputMap.action_add_event(&"bugbot_movement_speed_up", keyboard_movement_speed_up) InputMap.action_add_event(&"bugbot_movement_speed_up", mouse_movement_speed_up) InputMap.action_add_event(&"bugbot_movement_speed_up", joypad_movement_speed_up) InputMap.add_action(&"bugbot_movement_speed_down") InputMap.action_add_event(&"bugbot_movement_speed_down", keyboard_movement_speed_down) InputMap.action_add_event(&"bugbot_movement_speed_down", mouse_movement_speed_down) InputMap.action_add_event(&"bugbot_movement_speed_down", joypad_movement_speed_down) #endregion #region Marker Placement Configuration InputMap.add_action(&"bugbot_place_marker") InputMap.action_add_event(&"bugbot_place_marker", keyboard_place_marker) InputMap.action_add_event(&"bugbot_place_marker", mouse_place_marker) InputMap.action_add_event(&"bugbot_place_marker", joypad_place_marker) InputMap.add_action(&"bugbot_exit_placement") InputMap.action_add_event(&"bugbot_exit_placement", keyboard_exit_placement) InputMap.action_add_event(&"bugbot_exit_placement", mouse_exit_placement) InputMap.action_add_event(&"bugbot_exit_placement", joypad_exit_placement) #endregion func _exit_tree() -> void: #region Movement Deconfiguration InputMap.action_erase_event(&"bugbot_move_forward", keyboard_move_forward) InputMap.action_erase_event(&"bugbot_move_forward", joypad_move_forward) InputMap.action_erase_event(&"bugbot_move_backward", keyboard_move_backward) InputMap.action_erase_event(&"bugbot_move_backward", joypad_move_backward) InputMap.action_erase_event(&"bugbot_move_left", keyboard_move_left) InputMap.action_erase_event(&"bugbot_move_left", joypad_move_left) InputMap.action_erase_event(&"bugbot_move_right", keyboard_move_right) InputMap.action_erase_event(&"bugbot_move_right", joypad_move_right) InputMap.action_erase_event(&"bugbot_move_down", keyboard_move_down) InputMap.action_erase_event(&"bugbot_move_down", joypad_move_down) InputMap.action_erase_event(&"bugbot_move_up", keyboard_move_up) InputMap.action_erase_event(&"bugbot_move_up", joypad_move_up) #endregion #region Camera Deconfiguration InputMap.action_erase_event(&"bugbot_tilt_up", keyboard_tilt_up) InputMap.action_erase_event(&"bugbot_tilt_up", joypad_tilt_up) InputMap.action_erase_event(&"bugbot_tilt_down", keyboard_tilt_down) InputMap.action_erase_event(&"bugbot_tilt_down", joypad_tilt_down) InputMap.action_erase_event(&"bugbot_pan_left", keyboard_pan_left) InputMap.action_erase_event(&"bugbot_pan_left", joypad_pan_left) InputMap.action_erase_event(&"bugbot_pan_right", keyboard_pan_right) InputMap.action_erase_event(&"bugbot_pan_right", joypad_pan_right) #endregion #region Speed Deconfiguration InputMap.action_erase_event(&"bugbot_movement_speed_up", keyboard_movement_speed_up) InputMap.action_erase_event(&"bugbot_movement_speed_up", mouse_movement_speed_up) InputMap.action_erase_event(&"bugbot_movement_speed_up", joypad_movement_speed_up) InputMap.action_erase_event(&"bugbot_movement_speed_down", keyboard_movement_speed_down) InputMap.action_erase_event(&"bugbot_movement_speed_down", mouse_movement_speed_down) InputMap.action_erase_event(&"bugbot_movement_speed_down", joypad_movement_speed_down) #endregion #region Marker Placement Deconfiguration InputMap.action_erase_event(&"bugbot_place_marker", keyboard_place_marker) InputMap.action_erase_event(&"bugbot_place_marker", mouse_place_marker) InputMap.action_erase_event(&"bugbot_place_marker", joypad_place_marker) InputMap.action_erase_event(&"bugbot_exit_placement", keyboard_exit_placement) InputMap.action_erase_event(&"bugbot_exit_placement", mouse_exit_placement) InputMap.action_erase_event(&"bugbot_exit_placement", joypad_exit_placement) #endregion #region Action Removal InputMap.erase_action(&"bugbot_move_forward") InputMap.erase_action(&"bugbot_move_backward") InputMap.erase_action(&"bugbot_move_left") InputMap.erase_action(&"bugbot_move_right") InputMap.erase_action(&"bugbot_move_down") InputMap.erase_action(&"bugbot_move_up") InputMap.erase_action(&"bugbot_tilt_up") InputMap.erase_action(&"bugbot_tilt_down") InputMap.erase_action(&"bugbot_pan_left") InputMap.erase_action(&"bugbot_pan_right") InputMap.erase_action(&"bugbot_movement_speed_up") InputMap.erase_action(&"bugbot_movement_speed_down") InputMap.erase_action(&"bugbot_place_marker") InputMap.erase_action(&"bugbot_exit_placement") #endregion Input.mouse_mode = __stored_mouse_mode get_tree().paused = __stored_pause_status func _ready() -> void: __camera.current = true if __stored_camera: global_transform = __stored_camera.global_transform __camera.fov = __stored_camera.fov __camera.near = __stored_camera.near __camera.far = __stored_camera.far __bug_marker_dummy.enable_info = false #endregion func _process(_delta:float) -> void: __calculate_movement_speed() __calculate_rotation(_delta) __calculate_movement() __place_dummy_marker() func _physics_process(_delta:float) -> void: __raycast_to_world() __calculate_button_presses() move_and_slide() static func instantiate(tree:SceneTree) -> void: tree.root.add_child(load("res://addons/Bugbot/Scenes/bugbot_player.tscn").instantiate() as CharacterBody3D) static func load_bug_markers(marker_root:Node, bug_list:Array) -> void: var bug_marker_preload : PackedScene = preload("res://addons/Bugbot/Scenes/bug_marker.tscn") Bugbot.adjust_bug_marker_colours() for bug_info:BugbotBugData in bug_list: if not bug_info: continue var bug_status : BugMarker.BugStatus = BugMarker.BugStatus.NEUTRAL if bug_info.resolution == BugbotServerAPI.RESOLVED_TAG: bug_status = BugMarker.BugStatus.RESOLVED elif bug_info.resolution == BugbotServerAPI.IN_PROGRESS_TAG: bug_status = BugMarker.BugStatus.IN_PROGRESS elif bug_info.resolution == BugbotServerAPI.UNRESOLVED_TAG: bug_status = BugMarker.BugStatus.UNRESOLVED var bug_marker : BugMarker = bug_marker_preload.instantiate() as BugMarker marker_root.add_child(bug_marker) bug_marker.marker_status = bug_status bug_marker.bug_info = bug_info bug_marker.global_position = bug_info.marker_position bug_marker.set_rotation_to_normal(bug_info.marker_normal) static func adjust_bug_marker_colours() -> void: var unresolved_colour : Color = ProjectSettings.get_setting("bugbot/markers/unresolved/tint", BugMarker.UNRESOLVED_TINT) var in_progress_colour : Color = ProjectSettings.get_setting("bugbot/markers/in_progress/tint", BugMarker.IN_PROGRESS_TINT) var resolved_colour : Color = ProjectSettings.get_setting("bugbot/markers/resolved/tint", BugMarker.RESOLVED_TINT) var unresolved_material : BugMarkerMaterialPack = load("res://addons/Bugbot/Materials/BugMarker/bug_marker_unresolved.res") (unresolved_material.arrow as ShaderMaterial).set_shader_parameter("colour", unresolved_colour) (unresolved_material.icon as ShaderMaterial).set_shader_parameter("colour", unresolved_colour) var in_progress_material : BugMarkerMaterialPack = load("res://addons/Bugbot/Materials/BugMarker/bug_marker_in_progress.res") (in_progress_material.arrow as ShaderMaterial).set_shader_parameter("colour", in_progress_colour) (in_progress_material.icon as ShaderMaterial).set_shader_parameter("colour", in_progress_colour) var resolved_material : BugMarkerMaterialPack = load("res://addons/Bugbot/Materials/BugMarker/bug_marker_resolved.res") (resolved_material.arrow as ShaderMaterial).set_shader_parameter("colour", resolved_colour) (resolved_material.icon as ShaderMaterial).set_shader_parameter("colour", resolved_colour) static func __get_bug_marker_root(current_scene:Node) -> void: var current_scene_hash : String = current_scene.scene_file_path.sha256_text() var current_scene_children : Array = current_scene.get_children() var found_root : bool = false for child:Node in current_scene_children: if child.name == current_scene_hash: __bug_marker_root = child return var new_root : Node = Node.new() current_scene.add_child(new_root) new_root.name = current_scene_hash __bug_marker_root = new_root static func show_bug_markers(current_scene:Node) -> void: Bugbot.__get_bug_marker_root(current_scene) __bugbot_server._return_list_of_bugs(current_scene.scene_file_path, __show_bug_markers_response) static func __show_bug_markers_response(bugs:Array) -> void: Bugbot.load_bug_markers(__bug_marker_root, bugs) static func hide_bug_markers(current_scene:Node) -> void: Bugbot.__get_bug_marker_root(current_scene) __bug_marker_root.queue_free() func __calculate_movement_speed() -> void: if Input.is_action_pressed(&"bugbot_movement_speed_down") or Input.is_action_just_pressed(&"bugbot_movement_speed_down"): if __movement_speed_change_timer.is_stopped(): __movement_speed /= 1.25 __movement_speed_change_timer.start() elif Input.is_action_pressed(&"bugbot_movement_speed_up") or Input.is_action_just_pressed(&"bugbot_movement_speed_up"): if __movement_speed_change_timer.is_stopped(): __movement_speed *= 1.25 __movement_speed_change_timer.start() else: __movement_speed_change_timer.stop() __movement_speed = clampf(__movement_speed, 0.25, 100.0) func __calculate_rotation(_delta:float) -> void: var rotation_velocity : Vector2 = Vector2( Input.get_axis(&"bugbot_tilt_down", &"bugbot_tilt_up"), Input.get_axis(&"bugbot_pan_right", &"bugbot_pan_left")) * 2.5 var mouse_velocity : Vector2 = Input.get_last_mouse_velocity() rotation_velocity.x += mouse_velocity.y * -0.0025 rotation_velocity.y += mouse_velocity.x * -0.0025 rotation.x = clampf(rotation.x + (rotation_velocity.x * _delta), -PI/2.0 + 0.0001, PI/2.0 - 0.0001) rotation.y += rotation_velocity.y * _delta func __calculate_movement() -> void: var movement_vector_lateral : Vector2 = Input.get_vector(&"bugbot_move_left", &"bugbot_move_right", &"bugbot_move_forward", &"bugbot_move_backward") var movement_azimuth : float = Input.get_axis(&"bugbot_move_down", &"bugbot_move_up") velocity = transform.basis * Vector3(movement_vector_lateral.x, movement_azimuth, movement_vector_lateral.y) * __movement_speed func __calculate_button_presses() -> void: if Input.is_action_just_pressed(&"bugbot_place_marker") and __raycast_collision: __select_at_location(__raycast_collision["position"], __raycast_collision["normal"]) if Input.is_action_just_released(&"bugbot_exit_placement"): __exit_placement_timer.stop() elif Input.is_action_just_pressed(&"bugbot_exit_placement"): __exit_placement_timer.start() func __on_exit_placement_timer_timeout() -> void: queue_free() func __raycast_to_world() -> void: var ray_length : float = ProjectSettings.get_setting("bugbot/bug_placement/precise_placement_distance", DEFAULT_PRECISE_PLACEMENT_DISTANCE) var space : PhysicsDirectSpaceState3D = get_world_3d().direct_space_state var query : PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(global_position, global_position - global_transform.basis.z * ray_length) query.collide_with_areas = true query.collide_with_bodies = true query.exclude = [get_rid()] __raycast_collision = space.intersect_ray(query) if __raycast_collision: var collision_point : Vector3 = __raycast_collision["position"] var distance_to_collision : float = __laser_beam.global_position.distance_to(collision_point) __laser_beam.scale.z = distance_to_collision __laser_beam.look_at(collision_point) __laser_beam.visible = true else: __laser_beam.visible = false func __select_at_location(_position:Vector3, _normal:Vector3) -> void: var collider : BugInfoCollider = __raycast_collision["collider"] as BugInfoCollider if collider: __pop_up_marker_info(collider, _position, _normal) else: __place_marker(_position, _normal) func __place_marker(_position:Vector3, _normal:Vector3) -> void: if __bugbot_server._prepare_form(__bug_report_form_data_prepared) == BugbotServerAPI.BugbotServerError.OK: process_mode = Node.PROCESS_MODE_PAUSABLE func __pop_up_marker_info(_collider:BugInfoCollider, _position:Vector3, _normal:Vector3) -> void: var bug_info_dialogue : BugbotBugInfoDialogue = preload("res://addons/Bugbot/UI/bug_info.tscn").instantiate() add_child(bug_info_dialogue) bug_info_dialogue.bug_info = _collider.bug_info process_mode = Node.PROCESS_MODE_PAUSABLE bug_info_dialogue.tree_exited.connect(__on_bug_info_dialogue_closed) func __on_bug_info_dialogue_closed() -> void: process_mode = Node.PROCESS_MODE_ALWAYS func __bug_report_form_data_prepared(tag_lists:Array) -> void: __bug_report_form = preload("res://addons/Bugbot/UI/bug_report_form.tscn").instantiate() add_child(__bug_report_form) __bug_report_form.map_name = get_tree().current_scene.scene_file_path __bug_report_form.bug_location = __raycast_collision["position"] __bug_report_form.bug_rotation = __raycast_collision["normal"] __bug_report_form.fill_tags(tag_lists) __bug_report_form.submitted.connect(__form_submitted) __bug_report_form.cancelled.connect(__form_cancelled) func __form_submitted(bug:BugbotBugData) -> void: __get_bug_marker_root(get_tree().current_scene) var marker : BugMarker = (load(bug_marker) as PackedScene).instantiate() as BugMarker __bug_marker_root.add_child(marker) marker.global_position = bug.marker_position marker.set_rotation_to_normal(bug.marker_normal) marker.marker_status = BugMarker.BugStatus.UNRESOLVED marker.bug_info = bug __form_cancelled() func __form_cancelled() -> void: __bug_report_form.queue_free() process_mode = Node.PROCESS_MODE_ALWAYS func __place_dummy_marker() -> void: if __raycast_collision: __bug_marker_dummy.set_rotation_to_normal(__raycast_collision["normal"]) __bug_marker_dummy.visible = true else: __bug_marker_dummy.visible = false