316 lines
13 KiB
GDScript
316 lines
13 KiB
GDScript
extends Control
|
|
signal finished(success: bool)
|
|
const UI := preload("res://scripts/planets/expedition_ui.gd")
|
|
const GENERATOR := preload("res://scripts/planets/surface_generator.gd")
|
|
var captain: Resource
|
|
var progress: Resource
|
|
var world: Node2D
|
|
var lander: Sprite2D
|
|
var camera: Camera2D
|
|
var _hp: ProgressBar
|
|
var _cargo: Label
|
|
var _status: Label
|
|
var _dialog: PanelContainer
|
|
var _choices: VBoxContainer
|
|
var _poi: Dictionary = {}
|
|
var _rng := RandomNumberGenerator.new()
|
|
var _motion := preload("res://scripts/planets/lander_motion.gd").new()
|
|
var flight_ship: Node
|
|
var _ending := false
|
|
var _hud_refresh_time := 0.0
|
|
var _previous_position := Vector2.ZERO
|
|
var _active_event: Dictionary = {}
|
|
var _event_armed := true
|
|
const SURFACE_RENDER_SCALE := 1.5
|
|
const SURFACE_CAMERA_ZOOM := 0.8
|
|
var _surface_viewport: SubViewport
|
|
|
|
func _ready() -> void:
|
|
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_rng.seed = progress.visit_seed ^ 90317
|
|
if is_instance_valid(flight_ship):
|
|
_motion.configure(flight_ship)
|
|
var container := TextureRect.new()
|
|
container.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
container.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
container.stretch_mode = TextureRect.STRETCH_SCALE
|
|
container.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
|
container.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(container)
|
|
var viewport := SubViewport.new()
|
|
_surface_viewport = viewport
|
|
viewport.size = Vector2i(get_viewport_rect().size * SURFACE_RENDER_SCALE)
|
|
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
viewport.world_2d = World2D.new()
|
|
container.add_child(viewport)
|
|
container.texture = viewport.get_texture()
|
|
world = Node2D.new()
|
|
world.set_script(preload("res://scripts/planets/surface_world.gd"))
|
|
world.data = GENERATOR.generate(progress.visit_seed, progress.active_profile)
|
|
viewport.add_child(world)
|
|
lander = Sprite2D.new()
|
|
lander.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON
|
|
lander.texture = preload("res://assets/planets/lander.png")
|
|
lander.scale = Vector2.ONE * 100.0 / lander.texture.get_width()
|
|
lander.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS
|
|
var sprite_material := ShaderMaterial.new()
|
|
sprite_material.shader = preload("res://resources/effects/lander_detail.gdshader")
|
|
lander.material = sprite_material
|
|
lander.z_index = 30
|
|
viewport.add_child(lander)
|
|
lander.reset_physics_interpolation()
|
|
var engine := Node2D.new()
|
|
engine.name = "EngineVisual"
|
|
engine.set_script(preload("res://scripts/planets/lander_engine_visual.gd"))
|
|
engine.source = self
|
|
engine.scale = Vector2.ONE / lander.scale
|
|
engine.z_index = -1
|
|
lander.add_child(engine)
|
|
var audio := AudioStreamPlayer.new()
|
|
audio.name = "EngineAudio"
|
|
audio.set_script(preload("res://scripts/game/ship_engine_audio.gd"))
|
|
audio.thrust_source = self
|
|
add_child(audio)
|
|
camera = Camera2D.new()
|
|
camera.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
|
|
camera.process_callback = Camera2D.CAMERA2D_PROCESS_IDLE
|
|
camera.zoom = Vector2.ONE * SURFACE_CAMERA_ZOOM * SURFACE_RENDER_SCALE
|
|
camera.position_smoothing_enabled = true
|
|
camera.position_smoothing_speed = 6
|
|
camera.limit_left = -1920
|
|
camera.limit_top = -1920
|
|
camera.limit_right = 1920
|
|
camera.limit_bottom = 1920
|
|
viewport.add_child(camera)
|
|
resized.connect(_resize_surface_viewport)
|
|
var info := UI.panel(self, Vector2(440, 0))
|
|
info.position = Vector2(24, 24)
|
|
info.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
var column := VBoxContainer.new()
|
|
info.add_child(column)
|
|
UI.label(column, str(progress.active_profile.name).to_upper(), 25, UI.CYAN)
|
|
UI.label(column,str(world.data.landscape.name),14,Color("e4cc9d"))
|
|
UI.label(column, "%s / TLEN %d%% / %d°C / FLORA %d" % [
|
|
preload("res://scripts/planets/planet_profiles.gd").biome_name(progress.active_profile),
|
|
progress.active_profile.oxygen, progress.active_profile.temperature, progress.active_profile.flora], 13)
|
|
_hp = ProgressBar.new()
|
|
_hp.custom_minimum_size = Vector2(0, 14)
|
|
_hp.show_percentage = false
|
|
var hp_style := StyleBoxFlat.new()
|
|
hp_style.bg_color = Color("5bc9b5")
|
|
hp_style.set_corner_radius_all(5)
|
|
_hp.add_theme_stylebox_override("fill",hp_style)
|
|
column.add_child(_hp)
|
|
_status = UI.label(column, "", 13)
|
|
_cargo = UI.label(column, "", 13)
|
|
_cargo.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
_cargo.custom_minimum_size.x = 410
|
|
var back := UI.button(self, "POWRÓT NA STATEK [L]", func(): _return(true))
|
|
back.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
|
back.position = Vector2(get_viewport_rect().size.x-285, 24)
|
|
back.size.x = 260
|
|
var help := UI.panel(self)
|
|
help.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
|
help.position = Vector2(24, get_viewport_rect().size.y-84)
|
|
UI.label(help, "W — ciąg · S — hamuj / wstecz · A/D — obrót\nQ/E — ruch boczny · TAB — dopalanie · F — zbadaj", 14)
|
|
var map_frame := UI.panel(self, Vector2(195,195))
|
|
map_frame.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
|
map_frame.position = get_viewport_rect().size-Vector2(220,220)
|
|
var minimap := Control.new()
|
|
minimap.set_script(preload("res://scripts/planets/surface_minimap.gd"))
|
|
minimap.world = world
|
|
minimap.lander = lander
|
|
map_frame.add_child(minimap)
|
|
_dialog = UI.panel(self, Vector2(460,0))
|
|
_dialog.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
|
_dialog.position = Vector2(get_viewport_rect().size.x-490,get_viewport_rect().size.y*0.5-100)
|
|
_choices = VBoxContainer.new()
|
|
_choices.add_theme_constant_override("separation", 12)
|
|
_dialog.add_child(_choices)
|
|
_dialog.hide()
|
|
_update_hud()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if _ending:
|
|
return
|
|
_previous_position = lander.position
|
|
if _dialog.visible:
|
|
_motion.velocity = Vector2.ZERO
|
|
_motion.angular_velocity = 0
|
|
return
|
|
_motion.step(delta, Input.get_axis("ship_turn_left","ship_turn_right"),
|
|
Input.is_action_pressed("ship_thrust"), Input.is_action_pressed("ship_brake"),
|
|
Input.get_axis("ship_strafe_left","ship_strafe_right"), Input.is_action_pressed("ship_mini_boost"))
|
|
lander.rotation = _motion.rotation
|
|
var next := lander.position + _motion.velocity * delta
|
|
var bounded := next.clamp(Vector2(-1780,-1780),Vector2(1780,1780))
|
|
if next.x != bounded.x:
|
|
_motion.velocity.x = 0
|
|
if next.y != bounded.y:
|
|
_motion.velocity.y = 0
|
|
lander.position = bounded
|
|
|
|
func _process(delta: float) -> void:
|
|
if _ending:
|
|
return
|
|
# Camera and sprite must follow the same interpolated pose between physics ticks.
|
|
camera.position = _previous_position.lerp(lander.position, Engine.get_physics_interpolation_fraction())
|
|
_hud_refresh_time += delta
|
|
if _hud_refresh_time >= 0.1:
|
|
_hud_refresh_time = 0
|
|
world.refresh_pois(lander.position)
|
|
_update_hud()
|
|
_check_events()
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if not event is InputEventKey or not event.pressed or event.echo or _ending:
|
|
return
|
|
if event.is_action("ship_mini_boost"):
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
if event.keycode == KEY_L:
|
|
_return(true)
|
|
elif event.keycode == KEY_ESCAPE:
|
|
if _dialog.visible:
|
|
_dialog.hide()
|
|
else:
|
|
_return(true)
|
|
elif event.keycode == KEY_F and not _dialog.visible:
|
|
for encounter in world.data.events:
|
|
if not encounter.complete and lander.position.distance_to(encounter.position) <= 180:
|
|
_event_armed = false
|
|
_show_event(encounter)
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
for poi in world.data.pois:
|
|
if not poi.complete and lander.position.distance_to(poi.position) <= 150:
|
|
_poi = poi
|
|
_show_poi()
|
|
break
|
|
else:
|
|
return
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func _show_poi(message := "") -> void:
|
|
for child in _choices.get_children():
|
|
_choices.remove_child(child)
|
|
child.queue_free()
|
|
_dialog.show()
|
|
var names := {"mine":"OPUSZCZONA KOPALNIA", "ruins":"STAROŻYTNE RUINY", "deposit":"ZŁOŻE MINERAŁÓW"}
|
|
UI.label(_choices, names[_poi.kind], 22, UI.CYAN)
|
|
var description := UI.label(_choices, "Każdy kolejny etap daje więcej łupu, ale zwiększa ryzyko.\nNieudana próba uszkadza lądownik i kończy badanie miejsca.", 14)
|
|
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
description.custom_minimum_size.x = 410
|
|
if not message.is_empty():
|
|
var result := UI.label(_choices, message, 15, Color("#efca8a"))
|
|
result.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
result.custom_minimum_size.x = 410
|
|
if not _poi.complete:
|
|
var chance: float = preload("res://scripts/planets/planet_profiles.gd").success_chance(progress.active_profile, captain, _poi.stage)
|
|
UI.button(_choices, "%s · %d%% szans" % [{"mine":["Poziom 1 — wejście","Poziom 2 — głębszy szyb","Poziom 3 — najgłębsza komora"],"ruins":["Zbadaj dziedziniec","Otwórz komorę","Uruchom rdzeń"],"deposit":["Zbierz powierzchniowe złoże","Wykonaj odwiert","Wydobądź głęboką żyłę"]}[_poi.kind][int(_poi.stage)],roundi(chance*100)], _attempt)
|
|
UI.button(_choices, "Zakończ badanie tego miejsca", func(): _poi.complete = true; _dialog.hide())
|
|
UI.button(_choices, "Wróć do lotu", func(): _dialog.hide())
|
|
|
|
func _attempt() -> void:
|
|
if _ending:
|
|
return
|
|
var result: Dictionary = progress.resolve_poi(captain, _poi, _rng)
|
|
if result.is_empty():
|
|
return
|
|
var message := ""
|
|
if result.success:
|
|
message = "SUKCES · " + UI.material_text(result.materials)
|
|
if result.module != null:
|
|
message += "\nZnaleziono moduł statku, poziom %d." % result.module.item_level
|
|
if _poi.complete:
|
|
message += "\nMiejsce w pełni zbadane."
|
|
else:
|
|
message = "NIEPOWODZENIE · utrata %d HP.\nDalsze badanie tego miejsca jest niemożliwe." % result.damage
|
|
_update_hud()
|
|
_show_poi(message)
|
|
if progress.lander_hp <= 0:
|
|
_ending = true
|
|
_status.text = "LĄDOWNIK ZNISZCZONY — utracono łup wyprawy"
|
|
_dialog.hide()
|
|
var explosion := Node2D.new()
|
|
explosion.set_script(preload("res://scripts/game/player_destruction_effect.gd"))
|
|
explosion.configure(lander,Vector2.ZERO,camera)
|
|
lander.get_parent().add_child(explosion)
|
|
lander.hide()
|
|
await explosion.finished
|
|
finished.emit(false)
|
|
|
|
func _update_hud() -> void:
|
|
_hp.value = progress.lander_hp
|
|
if not _ending:
|
|
_status.text = "LĄDOWNIK %d / 100 HP · ENERGIA %d%%" % [ceili(progress.lander_hp),roundi(_motion.energy)]
|
|
var loot := UI.material_text(progress.cargo)
|
|
_cargo.text = "ŁUP WYPRAWY: " + (loot if not loot.is_empty() else "brak") + "\nModuły: %d" % progress.cargo_modules.size()
|
|
|
|
func _return(success: bool) -> void:
|
|
if _ending:
|
|
return
|
|
_ending = true
|
|
finished.emit(success)
|
|
|
|
func engine_thrust_active() -> bool:
|
|
return not _ending and not _dialog.visible and (_motion.boost_active or (Input.is_action_pressed("ship_thrust") and not Input.is_action_pressed("ship_brake")))
|
|
|
|
func _check_events() -> void:
|
|
for event in world.data.events:
|
|
if event.complete:
|
|
continue
|
|
var distance: float = lander.position.distance_to(event.position)
|
|
if distance > 260:
|
|
_event_armed = true
|
|
elif distance <= 180 and _event_armed and not _dialog.visible:
|
|
_event_armed = false
|
|
_show_event(event)
|
|
return
|
|
|
|
func _show_event(event: Dictionary, result: Dictionary = {}) -> void:
|
|
_active_event = event
|
|
for child in _choices.get_children():
|
|
_choices.remove_child(child)
|
|
child.queue_free()
|
|
_dialog.show()
|
|
UI.label(_choices,"SPOTKANIE / INŻYNIER",23,UI.CYAN)
|
|
var text := "Znalazłeś miejscowego inżyniera. Za 100 kredytów sprzeda Ci moduł. Nie jesteś pewien, czy będzie coś warty."
|
|
if not result.is_empty():
|
|
if result.get("success",false):
|
|
var item: ItemInstance = result.item
|
|
text = "Kupiono: %s\nPoziom modułu: %d\n\nModuł trafił do ładowni wyprawy. Wróć bezpiecznie na statek, aby go zachować." % [item.definition.display_name,item.item_level]
|
|
var icon := TextureRect.new()
|
|
icon.texture = item.definition.icon
|
|
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
icon.custom_minimum_size = Vector2(70,70)
|
|
_choices.add_child(icon)
|
|
else:
|
|
text = str(result.get("reason","Oferta niedostępna."))
|
|
var description := UI.label(_choices,text,16)
|
|
description.custom_minimum_size.x = 410
|
|
description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
if not event.complete:
|
|
UI.label(_choices,"Twoje kredyty: %d" % captain.credits,15,Color("efca8a"))
|
|
var buy := UI.button(_choices,"KUP NIEZNANY MODUŁ · 100 KREDYTÓW",_buy_engineer)
|
|
buy.disabled = captain.credits < 100
|
|
UI.button(_choices,"Może później",func(): _dialog.hide())
|
|
else:
|
|
UI.button(_choices,"Kontynuuj wyprawę",func(): _dialog.hide())
|
|
|
|
func _buy_engineer() -> void:
|
|
if _ending:
|
|
return
|
|
var result: Dictionary = progress.buy_engineer_offer(captain,_active_event,_rng)
|
|
_show_event(_active_event,result)
|
|
world.refresh_pois(lander.position)
|
|
_update_hud()
|
|
|
|
|
|
func _resize_surface_viewport() -> void:
|
|
if is_instance_valid(_surface_viewport):
|
|
_surface_viewport.size = Vector2i(Vector2(maxf(1,size.x),maxf(1,size.y))*SURFACE_RENDER_SCALE)
|