class_name RadioPanel extends CanvasLayer ## Radio report form (PLAN.md Level 4 assist / Level 5 emergency). ## Emergency calls use hold-to-activate (press and hold 1.6s) to prevent ## accidental activation. signal submitted(report: Dictionary, emergency: bool) signal cancelled() const Rules := preload("res://src/core/rules.gd") var panel: PanelContainer var npc_name_label: Label var location_edit: LineEdit var desc_edit: LineEdit var weapons_check: CheckBox var danger_option: OptionButton var action_button: Button var hold_hint: Label var status_label: Label var _emergency := false var _npc_id := "" var _holding := false var _hold_time := 0.0 const HOLD_NEEDED := 1.6 var _truth := {"weapons_seen": false, "danger_level": 0} func _init() -> void: name = "RadioPanel" layer = 20 var dim := ColorRect.new() dim.color = Color(0, 0, 0, 0.55) dim.set_anchors_preset(Control.PRESET_FULL_RECT) add_child(dim) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) center.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(center) panel = PanelContainer.new() center.add_child(panel) var vbox := VBoxContainer.new() panel.add_child(vbox) vbox.add_theme_constant_override("separation", 8) var title := Label.new() title.name = "RadioTitle" title.add_theme_font_size_override("font_size", 18) vbox.add_child(title) npc_name_label = Label.new() vbox.add_child(npc_name_label) location_edit = LineEdit.new() location_edit.placeholder_text = "Location (e.g. front steps)" location_edit.text = "front entrance" vbox.add_child(location_edit) desc_edit = LineEdit.new() desc_edit.placeholder_text = "Describe the person and behavior" vbox.add_child(desc_edit) weapons_check = CheckBox.new() weapons_check.text = "Weapon seen" vbox.add_child(weapons_check) var danger_row := HBoxContainer.new() vbox.add_child(danger_row) var danger_label := Label.new() danger_label.text = "Danger level: " danger_row.add_child(danger_label) danger_option = OptionButton.new() for i in 4: danger_option.add_item("level %d — %s" % [i, _danger_name(i)], i) danger_option.select(1) danger_row.add_child(danger_option) hold_hint = Label.new() hold_hint.text = "" hold_hint.modulate = Color(1, 0.85, 0.55) vbox.add_child(hold_hint) status_label = Label.new() status_label.text = "" vbox.add_child(status_label) var buttons := HBoxContainer.new() vbox.add_child(buttons) action_button = Button.new() action_button.text = "Send request" action_button.button_down.connect(func(): _start_hold()) action_button.button_up.connect(func(): _stop_hold(false)) buttons.add_child(action_button) var cancel_btn := Button.new() cancel_btn.text = "Cancel" cancel_btn.pressed.connect(func(): cancelled.emit()) buttons.add_child(cancel_btn) var _title_label: Label func open(controller: Object, emergency: bool, truth: Dictionary) -> void: _emergency = emergency _truth = truth _npc_id = controller.npc_id if controller else "" npc_name_label.text = "Target: %s" % (controller.profile.display_name if controller else "?") var title: Label = panel.find_child("RadioTitle", true, false) title.text = "EMERGENCY CALL" if emergency else "Security Assistance" weapons_check.button_pressed = truth.get("weapons_seen", false) danger_option.select(truth.get("danger_level", 1)) hold_hint.text = "Hold the button for %.1fs" % HOLD_NEEDED if emergency else "" status_label.text = "" action_button.text = "SEND EMERGENCY" if emergency else "Send request" _holding = false _hold_time = 0.0 visible = true func _start_hold() -> void: _holding = _emergency _hold_time = 0.0 func _stop_hold(cancelled_early: bool) -> void: if _holding: _holding = false if not cancelled_early and _hold_time >= HOLD_NEEDED: _submit() else: status_label.text = "Call cancelled - must hold the button." func _process(delta: float) -> void: if not visible: return if _holding: _hold_time += delta status_label.text = "Holding... %.0f%%" % (minf(1.0, _hold_time / HOLD_NEEDED) * 100.0) if _hold_time >= HOLD_NEEDED: var was := _holding _holding = false if was: _submit() func _submit() -> void: if not visible: return var report := { "npc_id": _npc_id, "location": location_edit.text, "description": desc_edit.text, "behavior": desc_edit.text, "weapons_seen": weapons_check.button_pressed, "danger_level": danger_option.get_selected_id(), } visible = false submitted.emit(report, _emergency) func _danger_name(level: int) -> String: match level: 0: return "none" 1: return "low" 2: return "elevated" 3: return "immediate threat" _: return "unknown"