Initial commit for testing/fixing a deprecated godot-colyseus addon
This commit is contained in:
315
scripts/hotas_mapper.gd
Normal file
315
scripts/hotas_mapper.gd
Normal file
@@ -0,0 +1,315 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@export var jet_path: NodePath
|
||||
@export var config_path: String = "user://hotas_mapping.cfg"
|
||||
@export var axis_capture_threshold: float = 0.35
|
||||
@export var toggle_action: String = "toggle_hotas_mapper"
|
||||
|
||||
@export var panel_position: Vector2 = Vector2(12, 12)
|
||||
@export var panel_size: Vector2 = Vector2(520, 360)
|
||||
|
||||
const AXIS_CONFIG = [
|
||||
{
|
||||
"name": "Roll",
|
||||
"axis_prop": "roll_axis",
|
||||
"invert_prop": "roll_invert",
|
||||
"device_prop": "roll_device_id",
|
||||
},
|
||||
{
|
||||
"name": "Pitch",
|
||||
"axis_prop": "pitch_axis",
|
||||
"invert_prop": "pitch_invert",
|
||||
"device_prop": "pitch_device_id",
|
||||
},
|
||||
{
|
||||
"name": "Yaw",
|
||||
"axis_prop": "yaw_axis",
|
||||
"invert_prop": "yaw_invert",
|
||||
"device_prop": "yaw_device_id",
|
||||
},
|
||||
{
|
||||
"name": "Throttle",
|
||||
"axis_prop": "throttle_axis",
|
||||
"invert_prop": "throttle_invert",
|
||||
"signed_prop": "throttle_signed",
|
||||
"device_prop": "throttle_device_id",
|
||||
},
|
||||
{
|
||||
"name": "Strafe",
|
||||
"axis_prop": "strafe_axis",
|
||||
"invert_prop": "strafe_invert",
|
||||
"device_prop": "strafe_device_id",
|
||||
},
|
||||
{
|
||||
"name": "Lift",
|
||||
"axis_prop": "lift_axis",
|
||||
"invert_prop": "lift_invert",
|
||||
"device_prop": "lift_device_id",
|
||||
},
|
||||
]
|
||||
|
||||
var _jet: Node
|
||||
var _panel: PanelContainer
|
||||
var _status_label: Label
|
||||
var _device_label: Label
|
||||
var _rows := {}
|
||||
var _listening_axis: String = ""
|
||||
|
||||
func _ready() -> void:
|
||||
_jet = get_node_or_null(jet_path)
|
||||
_build_ui()
|
||||
_load_config()
|
||||
_refresh_labels()
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if _is_toggle_event(event):
|
||||
visible = not visible
|
||||
if not visible:
|
||||
_listening_axis = ""
|
||||
_refresh_labels()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if not visible:
|
||||
return
|
||||
if _listening_axis.is_empty():
|
||||
return
|
||||
if event is InputEventJoypadMotion:
|
||||
if abs(event.axis_value) < axis_capture_threshold:
|
||||
return
|
||||
_apply_axis_mapping(_listening_axis, event.axis, event.device)
|
||||
_listening_axis = ""
|
||||
_status_label.text = "Axis captured."
|
||||
_refresh_labels()
|
||||
|
||||
func _build_ui() -> void:
|
||||
_panel = PanelContainer.new()
|
||||
_panel.name = "HotasMapper"
|
||||
_panel.anchor_left = 0.0
|
||||
_panel.anchor_top = 0.0
|
||||
_panel.anchor_right = 0.0
|
||||
_panel.anchor_bottom = 0.0
|
||||
_panel.offset_left = panel_position.x
|
||||
_panel.offset_top = panel_position.y
|
||||
_panel.offset_right = panel_position.x + panel_size.x
|
||||
_panel.offset_bottom = panel_position.y + panel_size.y
|
||||
add_child(_panel)
|
||||
|
||||
var root := VBoxContainer.new()
|
||||
root.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
root.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
root.add_theme_constant_override("separation", 8)
|
||||
_panel.add_child(root)
|
||||
|
||||
var header := HBoxContainer.new()
|
||||
header.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
root.add_child(header)
|
||||
|
||||
var title := Label.new()
|
||||
title.text = "HOTAS Axis Mapper"
|
||||
header.add_child(title)
|
||||
|
||||
var spacer := Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(spacer)
|
||||
|
||||
var close_button := Button.new()
|
||||
close_button.text = "Close"
|
||||
close_button.pressed.connect(_on_close_pressed)
|
||||
header.add_child(close_button)
|
||||
|
||||
_device_label = Label.new()
|
||||
_device_label.text = _get_device_text()
|
||||
root.add_child(_device_label)
|
||||
|
||||
_status_label = Label.new()
|
||||
_status_label.text = "Click Map, then move the desired axis. Toggle: %s" % _get_toggle_hint()
|
||||
root.add_child(_status_label)
|
||||
|
||||
for config in AXIS_CONFIG:
|
||||
root.add_child(_build_axis_row(config))
|
||||
|
||||
func _build_axis_row(config: Dictionary) -> Control:
|
||||
var row := HBoxContainer.new()
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
|
||||
var name_label := Label.new()
|
||||
name_label.text = config.name
|
||||
name_label.custom_minimum_size = Vector2(120, 0)
|
||||
row.add_child(name_label)
|
||||
|
||||
var map_button := Button.new()
|
||||
map_button.text = "Map"
|
||||
map_button.pressed.connect(_on_map_pressed.bind(config.axis_prop))
|
||||
row.add_child(map_button)
|
||||
|
||||
var axis_label := Label.new()
|
||||
axis_label.text = "Axis: ?"
|
||||
axis_label.custom_minimum_size = Vector2(120, 0)
|
||||
row.add_child(axis_label)
|
||||
|
||||
var device_label := Label.new()
|
||||
device_label.text = "Dev: ?"
|
||||
device_label.custom_minimum_size = Vector2(160, 0)
|
||||
row.add_child(device_label)
|
||||
|
||||
var invert_check := CheckBox.new()
|
||||
invert_check.text = "Invert"
|
||||
invert_check.toggled.connect(_on_invert_toggled.bind(config.invert_prop))
|
||||
row.add_child(invert_check)
|
||||
|
||||
var signed_check: CheckBox = null
|
||||
if config.has("signed_prop"):
|
||||
signed_check = CheckBox.new()
|
||||
signed_check.text = "Signed"
|
||||
signed_check.toggled.connect(_on_signed_toggled.bind(config.signed_prop))
|
||||
row.add_child(signed_check)
|
||||
|
||||
_rows[config.axis_prop] = {
|
||||
"axis_label": axis_label,
|
||||
"device_label": device_label,
|
||||
"map_button": map_button,
|
||||
"invert_check": invert_check,
|
||||
"signed_check": signed_check,
|
||||
}
|
||||
return row
|
||||
|
||||
func _on_map_pressed(axis_prop: String) -> void:
|
||||
if _listening_axis == axis_prop:
|
||||
_listening_axis = ""
|
||||
_status_label.text = "Mapping cancelled."
|
||||
else:
|
||||
_listening_axis = axis_prop
|
||||
_status_label.text = "Listening for %s axis..." % axis_prop
|
||||
_refresh_labels()
|
||||
|
||||
func _on_close_pressed() -> void:
|
||||
_listening_axis = ""
|
||||
visible = false
|
||||
|
||||
func _on_invert_toggled(pressed: bool, invert_prop: String) -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
_jet.set(invert_prop, pressed)
|
||||
_save_config()
|
||||
|
||||
func _on_signed_toggled(pressed: bool, signed_prop: String) -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
_jet.set(signed_prop, pressed)
|
||||
_save_config()
|
||||
|
||||
func _apply_axis_mapping(axis_prop: String, axis_index: int, device_id: int) -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
_jet.set(axis_prop, axis_index)
|
||||
if device_id >= 0:
|
||||
var device_prop = _get_device_prop(axis_prop)
|
||||
if not device_prop.is_empty():
|
||||
_jet.set(device_prop, device_id)
|
||||
_jet.set("joy_device_id", device_id)
|
||||
_save_config()
|
||||
_device_label.text = _get_device_text()
|
||||
|
||||
func _refresh_labels() -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
_device_label.text = _get_device_text()
|
||||
for config in AXIS_CONFIG:
|
||||
var axis_prop: String = config.axis_prop
|
||||
var row = _rows.get(axis_prop, null)
|
||||
if row == null:
|
||||
continue
|
||||
row["axis_label"].text = "Axis: %d" % int(_jet.get(axis_prop))
|
||||
var device_prop: String = config.device_prop
|
||||
var device_id: int = int(_jet.get(device_prop))
|
||||
row["device_label"].text = _get_device_label(device_id)
|
||||
var invert_prop: String = config.invert_prop
|
||||
row["invert_check"].set_pressed_no_signal(bool(_jet.get(invert_prop)))
|
||||
if config.has("signed_prop") and row["signed_check"] != null:
|
||||
row["signed_check"].set_pressed_no_signal(bool(_jet.get(config.signed_prop)))
|
||||
var map_button: Button = row["map_button"]
|
||||
if _listening_axis == axis_prop:
|
||||
map_button.text = "Listening..."
|
||||
else:
|
||||
map_button.text = "Map"
|
||||
|
||||
func _load_config() -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
var config := ConfigFile.new()
|
||||
var err = config.load(config_path)
|
||||
if err != OK:
|
||||
return
|
||||
for entry in AXIS_CONFIG:
|
||||
var axis_prop: String = entry.axis_prop
|
||||
if config.has_section_key("axes", axis_prop):
|
||||
_jet.set(axis_prop, int(config.get_value("axes", axis_prop)))
|
||||
var invert_prop: String = entry.invert_prop
|
||||
if config.has_section_key("invert", invert_prop):
|
||||
_jet.set(invert_prop, bool(config.get_value("invert", invert_prop)))
|
||||
var device_prop: String = entry.device_prop
|
||||
if config.has_section_key("devices", device_prop):
|
||||
_jet.set(device_prop, int(config.get_value("devices", device_prop)))
|
||||
if entry.has("signed_prop"):
|
||||
var signed_prop: String = entry.signed_prop
|
||||
if config.has_section_key("flags", signed_prop):
|
||||
_jet.set(signed_prop, bool(config.get_value("flags", signed_prop)))
|
||||
if config.has_section_key("device", "joy_device_id"):
|
||||
_jet.set("joy_device_id", int(config.get_value("device", "joy_device_id")))
|
||||
|
||||
func _save_config() -> void:
|
||||
if _jet == null:
|
||||
return
|
||||
var config := ConfigFile.new()
|
||||
for entry in AXIS_CONFIG:
|
||||
var axis_prop: String = entry.axis_prop
|
||||
config.set_value("axes", axis_prop, int(_jet.get(axis_prop)))
|
||||
var invert_prop: String = entry.invert_prop
|
||||
config.set_value("invert", invert_prop, bool(_jet.get(invert_prop)))
|
||||
var device_prop: String = entry.device_prop
|
||||
config.set_value("devices", device_prop, int(_jet.get(device_prop)))
|
||||
if entry.has("signed_prop"):
|
||||
var signed_prop: String = entry.signed_prop
|
||||
config.set_value("flags", signed_prop, bool(_jet.get(signed_prop)))
|
||||
config.set_value("device", "joy_device_id", int(_jet.get("joy_device_id")))
|
||||
config.save(config_path)
|
||||
|
||||
func _get_device_text() -> String:
|
||||
if _jet == null:
|
||||
return "Device: none"
|
||||
var device_id: int = int(_jet.get("joy_device_id"))
|
||||
if device_id < 0:
|
||||
var pads = Input.get_connected_joypads()
|
||||
if pads.is_empty():
|
||||
return "Device: none"
|
||||
device_id = pads[0]
|
||||
var name = Input.get_joy_name(device_id)
|
||||
return "Device: %s (id %d)" % [name, device_id]
|
||||
|
||||
func _get_device_label(device_id: int) -> String:
|
||||
if device_id < 0:
|
||||
return "Dev: auto"
|
||||
var name = Input.get_joy_name(device_id)
|
||||
if name.is_empty():
|
||||
return "Dev: %d" % device_id
|
||||
return "Dev: %s (%d)" % [name, device_id]
|
||||
|
||||
func _get_device_prop(axis_prop: String) -> String:
|
||||
for entry in AXIS_CONFIG:
|
||||
if entry.axis_prop == axis_prop:
|
||||
return entry.device_prop
|
||||
return ""
|
||||
|
||||
func _is_toggle_event(event: InputEvent) -> bool:
|
||||
if InputMap.has_action(toggle_action):
|
||||
return event.is_action_pressed(toggle_action)
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
return event.keycode == KEY_F1
|
||||
return false
|
||||
|
||||
func _get_toggle_hint() -> String:
|
||||
if InputMap.has_action(toggle_action):
|
||||
return toggle_action
|
||||
return "F1"
|
||||
1
scripts/hotas_mapper.gd.uid
Normal file
1
scripts/hotas_mapper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bj8dpyx6m80px
|
||||
130
scripts/hud.gd
Normal file
130
scripts/hud.gd
Normal file
@@ -0,0 +1,130 @@
|
||||
extends CanvasLayer
|
||||
|
||||
@export var player_path: NodePath
|
||||
@export var camera_path: NodePath
|
||||
@export var vector_scale: float = 2.5
|
||||
@export var max_radius: float = 140.0
|
||||
@export var gizmo_margin: Vector2 = Vector2(140, 140)
|
||||
@export var gizmo_axis_length: float = 55.0
|
||||
@export var gizmo_axis_width: float = 2.0
|
||||
@export var velocity_line_width: float = 3.0
|
||||
@export var show_axis_debug: bool = true
|
||||
|
||||
var _velocity_line: Line2D
|
||||
var _axis_x: Line2D
|
||||
var _axis_y: Line2D
|
||||
var _axis_z: Line2D
|
||||
var _vector_label: Label
|
||||
var _axis_label: Label
|
||||
|
||||
var _player: RigidBody3D
|
||||
var _camera: Camera3D
|
||||
|
||||
func _ready() -> void:
|
||||
_velocity_line = _get_or_create_velocity_line()
|
||||
_axis_x = _get_or_create_axis_line("AxisX", Color(0.95, 0.2, 0.2, 0.9))
|
||||
_axis_y = _get_or_create_axis_line("AxisY", Color(0.2, 0.9, 0.4, 0.9))
|
||||
_axis_z = _get_or_create_axis_line("AxisZ", Color(0.2, 0.5, 1.0, 0.9))
|
||||
_vector_label = _get_or_create_vector_label()
|
||||
_axis_label = _get_or_create_axis_label()
|
||||
_player = get_node_or_null(player_path)
|
||||
_camera = get_node_or_null(camera_path)
|
||||
_axis_label.visible = show_axis_debug
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if _player == null or _camera == null:
|
||||
return
|
||||
|
||||
var velocity = _player.linear_velocity
|
||||
var cam_basis = _camera.global_transform.basis
|
||||
var local_velocity = cam_basis.inverse() * velocity
|
||||
|
||||
var arrow = Vector2(local_velocity.x, -local_velocity.y) * vector_scale
|
||||
if arrow.length() > max_radius:
|
||||
arrow = arrow.normalized() * max_radius
|
||||
|
||||
var viewport_size = get_viewport().get_visible_rect().size
|
||||
var center = Vector2(
|
||||
viewport_size.x - gizmo_margin.x,
|
||||
viewport_size.y - gizmo_margin.y
|
||||
)
|
||||
_velocity_line.points = PackedVector2Array([center, center + arrow])
|
||||
|
||||
var player_basis = _player.global_transform.basis
|
||||
var rel_basis = cam_basis.inverse() * player_basis
|
||||
_axis_x.points = PackedVector2Array([
|
||||
center,
|
||||
center + Vector2(rel_basis.x.x, -rel_basis.x.y) * gizmo_axis_length
|
||||
])
|
||||
_axis_y.points = PackedVector2Array([
|
||||
center,
|
||||
center + Vector2(rel_basis.y.x, -rel_basis.y.y) * gizmo_axis_length
|
||||
])
|
||||
_axis_z.points = PackedVector2Array([
|
||||
center,
|
||||
center + Vector2(rel_basis.z.x, -rel_basis.z.y) * gizmo_axis_length
|
||||
])
|
||||
_vector_label.text = "Velocity: (%.1f, %.1f, %.1f) m/s | Speed: %.1f" % [
|
||||
velocity.x,
|
||||
velocity.y,
|
||||
velocity.z,
|
||||
velocity.length(),
|
||||
]
|
||||
|
||||
if show_axis_debug and _axis_label != null:
|
||||
if _player.has_method("get_axis_debug_text"):
|
||||
_axis_label.text = _player.get_axis_debug_text()
|
||||
|
||||
func _get_or_create_velocity_line() -> Line2D:
|
||||
var existing: Line2D = get_node_or_null("VelocityLine")
|
||||
if existing != null:
|
||||
return existing
|
||||
var line := Line2D.new()
|
||||
line.name = "VelocityLine"
|
||||
line.width = velocity_line_width
|
||||
line.default_color = Color(1, 0.7, 0.2, 0.9)
|
||||
line.antialiased = true
|
||||
add_child(line)
|
||||
return line
|
||||
|
||||
func _get_or_create_axis_line(name: String, color: Color) -> Line2D:
|
||||
var existing: Line2D = get_node_or_null(name)
|
||||
if existing != null:
|
||||
return existing
|
||||
var line := Line2D.new()
|
||||
line.name = name
|
||||
line.width = gizmo_axis_width
|
||||
line.default_color = color
|
||||
line.antialiased = true
|
||||
add_child(line)
|
||||
return line
|
||||
|
||||
func _get_or_create_vector_label() -> Label:
|
||||
var existing: Label = get_node_or_null("VectorLabel")
|
||||
if existing != null:
|
||||
return existing
|
||||
var label := Label.new()
|
||||
label.name = "VectorLabel"
|
||||
label.text = "Velocity:"
|
||||
label.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||
label.offset_left = 12.0
|
||||
label.offset_top = 12.0
|
||||
label.offset_right = 520.0
|
||||
label.offset_bottom = 40.0
|
||||
add_child(label)
|
||||
return label
|
||||
|
||||
func _get_or_create_axis_label() -> Label:
|
||||
var existing: Label = get_node_or_null("AxisLabel")
|
||||
if existing != null:
|
||||
return existing
|
||||
var label := Label.new()
|
||||
label.name = "AxisLabel"
|
||||
label.text = "Axis:"
|
||||
label.set_anchors_and_offsets_preset(Control.PRESET_BOTTOM_LEFT)
|
||||
label.offset_left = 12.0
|
||||
label.offset_top = -60.0
|
||||
label.offset_right = 520.0
|
||||
label.offset_bottom = -12.0
|
||||
add_child(label)
|
||||
return label
|
||||
1
scripts/hud.gd.uid
Normal file
1
scripts/hud.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://csqjx18o6oblo
|
||||
194
scripts/jet_controller.gd
Normal file
194
scripts/jet_controller.gd
Normal file
@@ -0,0 +1,194 @@
|
||||
extends RigidBody3D
|
||||
|
||||
@export var joy_device_id: int = -1
|
||||
|
||||
@export var pitch_axis: int = 1
|
||||
@export var pitch_invert: bool = true
|
||||
@export var pitch_device_id: int = -1
|
||||
@export var roll_axis: int = 0
|
||||
@export var roll_invert: bool = false
|
||||
@export var roll_device_id: int = -1
|
||||
@export var yaw_axis: int = 2
|
||||
@export var yaw_invert: bool = false
|
||||
@export var yaw_device_id: int = -1
|
||||
@export var throttle_axis: int = 3
|
||||
@export var throttle_invert: bool = true
|
||||
@export var throttle_signed: bool = true
|
||||
@export var throttle_device_id: int = -1
|
||||
@export var strafe_axis: int = 4
|
||||
@export var strafe_invert: bool = false
|
||||
@export var strafe_device_id: int = -1
|
||||
@export var lift_axis: int = 5
|
||||
@export var lift_invert: bool = false
|
||||
@export var lift_device_id: int = -1
|
||||
|
||||
@export var stick_deadzone: float = 0.08
|
||||
@export var throttle_deadzone: float = 0.02
|
||||
|
||||
@export var max_thrust: float = 7500.0
|
||||
@export var strafe_thrust: float = 3500.0
|
||||
@export var lift_thrust: float = 3500.0
|
||||
@export var torque_strength: float = 2000.0
|
||||
|
||||
@export var throttle_response: float = 3.0
|
||||
@export var stick_response: float = 6.0
|
||||
@export var stick_expo: float = 1.6
|
||||
@export var max_speed: float = 180.0
|
||||
@export var max_angular_speed: float = 2.6
|
||||
|
||||
@export var vector_line_path: NodePath = NodePath("DisplacementVector")
|
||||
@export var vector_scale: float = 0.05
|
||||
@export var max_vector_length: float = 20.0
|
||||
|
||||
var _device_id: int = -1
|
||||
var _current_throttle: float = 0.0
|
||||
var _vector_line: Node
|
||||
var _axis_state := {
|
||||
"roll": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"throttle": 0.0,
|
||||
"strafe": 0.0,
|
||||
"lift": 0.0,
|
||||
}
|
||||
var _axis_smoothed := {
|
||||
"roll": 0.0,
|
||||
"pitch": 0.0,
|
||||
"yaw": 0.0,
|
||||
"strafe": 0.0,
|
||||
"lift": 0.0,
|
||||
}
|
||||
|
||||
func _ready() -> void:
|
||||
_vector_line = get_node_or_null(vector_line_path)
|
||||
_device_id = _resolve_joypad()
|
||||
if _device_id == -1:
|
||||
push_warning("No HOTAS detected. Connect the X56 or set joy_device_id.")
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_device_id = _resolve_joypad()
|
||||
if _device_id == -1:
|
||||
_update_vector_line()
|
||||
return
|
||||
|
||||
var roll = _shape_axis(_read_axis(_resolve_axis_device(roll_device_id), roll_axis, stick_deadzone, roll_invert))
|
||||
var pitch = _shape_axis(_read_axis(_resolve_axis_device(pitch_device_id), pitch_axis, stick_deadzone, pitch_invert))
|
||||
var yaw = _shape_axis(_read_axis(_resolve_axis_device(yaw_device_id), yaw_axis, stick_deadzone, yaw_invert))
|
||||
var strafe = _shape_axis(_read_axis(_resolve_axis_device(strafe_device_id), strafe_axis, stick_deadzone, strafe_invert))
|
||||
var lift = _shape_axis(_read_axis(_resolve_axis_device(lift_device_id), lift_axis, stick_deadzone, lift_invert))
|
||||
var raw_throttle = _read_axis(_resolve_axis_device(throttle_device_id), throttle_axis, throttle_deadzone, throttle_invert)
|
||||
|
||||
var target_throttle: float
|
||||
if throttle_signed:
|
||||
target_throttle = clamp(raw_throttle, -1.0, 1.0)
|
||||
else:
|
||||
target_throttle = clamp((raw_throttle + 1.0) * 0.5, 0.0, 1.0)
|
||||
|
||||
_current_throttle = lerp(
|
||||
_current_throttle,
|
||||
target_throttle,
|
||||
clamp(throttle_response * delta, 0.0, 1.0)
|
||||
)
|
||||
|
||||
roll = _smooth_axis("roll", roll, delta)
|
||||
pitch = _smooth_axis("pitch", pitch, delta)
|
||||
yaw = _smooth_axis("yaw", yaw, delta)
|
||||
strafe = _smooth_axis("strafe", strafe, delta)
|
||||
lift = _smooth_axis("lift", lift, delta)
|
||||
|
||||
_axis_state["roll"] = roll
|
||||
_axis_state["pitch"] = pitch
|
||||
_axis_state["yaw"] = yaw
|
||||
_axis_state["throttle"] = _current_throttle
|
||||
_axis_state["strafe"] = strafe
|
||||
_axis_state["lift"] = lift
|
||||
|
||||
var basis = global_transform.basis
|
||||
var forward = -basis.z
|
||||
var right = basis.x
|
||||
var up = basis.y
|
||||
|
||||
var thrust_force = forward * (_current_throttle * max_thrust)
|
||||
var strafe_force = right * (strafe * strafe_thrust)
|
||||
var lift_force = up * (lift * lift_thrust)
|
||||
apply_central_force(thrust_force + strafe_force + lift_force)
|
||||
|
||||
var local_angular = basis.inverse() * angular_velocity
|
||||
var desired_angular = Vector3(pitch, yaw, -roll) * max_angular_speed
|
||||
var torque_local = (desired_angular - local_angular) * torque_strength
|
||||
apply_torque(basis * torque_local)
|
||||
|
||||
if max_speed > 0.0 and linear_velocity.length() > max_speed:
|
||||
linear_velocity = linear_velocity.limit_length(max_speed)
|
||||
if max_angular_speed > 0.0 and angular_velocity.length() > max_angular_speed:
|
||||
angular_velocity = angular_velocity.limit_length(max_angular_speed)
|
||||
|
||||
_update_vector_line()
|
||||
|
||||
func get_axis_debug_text() -> String:
|
||||
if _device_id == -1:
|
||||
return "No HOTAS detected"
|
||||
return "Roll: %.2f (d%d) Pitch: %.2f (d%d) Yaw: %.2f (d%d)\nThrottle: %.2f (d%d) Strafe: %.2f (d%d) Lift: %.2f (d%d)" % [
|
||||
_axis_state["roll"],
|
||||
_resolve_axis_device(roll_device_id),
|
||||
_axis_state["pitch"],
|
||||
_resolve_axis_device(pitch_device_id),
|
||||
_axis_state["yaw"],
|
||||
_resolve_axis_device(yaw_device_id),
|
||||
_axis_state["throttle"],
|
||||
_resolve_axis_device(throttle_device_id),
|
||||
_axis_state["strafe"],
|
||||
_resolve_axis_device(strafe_device_id),
|
||||
_axis_state["lift"],
|
||||
_resolve_axis_device(lift_device_id),
|
||||
]
|
||||
|
||||
func get_joypad_id() -> int:
|
||||
return _device_id
|
||||
|
||||
func _resolve_joypad() -> int:
|
||||
if joy_device_id >= 0:
|
||||
return joy_device_id
|
||||
var pads = Input.get_connected_joypads()
|
||||
if pads.is_empty():
|
||||
return -1
|
||||
return pads[0]
|
||||
|
||||
func _resolve_axis_device(axis_device_id: int) -> int:
|
||||
if axis_device_id >= 0:
|
||||
return axis_device_id
|
||||
return _device_id
|
||||
|
||||
func _read_axis(device_id: int, axis: int, deadzone: float, invert: bool) -> float:
|
||||
if axis < 0 or device_id < 0:
|
||||
return 0.0
|
||||
var value = Input.get_joy_axis(device_id, axis)
|
||||
if invert:
|
||||
value = -value
|
||||
var magnitude = abs(value)
|
||||
if magnitude <= deadzone:
|
||||
return 0.0
|
||||
var scaled = (magnitude - deadzone) / (1.0 - deadzone)
|
||||
return scaled * sign(value)
|
||||
|
||||
func _shape_axis(value: float) -> float:
|
||||
if stick_expo <= 1.0:
|
||||
return value
|
||||
return sign(value) * pow(abs(value), stick_expo)
|
||||
|
||||
func _smooth_axis(name: String, value: float, delta: float) -> float:
|
||||
if stick_response <= 0.0:
|
||||
_axis_smoothed[name] = value
|
||||
return value
|
||||
var t = clamp(stick_response * delta, 0.0, 1.0)
|
||||
_axis_smoothed[name] = lerp(_axis_smoothed[name], value, t)
|
||||
return _axis_smoothed[name]
|
||||
|
||||
func _update_vector_line() -> void:
|
||||
if _vector_line == null:
|
||||
return
|
||||
if not _vector_line.has_method("set_vector"):
|
||||
return
|
||||
var local_velocity = global_transform.basis.inverse() * linear_velocity
|
||||
var clamped = local_velocity.limit_length(max_vector_length)
|
||||
_vector_line.set_vector(clamped * vector_scale)
|
||||
1
scripts/jet_controller.gd.uid
Normal file
1
scripts/jet_controller.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bs6rfdeujndbp
|
||||
109
scripts/network.gd
Normal file
109
scripts/network.gd
Normal file
@@ -0,0 +1,109 @@
|
||||
# res://scripts/network.gd
|
||||
extends Node
|
||||
|
||||
signal connected(room_id: String)
|
||||
signal disconnected(reason: String)
|
||||
signal state_changed()
|
||||
signal message_received(type: String, payload: Variant)
|
||||
signal error(msg: String)
|
||||
|
||||
const colyseus := preload("res://addons/godot_colyseus/lib/colyseus.gd")
|
||||
const RoomState := preload("res://scripts/room_state.gd")
|
||||
const Room := preload("res://addons/godot_colyseus/lib/room.gd")
|
||||
|
||||
var client: colyseus.Client
|
||||
var room: Room
|
||||
var state: RoomState
|
||||
|
||||
var _connecting := false
|
||||
var _endpoint := ""
|
||||
|
||||
#func is_connected() -> bool:
|
||||
#return room != null
|
||||
|
||||
func connect_and_join(endpoint: String, room_name: String = "my_room") -> void:
|
||||
if _connecting:
|
||||
return
|
||||
if room != null:
|
||||
emit_signal("error", "Already connected.")
|
||||
return
|
||||
|
||||
_connecting = true
|
||||
_endpoint = endpoint
|
||||
|
||||
client = colyseus.Client.new(endpoint)
|
||||
|
||||
var promise = client.join_or_create(RoomState, room_name)
|
||||
await promise.completed
|
||||
_connecting = false
|
||||
|
||||
if promise.get_state() == promise.State.Failed:
|
||||
var msg := "Join failed: %s" % str(promise.get_error())
|
||||
emit_signal("error", msg)
|
||||
return
|
||||
|
||||
# old addon: result is a property
|
||||
room = promise.result
|
||||
if room == null:
|
||||
emit_signal("error", "Join succeeded but room is null (promise.result).")
|
||||
return
|
||||
|
||||
state = room.get_state() as RoomState
|
||||
if state == null:
|
||||
emit_signal("error", "Joined but could not cast state to RoomState.")
|
||||
return
|
||||
|
||||
# --- wire listeners ---
|
||||
room.on_state_change.on(Callable(self, "_on_state_change"))
|
||||
|
||||
# if addon supports on_leave / on_error, hook them too (safe-guarded)
|
||||
if "on_leave" in room:
|
||||
room.on_leave.on(Callable(self, "_on_room_left"))
|
||||
if "on_error" in room:
|
||||
room.on_error.on(Callable(self, "_on_room_error"))
|
||||
|
||||
emit_signal("connected", room.room_id)
|
||||
|
||||
func leave() -> void:
|
||||
if room == null:
|
||||
return
|
||||
# some addons use room.leave(), some room.disconnect()
|
||||
if room.has_method("leave"):
|
||||
room.leave()
|
||||
elif room.has_method("disconnect"):
|
||||
pass
|
||||
#room.disconnect()
|
||||
_cleanup("left")
|
||||
|
||||
func send(type: String, payload: Variant = null) -> void:
|
||||
if room == null:
|
||||
emit_signal("error", "Cannot send, not connected.")
|
||||
return
|
||||
room.send(type, payload)
|
||||
|
||||
func listen_message(type: String) -> void:
|
||||
if room == null:
|
||||
emit_signal("error", "Cannot listen, not connected.")
|
||||
return
|
||||
room.on_message(type).on(Callable(self, "_on_message").bind(type))
|
||||
|
||||
func reconnect(room_name: String = "my_room") -> void:
|
||||
_cleanup("reconnect")
|
||||
await connect_and_join(_endpoint, room_name)
|
||||
|
||||
func _on_state_change(_new_state = null) -> void:
|
||||
emit_signal("state_changed")
|
||||
|
||||
func _on_message(payload: Variant, type: String) -> void:
|
||||
emit_signal("message_received", type, payload)
|
||||
|
||||
func _on_room_left(code = null) -> void:
|
||||
_cleanup("room_left %s" % str(code))
|
||||
|
||||
func _on_room_error(code = null, message = null) -> void:
|
||||
_cleanup("room_error %s %s" % [str(code), str(message)])
|
||||
|
||||
func _cleanup(reason: String) -> void:
|
||||
room = null
|
||||
state = null
|
||||
emit_signal("disconnected", reason)
|
||||
1
scripts/network.gd.uid
Normal file
1
scripts/network.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d0amrs41uhwql
|
||||
10
scripts/room_state.gd
Normal file
10
scripts/room_state.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
# res://scripts/room_state.gd
|
||||
extends "res://addons/godot_colyseus/lib/schema.gd"
|
||||
class_name RoomState
|
||||
|
||||
var mySynchronizedProperty: String = "Hello world"
|
||||
|
||||
static func define_fields():
|
||||
return [
|
||||
Field.new("mySynchronizedProperty", Types.STRING),
|
||||
]
|
||||
1
scripts/room_state.gd.uid
Normal file
1
scripts/room_state.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dlh5cvfm6xapu
|
||||
22
scripts/vector_line_3d.gd
Normal file
22
scripts/vector_line_3d.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
extends MeshInstance3D
|
||||
|
||||
@export var color: Color = Color(0.1, 0.8, 1.0, 0.9)
|
||||
|
||||
var _mesh: ImmediateMesh
|
||||
var _material: StandardMaterial3D
|
||||
|
||||
func _ready() -> void:
|
||||
_mesh = ImmediateMesh.new()
|
||||
mesh = _mesh
|
||||
_material = StandardMaterial3D.new()
|
||||
_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
_material.albedo_color = color
|
||||
|
||||
func set_vector(end_point: Vector3) -> void:
|
||||
if _mesh == null:
|
||||
return
|
||||
_mesh.clear_surfaces()
|
||||
_mesh.surface_begin(Mesh.PRIMITIVE_LINES, _material)
|
||||
_mesh.surface_add_vertex(Vector3.ZERO)
|
||||
_mesh.surface_add_vertex(end_point)
|
||||
_mesh.surface_end()
|
||||
1
scripts/vector_line_3d.gd.uid
Normal file
1
scripts/vector_line_3d.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3ierf3uelmp8
|
||||
Reference in New Issue
Block a user