mirror of
https://github.com/ElHombreAlex/FTL-Faster-Than-Light-VR.git
synced 2026-10-06 03:00:06 +02:00
Update gameplay feedback and repair portable VR launchers
This commit is contained in:
1 parent
35cdf71054
commit
6fffde9dc3
36 files changed
+1498
-176
No files matched your search
+136
-17
@@ -11,6 +11,81 @@ var heading := "ACTIONS"
|
||||
var viewport: SubViewport
|
||||
var canvas: Control
|
||||
var content_revision := 0
|
||||
var display_rows: Array = []
|
||||
const LABEL_WIDTH := 136.0
|
||||
const LABEL_SIZE := 23
|
||||
|
||||
|
||||
static func equipment_entries(player: Dictionary, drone_slots: int) -> Array:
|
||||
# Slot keys remain the native 1–8 shortcuts. Only equipment metadata is
|
||||
# retained: positions, charge clocks and moving special actors cannot make
|
||||
# the held wheel redraw on each state snapshot.
|
||||
var result: Array = []
|
||||
var weapons: Array = _equipment_rows(player.get("weapons", []))
|
||||
var drones: Array = _equipment_rows(player.get("drone_equipment", player.get("drones", [])))
|
||||
var has_drone_equipment := player.has("drone_equipment")
|
||||
for slot in range(8):
|
||||
var weapon_slot := slot < 4
|
||||
var index := slot if weapon_slot else slot - 4
|
||||
var equipment: Dictionary = {}
|
||||
var source: Array = weapons if weapon_slot else drones
|
||||
for i in range(source.size()):
|
||||
if not source[i] is Dictionary:
|
||||
continue
|
||||
var item: Dictionary = source[i]
|
||||
var native_slot := int(item.get("slot", i)) if weapon_slot or has_drone_equipment else int(item.get("equipment_slot", item.get("slot", -1)))
|
||||
if native_slot == index:
|
||||
equipment = item
|
||||
break
|
||||
var enabled := not equipment.is_empty() if weapon_slot else index < drone_slots
|
||||
var family := str(equipment.get("kind", "drone" if not weapon_slot else "laser")).to_lower()
|
||||
family = {"missiles":"missile", "burst":"flak"}.get(family, family)
|
||||
var name := str(equipment.get("title", equipment.get("short_title", ""))).strip_edges()
|
||||
if name == "":
|
||||
name = str(equipment.get("name", "")).replace("_", " ").strip_edges()
|
||||
if name == "":
|
||||
name = "DRONE %d" % (index + 1) if enabled and not weapon_slot else "EMPTY"
|
||||
var missile_cost := int(equipment.get("ammo_cost", equipment.get("missile_cost", equipment.get("missiles", 1 if family in ["missile", "bomb"] else 0))))
|
||||
var detail := "%d MISSILE%s" % [missile_cost, "S" if missile_cost != 1 else ""] if weapon_slot and missile_cost > 0 else family.to_upper() if enabled else "NO EQUIPMENT"
|
||||
result.append({"label":name,"key":49 + slot,"enabled":enabled,
|
||||
"icon_kind":family if enabled else "empty", "detail":detail,
|
||||
"equipment":true,"weapon":weapon_slot})
|
||||
return result
|
||||
|
||||
|
||||
static func _equipment_rows(value: Variant) -> Array:
|
||||
# Empty native Lua tables serialize as {}. Treat a missing equipment list
|
||||
# as empty so a ship without drones still exposes its weapon shortcuts.
|
||||
return value if value is Array else []
|
||||
|
||||
|
||||
static func bounded_lines(value: String, width: float = LABEL_WIDTH, size_px: int = LABEL_SIZE, limit: int = 3) -> Array[String]:
|
||||
# Fit using the actual locally loaded glyph metrics, including long words
|
||||
# and translations. Fixed character counts cannot bound native font ink.
|
||||
var words := value.replace("\n", " ").split(" ", false)
|
||||
var output: Array[String] = []
|
||||
var line := ""
|
||||
for word in words:
|
||||
var next := str(word) if line == "" else line + " " + str(word)
|
||||
if UI.text_size(next, size_px).x <= width:
|
||||
line = next
|
||||
continue
|
||||
if line != "":
|
||||
output.append(line)
|
||||
line = ""
|
||||
for character in str(word):
|
||||
if line != "" and UI.text_size(line + character, size_px).x > width:
|
||||
output.append(line)
|
||||
line = ""
|
||||
line += character
|
||||
if line != "": output.append(line)
|
||||
if output.size() > limit:
|
||||
output.resize(limit)
|
||||
var last := output[limit - 1]
|
||||
while last != "" and UI.text_size(last + "...", size_px).x > width:
|
||||
last = last.left(last.length() - 1)
|
||||
output[limit - 1] = last + "..."
|
||||
return output
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -46,6 +121,10 @@ func set_entries(value: Array, title: String) -> void:
|
||||
return
|
||||
entries = value.duplicate(true)
|
||||
heading = title
|
||||
display_rows.clear()
|
||||
for entry in entries:
|
||||
display_rows.append({"lines":bounded_lines(str(entry.get("label", "")),LABEL_WIDTH,LABEL_SIZE,2 if entry.get("equipment",false) else 3),
|
||||
"detail":bounded_lines(str(entry.get("detail", "")), LABEL_WIDTH, 18, 1) if entry.get("enabled",true) else []})
|
||||
_redraw()
|
||||
|
||||
|
||||
@@ -68,6 +147,43 @@ func _redraw() -> void:
|
||||
class WheelCanvas extends Control:
|
||||
var wheel: Node3D
|
||||
const CENTER := Vector2(360,353)
|
||||
var sectors: Array[PackedVector2Array] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
for i in range(8): sectors.append(_sector(i * TAU / 8.0))
|
||||
|
||||
|
||||
func _equipment_icon(kind: String, center: Vector2, color: Color) -> void:
|
||||
# Original vector silhouettes: no extracted sprite, new viewport or
|
||||
# mesh allocation. Shape + ammo caption distinguishes similar names.
|
||||
match kind:
|
||||
"beam":
|
||||
draw_line(center + Vector2(-24,8), center + Vector2(-13,-8), color, 4)
|
||||
draw_line(center + Vector2(-13,-8), center + Vector2(24,-8), color, 3)
|
||||
draw_line(center + Vector2(-13,-3), center + Vector2(24,-3), color, 1)
|
||||
"ion":
|
||||
draw_arc(center, 10, 0, TAU, 16, color, 3)
|
||||
draw_line(center + Vector2(-23,0), center + Vector2(-13,0), color, 3)
|
||||
draw_line(center + Vector2(13,0), center + Vector2(23,0), color, 3)
|
||||
"missile":
|
||||
draw_colored_polygon(PackedVector2Array([center + Vector2(-21,-6),center + Vector2(10,-6),center + Vector2(22,0),center + Vector2(10,6),center + Vector2(-21,6)]),color)
|
||||
draw_line(center + Vector2(-11,-11),center + Vector2(-11,11),color,3)
|
||||
"bomb":
|
||||
draw_circle(center + Vector2(-2,2),10,color)
|
||||
draw_line(center + Vector2(3,-7),center + Vector2(13,-13),color,3)
|
||||
draw_line(center + Vector2(13,-13),center + Vector2(20,-9),color,2)
|
||||
"flak":
|
||||
for offset in [Vector2(-17,3),Vector2(-3,-8),Vector2(11,5),Vector2(22,-7)]: draw_rect(Rect2(center + offset - Vector2(3,3),Vector2(6,6)),color)
|
||||
"laser":
|
||||
draw_rect(Rect2(center + Vector2(-23,-5),Vector2(20,10)),color)
|
||||
draw_line(center + Vector2(-3,0),center + Vector2(23,0),color,3)
|
||||
"empty":
|
||||
draw_line(center + Vector2(-12,0),center + Vector2(12,0),color,2)
|
||||
_:
|
||||
draw_rect(Rect2(center - Vector2(10,7),Vector2(20,14)),color,false,2)
|
||||
draw_line(center + Vector2(-23,0),center + Vector2(-10,0),color,3)
|
||||
draw_line(center + Vector2(10,0),center + Vector2(23,0),color,3)
|
||||
|
||||
|
||||
func _sector(angle: float) -> PackedVector2Array:
|
||||
@@ -87,28 +203,31 @@ class WheelCanvas extends Control:
|
||||
var enabled: bool = i < wheel.entries.size() and bool(wheel.entries[i].get("enabled",true))
|
||||
var active: bool = wheel.selected == i and enabled
|
||||
var angle := i * TAU / 8.0
|
||||
var outline := _sector(angle)
|
||||
var outline := sectors[i]
|
||||
draw_colored_polygon(outline,UI.SELECTED if active else UI.PANEL if enabled else UI.DARK)
|
||||
outline.append(outline[0])
|
||||
draw_polyline(outline,UI.GOLD if active else UI.EDGE if enabled else UI.MUTED,1.5,false)
|
||||
draw_line(outline[outline.size() - 1],outline[0],UI.GOLD if active else UI.EDGE if enabled else UI.MUTED,1.5)
|
||||
var center := CENTER + Vector2(sin(angle),-cos(angle)) * 225
|
||||
UI.centered_text(self,str(i + 1),center + Vector2(0,-22),38,UI.GOLD if active else UI.GREEN if enabled else UI.MUTED,"header")
|
||||
var label := str(wheel.entries[i].get("label","")) if i < wheel.entries.size() else "-"
|
||||
var lines := label.replace("\n"," ").split(" ",false)
|
||||
var current := ""
|
||||
var wrapped: Array[String] = []
|
||||
for word in lines:
|
||||
if current.length() + word.length() + 1 > 12 and current != "":
|
||||
wrapped.append(current)
|
||||
current = ""
|
||||
current = word if current == "" else current + " " + word
|
||||
if current != "": wrapped.append(current)
|
||||
for line in range(mini(3,wrapped.size())):
|
||||
UI.centered_text(self,wrapped[line],center + Vector2(0,11 + line * 21),24,UI.INK if enabled else UI.MUTED)
|
||||
var entry: Dictionary = wheel.entries[i] if i < wheel.entries.size() else {}
|
||||
var equipment := bool(entry.get("equipment", false))
|
||||
UI.centered_text(self,str(i + 1),center + Vector2(0,-55 if equipment else -22),38,UI.GOLD if active else UI.GREEN if enabled else UI.MUTED,"header")
|
||||
if equipment: _equipment_icon(str(entry.get("icon_kind", "empty")),center + Vector2(0,-20),UI.GOLD if active else UI.GREEN if enabled else UI.MUTED)
|
||||
var row: Dictionary = wheel.display_rows[i] if i < wheel.display_rows.size() else {"lines":["-"],"detail":[]}
|
||||
var wrapped: Array = row.lines
|
||||
for line in range(wrapped.size()):
|
||||
UI.centered_text(self,str(wrapped[line]),center + Vector2(0,9 + line * 22),LABEL_SIZE,UI.INK if enabled else UI.MUTED)
|
||||
if equipment and not row.detail.is_empty():
|
||||
UI.centered_text(self,str(row.detail[0]),center + Vector2(0,56),18,UI.GOLD if enabled else UI.MUTED)
|
||||
UI.frame(self,Rect2(CENTER - Vector2(102,56),Vector2(204,112)),UI.DARK,UI.EDGE,17)
|
||||
var titles := str(wheel.heading).split("\n")
|
||||
for i in range(titles.size()):
|
||||
UI.centered_text(self,titles[i],CENTER + Vector2(0,(i - (titles.size() - 1) * .5) * 29),25,UI.INK,"header")
|
||||
UI.frame(self,Rect2(38,700,644,88),UI.DARK,UI.EDGE,12)
|
||||
UI.centered_text(self,"STICK: CHOOSE RELEASE BUMPER: CONFIRM",Vector2(360,727),24,UI.INK)
|
||||
UI.centered_text(self,"A: CATEGORY B / CENTER: CANCEL",Vector2(360,760),24,UI.MUTED)
|
||||
var selected: Dictionary = wheel.entries[wheel.selected] if wheel.selected >= 0 and wheel.selected < wheel.entries.size() else {}
|
||||
var caption := str(selected.get("label", "STICK: CHOOSE RELEASE BUMPER: CONFIRM"))
|
||||
if selected.get("equipment",false) and selected.get("enabled",true): caption += " / " + str(selected.get("detail",""))
|
||||
var caption_lines: Array = wheel.bounded_lines(caption,608,23,1)
|
||||
UI.centered_text(self,str(caption_lines[0]) if not caption_lines.is_empty() else "",Vector2(360,716),23,UI.INK)
|
||||
UI.centered_text(self,"A: CATEGORY B / CENTER: CANCEL TRIGGER: CONFIRM",Vector2(360,745),20,UI.MUTED)
|
||||
var hint := "SHIFT: DEPOWER CTRL: REVERSE AUTOFIRE" if bool(selected.get("weapon", false)) else "SHIFT: DEPOWER RELEASE BUMPER: CONFIRM" if bool(selected.get("equipment", false)) else "RELEASE BUMPER: CONFIRM"
|
||||
UI.centered_text(self,hint,Vector2(360,772),20,UI.GOLD)
|
||||
Reference in new issue
Block a user