Initial commit
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
extends Control
|
||||
## Look preview + culture/job lore. Compact grid. No Dock Chaplain.
|
||||
|
||||
var name_input: LineEdit
|
||||
var background_list: Control
|
||||
var confirm_button: Button
|
||||
var back_button: Button
|
||||
var description_label: Label
|
||||
var bonus_label: Label
|
||||
var culture_list: Control
|
||||
var look_world: Node3D
|
||||
var preview_cam: Camera3D
|
||||
var sex_male: Button
|
||||
var sex_female: Button
|
||||
var name_head: Label
|
||||
var name_body: Label
|
||||
var name_legs: Label
|
||||
var name_feet: Label
|
||||
|
||||
const MALE_FBX := "res://character assets/All together/FBX/Humans_Master.fbx"
|
||||
const FEMALE_FBX := "res://character assets/All together/FBX women/All together.fbx"
|
||||
|
||||
const MALE_HEADS := ["Casual_Head", "Casual2_Head", "Worker_Head", "Farmer_Head", "Adventurer_Head", "Punk_Head", "Suit_Head", "Swat_Head", "SpaceSuit_Head"]
|
||||
const MALE_BODIES := ["Casual_Body", "Casual2_Body", "Worker_Body", "Farmer_Body", "Adventurer_Body", "Punk_Body", "Suit_Body", "Swat_Body", "SpaceSuit_Body"]
|
||||
const MALE_LEGS := ["Casual_Legs", "Casual2_Legs", "Worker_Legs", "Farmer_Pants", "Adventurer_Legs", "Punk_Legs", "Suit_Legs", "Swat_Legs", "SpaceSuit_Legs"]
|
||||
const MALE_FEET := ["Casual_Feet", "Casual2_Feet", "Worker_Feet", "Farmer_Feet", "Adventurer_Feet", "Punk_Feet", "Suit_Feet", "Swat_Feet", "SpaceSuit_Feet"]
|
||||
const FEMALE_HEADS := ["Casual_Head", "Worker_Head", "Suit_Head", "Formal_Head", "Formad_Head", "Adventurer_Head", "Punk_Head", "Soldier_Head", "SciFi_Head"]
|
||||
const FEMALE_BODIES := ["Casual_Body", "Worker_Body", "Suit_Body", "Formal_Body", "Adventurer_Body", "Punk_Body", "Soldier_Body", "SciFi_Body"]
|
||||
const FEMALE_LEGS := ["Casual_Legs", "Worker_Legs", "Suit_Legs", "Formal_Legs", "Adventurer_Legs", "Punk_Legs", "Soldier_Legs", "SciFi_Legs"]
|
||||
const FEMALE_FEET := ["Casual_Feet", "Worker_Feet", "Suit_Feet", "Formal_Feet", "Adventurer_Feet", "Punk_Feet", "Soldier_Feet", "SciFi_Feet"]
|
||||
|
||||
var sex: String = "male"
|
||||
var idx := {"head": 0, "body": 0, "legs": 0, "feet": 0}
|
||||
var preview_male: Node3D = null
|
||||
var preview_female: Node3D = null
|
||||
|
||||
var cultures := [
|
||||
{"id": "naf", "name": "North American", "desc": "You grew up under the NAF eagle — Mexico to Panama welded into one machine. English and Spanish in the same breath, carriers in the black, and a simple article of faith: the hemisphere holds, or nothing does."},
|
||||
{"id": "eaa", "name": "East Asian", "desc": "EAA orthodoxy: long plans, short mercy. Beijing's shadow, Korean steel, Vietnamese patience. You learned early that the loudest claim in the room is rarely the one that survives."},
|
||||
{"id": "european", "name": "European", "desc": "Old flags, thin coalitions, and the sound of Russia testing the locks. Paris, Berlin, London, Warsaw — still arguing while the sky hardens. You left with a ticket and no illusions about unity."},
|
||||
{"id": "russian", "name": "Russian", "desc": "Victory parades and rationed truth. Ukraine was the lesson; the Baltics are the rumor that keeps generals warm. Orthodox steel, long winters, and a state that still treats the map as unfinished business."},
|
||||
{"id": "brazilian", "name": "Brazilian", "desc": "Southern empire blood. Industry, orbit yards, and a continent that refuses to kneel to the north. You carry the heat of a country that builds its own guns and names its own heroes."},
|
||||
{"id": "independent", "name": "Independent", "desc": "No anthem owns you. Station brats, belt orphans, passport ghosts — you answer to contracts, crew, and the thin margin between air and vacuum."}
|
||||
]
|
||||
|
||||
var backgrounds := [
|
||||
{"id": "dock_worker", "name": "Dock Worker", "desc": "Hard vacuum on one side of the hatch, warehouse stink on the other. You stacked the mass that keeps Young alive and learned every lie a shipping manifest can tell.", "bonuses": {"money": 90}},
|
||||
{"id": "junior_engineer", "name": "Junior Engineer", "desc": "When the lights brown out, they call you. Breakers, scrubbers, jury-rigged miracles — you keep the arcology's pulse steady with a multitool and bad coffee.", "bonuses": {"money": 100}},
|
||||
{"id": "med_tech", "name": "Med Tech", "desc": "Blood on the deck plating, timers on the monitors. You stabilize what the station breaks and don't ask whether the patient salutes the same flag you do.", "bonuses": {"money": 95, "health": 10}},
|
||||
{"id": "smuggler", "name": "Smuggler", "desc": "Customs is a suggestion; the real map is drawn in debts. You moved what official channels refused to name — and you got paid in silence and credits.", "bonuses": {"money": 120}},
|
||||
{"id": "security_cadet", "name": "Security Cadet", "desc": "Badge, route, rules of engagement written by people who sleep in better modules. You've already learned how fast procedure burns when someone runs.", "bonuses": {"money": 85, "health": 5}},
|
||||
{"id": "botanist", "name": "Botanist", "desc": "Oxygen has a green face. You worked the trays and towers that turn waste into breath — the quietest strategic asset on the Moon.", "bonuses": {"money": 80, "food": 15}},
|
||||
{"id": "skiff_pilot", "name": "Skiff Pilot", "desc": "Low and fast over dead regolith. Hop routes, run supplies, and trust the aerospace skills that keep you off the crater floor.", "bonuses": {"money": 105}},
|
||||
{"id": "life_support_tech", "name": "Life Support Tech", "desc": "Pressure, CO₂, water loops — the boring systems that kill everyone when they fail. You listened to the station the way a doctor listens to a chest.", "bonuses": {"money": 95}},
|
||||
{"id": "university_aide", "name": "University Aide", "desc": "Tsiolkovsky Hall: fusion on the chalkboard, rumors in the aisle. Dr Ernesto talks brachistochrones like scripture. You fetched the data and heard the future early.", "bonuses": {"money": 75}},
|
||||
{"id": "unemployed", "name": "Unemployed", "desc": "No contract, no module lease, no one to vouch. Just a name on a transit list and a short window to become necessary.", "bonuses": {"money": 40}}
|
||||
]
|
||||
|
||||
var selected_culture: Dictionary = {}
|
||||
var selected_background: Dictionary = {}
|
||||
var background_buttons: Array[Button] = []
|
||||
var culture_buttons: Array[Button] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
name_input = _find("NameInput") as LineEdit
|
||||
background_list = _find("BackgroundL") as Control
|
||||
if background_list == null:
|
||||
background_list = _find("BackgroundList") as Control
|
||||
confirm_button = _find("ConfirmButton") as Button
|
||||
back_button = _find("BackButton") as Button
|
||||
description_label = _find("DescriptionL") as Label
|
||||
if description_label == null:
|
||||
description_label = _find("DescriptionLabel") as Label
|
||||
bonus_label = _find("BonusLabel") as Label
|
||||
culture_list = _find("CultureList") as Control
|
||||
look_world = _find("LookWorld") as Node3D
|
||||
preview_cam = _find("LookCam") as Camera3D
|
||||
sex_male = _find("SexMale") as Button
|
||||
sex_female = _find("SexFemale") as Button
|
||||
name_head = _find("Name_Head") as Label
|
||||
name_body = _find("Name_Body") as Label
|
||||
name_legs = _find("Name_Legs") as Label
|
||||
name_feet = _find("Name_Feet") as Label
|
||||
var vp := _find("SubViewport") as SubViewport
|
||||
if vp:
|
||||
vp.own_world_3d = true
|
||||
vp.transparent_bg = false
|
||||
vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
if description_label:
|
||||
description_label.visible = true
|
||||
description_label.add_theme_font_size_override("font_size", 14)
|
||||
description_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
description_label.custom_minimum_size = Vector2(0, 88)
|
||||
description_label.max_lines_visible = 8
|
||||
description_label.clip_text = false
|
||||
description_label.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
if bonus_label:
|
||||
bonus_label.visible = true
|
||||
bonus_label.add_theme_font_size_override("font_size", 14)
|
||||
bonus_label.custom_minimum_size = Vector2(0, 22)
|
||||
bonus_label.max_lines_visible = 2
|
||||
bonus_label.clip_text = false
|
||||
if confirm_button:
|
||||
confirm_button.pressed.connect(_on_confirm)
|
||||
if back_button:
|
||||
back_button.pressed.connect(_on_back)
|
||||
if sex_male:
|
||||
sex_male.pressed.connect(func() -> void: _set_sex("male"))
|
||||
if sex_female:
|
||||
sex_female.pressed.connect(func() -> void: _set_sex("female"))
|
||||
_wire_cycle("Prev_Head", "Next_Head", "head")
|
||||
_wire_cycle("Prev_Body", "Next_Body", "body")
|
||||
_wire_cycle("Prev_Legs", "Next_Legs", "legs")
|
||||
_wire_cycle("Prev_Feet", "Next_Feet", "feet")
|
||||
if culture_list is GridContainer:
|
||||
(culture_list as GridContainer).columns = 3
|
||||
if background_list is GridContainer:
|
||||
(background_list as GridContainer).columns = 2
|
||||
_wire_culture_buttons()
|
||||
_spawn_previews()
|
||||
_build_background_buttons()
|
||||
if cultures.size() > 0:
|
||||
_select_culture(cultures[0])
|
||||
if backgrounds.size() > 0:
|
||||
_select_background(backgrounds[0])
|
||||
_set_sex("male")
|
||||
|
||||
|
||||
func _find(n: String) -> Node:
|
||||
return find_child(n, true, false)
|
||||
|
||||
|
||||
func _size_grid_button(btn: Button) -> void:
|
||||
btn.custom_minimum_size = Vector2(0, 24)
|
||||
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
btn.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
|
||||
|
||||
func _wire_cycle(prev_name: String, next_name: String, slot: String) -> void:
|
||||
var prev_b := _find(prev_name) as Button
|
||||
var next_b := _find(next_name) as Button
|
||||
if prev_b:
|
||||
prev_b.pressed.connect(func() -> void: _cycle(slot, -1))
|
||||
if next_b:
|
||||
next_b.pressed.connect(func() -> void: _cycle(slot, 1))
|
||||
|
||||
|
||||
func _wire_culture_buttons() -> void:
|
||||
culture_buttons.clear()
|
||||
if culture_list == null:
|
||||
return
|
||||
var map := {
|
||||
"Culture_NA": "naf", "Culture_NAF": "naf",
|
||||
"Culture_EA": "eaa", "Culture_EAA": "eaa",
|
||||
"Culture_Eur": "european", "Culture_European": "european",
|
||||
"Culture_Ru": "russian", "Culture_Russian": "russian",
|
||||
"Culture_Br": "brazilian", "Culture_Brazilian": "brazilian",
|
||||
"Culture_In": "independent", "Culture_Independent": "independent",
|
||||
}
|
||||
for child in culture_list.get_children():
|
||||
if not (child is Button):
|
||||
continue
|
||||
var btn := child as Button
|
||||
var cid: String = str(map.get(btn.name, ""))
|
||||
if cid.is_empty():
|
||||
continue
|
||||
var data: Dictionary = {}
|
||||
for c in cultures:
|
||||
if c["id"] == cid:
|
||||
data = c
|
||||
break
|
||||
if data.is_empty():
|
||||
continue
|
||||
btn.toggle_mode = true
|
||||
_size_grid_button(btn)
|
||||
btn.pressed.connect(_on_culture_pressed.bind(data, btn))
|
||||
culture_buttons.append(btn)
|
||||
|
||||
|
||||
func _on_culture_pressed(data: Dictionary, btn: Button) -> void:
|
||||
_select_culture(data)
|
||||
for b in culture_buttons:
|
||||
b.button_pressed = (b == btn)
|
||||
|
||||
|
||||
func _select_culture(data: Dictionary) -> void:
|
||||
selected_culture = data
|
||||
_refresh_desc()
|
||||
|
||||
|
||||
func _build_background_buttons() -> void:
|
||||
if background_list == null:
|
||||
push_error("BackgroundL / BackgroundList missing — jobs cannot spawn")
|
||||
return
|
||||
for child in background_list.get_children():
|
||||
child.queue_free()
|
||||
background_buttons.clear()
|
||||
for bg in backgrounds:
|
||||
var btn := Button.new()
|
||||
btn.text = str(bg["name"])
|
||||
btn.toggle_mode = true
|
||||
_size_grid_button(btn)
|
||||
btn.pressed.connect(_on_background_pressed.bind(bg, btn))
|
||||
background_list.add_child(btn)
|
||||
background_buttons.append(btn)
|
||||
|
||||
|
||||
func _on_background_pressed(bg: Dictionary, btn: Button) -> void:
|
||||
_select_background(bg)
|
||||
for b in background_buttons:
|
||||
b.button_pressed = (b == btn)
|
||||
|
||||
|
||||
func _select_background(bg: Dictionary) -> void:
|
||||
selected_background = bg
|
||||
_refresh_desc()
|
||||
if bonus_label:
|
||||
var bonuses: Dictionary = bg.get("bonuses", {})
|
||||
var money: int = int(bonuses.get("money", 0))
|
||||
var extra: PackedStringArray = []
|
||||
if bonuses.has("food"):
|
||||
extra.append("Food +%s" % str(bonuses["food"]))
|
||||
if bonuses.has("health"):
|
||||
extra.append("Health +%s" % str(bonuses["health"]))
|
||||
var extra_txt := (", " + ", ".join(extra)) if extra.size() > 0 else ""
|
||||
bonus_label.text = "Starting bonus: %d credits%s" % [money, extra_txt]
|
||||
|
||||
|
||||
func _refresh_desc() -> void:
|
||||
var parts: PackedStringArray = []
|
||||
if not selected_culture.is_empty():
|
||||
parts.append(str(selected_culture.get("desc", "")))
|
||||
if not selected_background.is_empty():
|
||||
parts.append(str(selected_background.get("desc", "")))
|
||||
var body := "\n\n".join(parts)
|
||||
if description_label:
|
||||
description_label.visible = true
|
||||
description_label.text = body
|
||||
if bonus_label == null and description_label and not selected_background.is_empty():
|
||||
var bonuses: Dictionary = selected_background.get("bonuses", {})
|
||||
var money: int = int(bonuses.get("money", 0))
|
||||
description_label.text = body + "\n\nStarting bonus: %d credits" % money
|
||||
|
||||
|
||||
func _spawn_previews() -> void:
|
||||
if look_world == null:
|
||||
push_warning("LookWorld missing — Look preview will be empty")
|
||||
return
|
||||
_ensure_look_stage()
|
||||
preview_male = _instance_fbx(MALE_FBX)
|
||||
preview_female = _instance_fbx(FEMALE_FBX)
|
||||
if preview_male:
|
||||
preview_male.name = "PreviewMale"
|
||||
look_world.add_child(preview_male)
|
||||
if preview_female:
|
||||
preview_female.name = "PreviewFemale"
|
||||
look_world.add_child(preview_female)
|
||||
print("Look spawn male=", preview_male, " female=", preview_female, " cam=", preview_cam)
|
||||
if preview_cam:
|
||||
preview_cam.current = true
|
||||
preview_cam.position = Vector3(0.0, 1.08, 5.4)
|
||||
preview_cam.rotation_degrees = Vector3(-4.0, 0.0, 0.0)
|
||||
preview_cam.fov = 34.0
|
||||
|
||||
|
||||
func _ensure_look_stage() -> void:
|
||||
if look_world.get_node_or_null("LookFill") == null:
|
||||
var fill := OmniLight3D.new()
|
||||
fill.name = "LookFill"
|
||||
fill.light_energy = 1.6
|
||||
fill.omni_range = 14.0
|
||||
fill.position = Vector3(0.5, 2.0, 2.2)
|
||||
look_world.add_child(fill)
|
||||
if look_world.get_node_or_null("LookEnv") == null:
|
||||
var we := WorldEnvironment.new()
|
||||
we.name = "LookEnv"
|
||||
var env := Environment.new()
|
||||
env.background_mode = Environment.BG_COLOR
|
||||
env.background_color = Color(0.10, 0.11, 0.13)
|
||||
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
env.ambient_light_color = Color(0.78, 0.78, 0.82)
|
||||
env.ambient_light_energy = 1.0
|
||||
we.environment = env
|
||||
look_world.add_child(we)
|
||||
|
||||
|
||||
func _instance_fbx(path: String) -> Node3D:
|
||||
if not ResourceLoader.exists(path):
|
||||
push_warning("Missing character file: " + path)
|
||||
return null
|
||||
var res = load(path)
|
||||
if res == null:
|
||||
return null
|
||||
var inst: Node = res.instantiate()
|
||||
if inst is Node3D:
|
||||
(inst as Node3D).position = Vector3.ZERO
|
||||
(inst as Node3D).rotation_degrees = Vector3.ZERO
|
||||
(inst as Node3D).scale = Vector3.ONE
|
||||
return inst as Node3D
|
||||
return null
|
||||
|
||||
|
||||
func _lists() -> Dictionary:
|
||||
if sex == "male":
|
||||
return {"head": MALE_HEADS, "body": MALE_BODIES, "legs": MALE_LEGS, "feet": MALE_FEET}
|
||||
return {"head": FEMALE_HEADS, "body": FEMALE_BODIES, "legs": FEMALE_LEGS, "feet": FEMALE_FEET}
|
||||
|
||||
|
||||
func _active_preview() -> Node3D:
|
||||
return preview_male if sex == "male" else preview_female
|
||||
|
||||
|
||||
func _set_sex(s: String) -> void:
|
||||
sex = s
|
||||
idx = {"head": 0, "body": 0, "legs": 0, "feet": 0}
|
||||
if preview_male:
|
||||
preview_male.visible = (s == "male")
|
||||
if preview_female:
|
||||
preview_female.visible = (s == "female")
|
||||
_apply_look()
|
||||
|
||||
|
||||
func _cycle(slot: String, dir: int) -> void:
|
||||
var arr: Array = _lists()[slot]
|
||||
if arr.is_empty():
|
||||
return
|
||||
idx[slot] = posmod(idx[slot] + dir, arr.size())
|
||||
_apply_look()
|
||||
|
||||
|
||||
func _apply_look() -> void:
|
||||
var root := _active_preview()
|
||||
if root == null:
|
||||
return
|
||||
var lists := _lists()
|
||||
var keep: Array[String] = [
|
||||
str(lists["head"][idx["head"]]),
|
||||
str(lists["body"][idx["body"]]),
|
||||
str(lists["legs"][idx["legs"]]),
|
||||
str(lists["feet"][idx["feet"]]),
|
||||
]
|
||||
_hide_all_geometry(root)
|
||||
for mesh_name in keep:
|
||||
_show_mesh(root, mesh_name)
|
||||
_hide_other_slots(root, keep)
|
||||
if name_head:
|
||||
name_head.text = lists["head"][idx["head"]].replace("_Head", "").replace("Formad", "Formal")
|
||||
if name_body:
|
||||
name_body.text = lists["body"][idx["body"]].replace("_Body", "")
|
||||
if name_legs:
|
||||
name_legs.text = lists["legs"][idx["legs"]].replace("_Legs", "").replace("_Pants", "")
|
||||
if name_feet:
|
||||
name_feet.text = lists["feet"][idx["feet"]].replace("_Feet", "")
|
||||
_play_idle(root)
|
||||
|
||||
|
||||
func _hide_all_geometry(root: Node) -> void:
|
||||
var stack: Array[Node] = [root]
|
||||
while stack.size() > 0:
|
||||
var n: Node = stack.pop_back()
|
||||
if n is VisualInstance3D:
|
||||
n.visible = false
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
|
||||
|
||||
func _show_mesh(root: Node, mesh_name: String) -> void:
|
||||
var node := root.find_child(mesh_name, true, false)
|
||||
if node == null:
|
||||
return
|
||||
_show_tree(node)
|
||||
|
||||
|
||||
func _show_tree(n: Node) -> void:
|
||||
n.visible = true
|
||||
if n is VisualInstance3D:
|
||||
n.visible = true
|
||||
for c in n.get_children():
|
||||
_show_tree(c)
|
||||
|
||||
|
||||
func _hide_other_slots(root: Node, keep: Array[String]) -> void:
|
||||
var stack: Array[Node] = [root]
|
||||
while stack.size() > 0:
|
||||
var n: Node = stack.pop_back()
|
||||
if _is_slot_root(n.name) and keep.find(n.name) == -1:
|
||||
_hide_tree(n)
|
||||
else:
|
||||
for c in n.get_children():
|
||||
stack.append(c)
|
||||
|
||||
|
||||
func _hide_tree(n: Node) -> void:
|
||||
n.visible = false
|
||||
if n is VisualInstance3D:
|
||||
n.visible = false
|
||||
for c in n.get_children():
|
||||
_hide_tree(c)
|
||||
|
||||
|
||||
func _is_slot_root(mesh_name: String) -> bool:
|
||||
return (
|
||||
mesh_name.ends_with("_Head")
|
||||
or mesh_name.ends_with("_Body")
|
||||
or mesh_name.ends_with("_Legs")
|
||||
or mesh_name.ends_with("_Feet")
|
||||
or mesh_name.ends_with("_Pants")
|
||||
)
|
||||
|
||||
|
||||
func _play_idle(root: Node) -> void:
|
||||
var anim: AnimationPlayer = root.find_child("AnimationPlayer", true, false)
|
||||
if anim == null:
|
||||
return
|
||||
for clip in ["CharacterArmature|Idle", "Idle"]:
|
||||
if anim.has_animation(clip):
|
||||
anim.play(clip)
|
||||
return
|
||||
|
||||
|
||||
func _look_payload() -> Dictionary:
|
||||
var lists := _lists()
|
||||
return {
|
||||
"sex": sex,
|
||||
"head": lists["head"][idx["head"]],
|
||||
"body": lists["body"][idx["body"]],
|
||||
"legs": lists["legs"][idx["legs"]],
|
||||
"feet": lists["feet"][idx["feet"]],
|
||||
}
|
||||
|
||||
|
||||
func _on_confirm() -> void:
|
||||
var player_name := "Colonist"
|
||||
if name_input:
|
||||
player_name = name_input.text.strip_edges()
|
||||
if player_name.is_empty():
|
||||
player_name = "Colonist"
|
||||
if selected_background.is_empty() and backgrounds.size() > 0:
|
||||
selected_background = backgrounds[0]
|
||||
if selected_culture.is_empty() and cultures.size() > 0:
|
||||
selected_culture = cultures[0]
|
||||
var data := {
|
||||
"player_name": player_name,
|
||||
"appearance": _look_payload(),
|
||||
"culture_id": selected_culture.get("id", ""),
|
||||
"culture_name": selected_culture.get("name", ""),
|
||||
"background_id": selected_background.get("id", ""),
|
||||
"background_name": selected_background.get("name", ""),
|
||||
"bonuses": selected_background.get("bonuses", {}),
|
||||
}
|
||||
Engine.set_meta("player_creation_data", data)
|
||||
print("Creation packed: ", data)
|
||||
get_tree().change_scene_to_file("res://Scenes/main.tscn")
|
||||
|
||||
|
||||
func _on_back() -> void:
|
||||
get_tree().change_scene_to_file("res://Scenes/main_menu.tscn")
|
||||
Reference in New Issue
Block a user