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 ## Distance from camera to allow placing bug markers. @export_range(0.0, 100.0, 0.01, "or_greater", "suffix:m") var ray_length : float = 10.0 #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 Private Properties var __bugbot_server : BugbotServerAPI 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:Node3D, bug_list:Array): var bug_marker_preload : PackedScene = preload("res://addons/Bugbot/Scenes/bug_marker.tscn") for bugbot_data:Resource in bug_list: if bugbot_data is BugbotBugData: var bug_status : BugMarker.BugStatus = BugMarker.BugStatus.NEUTRAL var bug_data : BugbotBugData = bugbot_data as BugbotBugData if bug_data.resolution == BugbotServerGiteaAPI.RESOLVED_TAG: bug_status = BugMarker.BugStatus.RESOLVED elif bug_data.resolution == BugbotServerGiteaAPI.IN_PROGRESS_TAG: bug_status = BugMarker.BugStatus.IN_PROGRESS elif bug_data.resolution == BugbotServerGiteaAPI.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_data = bugbot_data bug_marker.global_position = bugbot_data.marker_position bug_marker.set_rotation_to_normal(bugbot_data.marker_normal) 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(): 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): var collider : Area3D = __raycast_collision["collider"] as Area3D if collider and collider.is_in_group("BugMarkerInfo"): __pop_up_marker_info(_position, _normal) else: __place_marker(_position, _normal) func __place_marker(_position:Vector3, _normal:Vector3): process_mode = Node.PROCESS_MODE_PAUSABLE var marker : BugMarker = (load(bug_marker) as PackedScene).instantiate() as BugMarker get_tree().get_root().add_child(marker) marker.global_position = _position marker.set_rotation_to_normal(_normal) marker.marker_status = BugMarker.BugStatus.UNRESOLVED __bugbot_server._prepare_form(__bug_report_form_data_prepared) func __pop_up_marker_info(_position:Vector3, _normal:Vector3): print("Pop up bug marker info here.") func __bug_report_form_data_prepared(tag_lists:Array): __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.closed.connect(__form_closed) func __form_closed(): __bug_report_form.queue_free() process_mode = Node.PROCESS_MODE_ALWAYS func __place_dummy_marker(): if __raycast_collision: __bug_marker_dummy.set_rotation_to_normal(__raycast_collision["normal"]) __bug_marker_dummy.visible = true else: __bug_marker_dummy.visible = false