diff --git a/galaxy/star_systems/barnard's_star.tscn b/galaxy/star_systems/barnard's_star.tscn index 39297e13..4c1650b5 100644 --- a/galaxy/star_systems/barnard's_star.tscn +++ b/galaxy/star_systems/barnard's_star.tscn @@ -35,14 +35,15 @@ texture = ExtResource("2_xr5lq") [node name="Frigate" parent="." instance=ExtResource("3_snupf")] transform = Transform3D(0.866897, -1.28496e-16, 0.498488, 3.48787e-16, 1, -3.48787e-16, -0.498488, 4.76228e-16, 0.866897, -8.39, 2.08165e-12, 1.15) script = ExtResource("4_cyjdf") -detection_range = 10.0 -preferred_distance = 3.0 -distance_hysteresis = 1.5 -fire_range = 6.0 -direction_tolerance_deg = 10.0 -patrol_radius = 10.0 -patrol_target_tolerance = 1.0 +detection_range = null +preferred_distance = null +distance_hysteresis = null +fire_range = null +direction_tolerance_deg = null +patrol_radius = null +patrol_target_tolerance = null ship_def = ExtResource("5_shiqq") +free_when_destroyed = null [node name="CollisionShape3D" parent="Frigate" index="0"] transform = Transform3D(-4.63277e-08, 3.48787e-16, 1, 3.48787e-16, 1, -3.48787e-16, -1, 3.48787e-16, -1.07655e-07, 0.00127268, -0.00946045, 0.308623) diff --git a/galaxy/star_systems/wolf_359.tscn b/galaxy/star_systems/wolf_359.tscn index 501a77ee..f12e8e59 100644 --- a/galaxy/star_systems/wolf_359.tscn +++ b/galaxy/star_systems/wolf_359.tscn @@ -35,14 +35,15 @@ texture = ExtResource("2_sinon") [node name="Frigate" parent="." instance=ExtResource("3_myr2k")] transform = Transform3D(-0.155866, 3.98888e-16, 0.987779, 3.48787e-16, 1, -3.48787e-16, -0.987779, 2.9016e-16, -0.155866, 5.29319, 2.08165e-12, -2.13469) script = ExtResource("4_j6bxn") -detection_range = 10.0 -preferred_distance = 3.0 -distance_hysteresis = 1.5 -fire_range = 6.0 -direction_tolerance_deg = 10.0 -patrol_radius = 10.0 -patrol_target_tolerance = 1.0 +detection_range = null +preferred_distance = null +distance_hysteresis = null +fire_range = null +direction_tolerance_deg = null +patrol_radius = null +patrol_target_tolerance = null ship_def = ExtResource("5_gryh4") +free_when_destroyed = null [node name="CollisionShape3D" parent="Frigate" index="0"] transform = Transform3D(-4.63277e-08, 3.48787e-16, 1, 3.48787e-16, 1, -3.48787e-16, -1, 3.48787e-16, -1.07655e-07, 0.00127268, -0.00946045, 0.308623) @@ -58,14 +59,15 @@ mesh = SubResource("QuadMesh_fxdwr") [node name="Frigate2" parent="." instance=ExtResource("3_myr2k")] transform = Transform3D(-0.445682, -1.56783e-16, -0.895191, 3.48787e-16, 1, -3.48787e-16, 0.895191, -4.67679e-16, -0.445682, -6.43007, 2.08165e-12, -4.51201) script = ExtResource("4_j6bxn") -detection_range = 10.0 -preferred_distance = 3.0 -distance_hysteresis = 1.5 -fire_range = 6.0 -direction_tolerance_deg = 10.0 -patrol_radius = 10.0 -patrol_target_tolerance = 1.0 +detection_range = null +preferred_distance = null +distance_hysteresis = null +fire_range = null +direction_tolerance_deg = null +patrol_radius = null +patrol_target_tolerance = null ship_def = ExtResource("5_gryh4") +free_when_destroyed = null [node name="CollisionShape3D" parent="Frigate2" index="0"] transform = Transform3D(-4.63277e-08, 3.48787e-16, 1, 3.48787e-16, 1, -3.48787e-16, -1, 3.48787e-16, -1.07655e-07, 0.00127268, -0.00946045, 0.308623) diff --git a/game.tscn b/game.tscn index 90b200d5..012fd79e 100644 --- a/game.tscn +++ b/game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=37 format=3 uid="uid://sunhu71swcs2"] +[gd_scene load_steps=38 format=3 uid="uid://sunhu71swcs2"] [ext_resource type="Script" path="res://utils/shader_precompiler.gd" id="1_kgmvv"] [ext_resource type="Script" path="res://galaxy/hyperspace_controller.gd" id="2_5gj7y"] @@ -26,6 +26,7 @@ [ext_resource type="Script" path="res://gui/target_info.gd" id="20_4ebat"] [ext_resource type="Script" path="res://gui/game_over.gd" id="22_g1tis"] [ext_resource type="Script" path="res://gui/fps_counter.gd" id="23_ncnr7"] +[ext_resource type="Texture2D" uid="uid://dif27wf74itjw" path="res://gui/nesia_assets/elements/IconEnergy.png" id="24_iwm5p"] [sub_resource type="World3D" id="World3D_pvg5q"] environment = ExtResource("6_srnxb") @@ -241,12 +242,13 @@ pick_sound = NodePath("PickSound") stream = ExtResource("16_q8d2x") volume_db = -10.0 -[node name="PlayerVitals" type="GridContainer" parent="HUD/Sidebar" node_paths=PackedStringArray("hull_bar", "shield_bar")] +[node name="PlayerVitals" type="GridContainer" parent="HUD/Sidebar" node_paths=PackedStringArray("hull_bar", "shield_bar", "energy_bar")] layout_mode = 2 columns = 2 script = ExtResource("17_kyl4x") hull_bar = NodePath("HullBar") shield_bar = NodePath("ShieldBar") +energy_bar = NodePath("EnergyBar") [node name="HullIcon" type="TextureRect" parent="HUD/Sidebar/PlayerVitals"] layout_mode = 2 @@ -279,13 +281,29 @@ mouse_filter = 2 theme = ExtResource("11_x7io4") step = 2.08165e-12 -[node name="TargetInfo" type="VBoxContainer" parent="HUD/Sidebar" node_paths=PackedStringArray("target_label", "vitals_container", "hull_bar", "shield_bar", "pick_sound")] +[node name="EnergyIcon" type="TextureRect" parent="HUD/Sidebar/PlayerVitals"] +layout_mode = 2 +tooltip_text = "Shield" +theme = ExtResource("11_x7io4") +texture = ExtResource("24_iwm5p") +expand_mode = 2 +stretch_mode = 4 + +[node name="EnergyBar" type="ProgressBar" parent="HUD/Sidebar/PlayerVitals"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 +theme = ExtResource("11_x7io4") +step = 2.08165e-12 + +[node name="TargetInfo" type="VBoxContainer" parent="HUD/Sidebar" node_paths=PackedStringArray("target_label", "vitals_container", "hull_bar", "shield_bar", "energy_bar", "pick_sound")] layout_mode = 2 script = ExtResource("20_4ebat") target_label = NodePath("TargetedShipName") vitals_container = NodePath("TargetVitals") hull_bar = NodePath("TargetVitals/HullBar") shield_bar = NodePath("TargetVitals/ShieldBar") +energy_bar = NodePath("TargetVitals/EnergyBar") pick_sound = NodePath("PickSound") [node name="PickSound" type="AudioStreamPlayer" parent="HUD/Sidebar/TargetInfo"] @@ -336,6 +354,21 @@ mouse_filter = 2 theme = ExtResource("11_x7io4") step = 2.08165e-12 +[node name="EnergyIcon" type="TextureRect" parent="HUD/Sidebar/TargetInfo/TargetVitals"] +layout_mode = 2 +tooltip_text = "Shield" +theme = ExtResource("11_x7io4") +texture = ExtResource("24_iwm5p") +expand_mode = 2 +stretch_mode = 4 + +[node name="EnergyBar" type="ProgressBar" parent="HUD/Sidebar/TargetInfo/TargetVitals"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 +theme = ExtResource("11_x7io4") +step = 2.08165e-12 + [node name="GameOverDialog" type="AcceptDialog" parent="HUD"] title = "Game Over" initial_position = 2 @@ -365,6 +398,7 @@ script = ExtResource("23_ncnr7") [connection signal="jump_destination_loaded" from="HyperspaceController" to="HUD/Sidebar/SystemName" method="_on_jump_destination_loaded"] [connection signal="jump_started" from="HyperspaceController" to="MainCamera/HyperspaceEffect" method="_on_jump_started"] [connection signal="ship_destroyed" from="Player" to="HUD/GameOverDialog" method="_on_player_ship_destroyed"] +[connection signal="ship_energy_changed" from="Player" to="HUD/Sidebar/PlayerVitals" method="_on_player_ship_energy_changed"] [connection signal="ship_hull_changed" from="Player" to="HUD/Sidebar/PlayerVitals" method="_on_player_ship_hull_changed"] [connection signal="ship_shield_changed" from="Player" to="HUD/Sidebar/PlayerVitals" method="_on_player_ship_shield_changed"] [connection signal="ship_target_changed" from="Player" to="HUD/Sidebar/TargetInfo" method="_on_player_ship_target_changed"] diff --git a/gui/nesia_assets/elements/IconEnergy.png b/gui/nesia_assets/elements/IconEnergy.png new file mode 100755 index 00000000..7ac97f65 Binary files /dev/null and b/gui/nesia_assets/elements/IconEnergy.png differ diff --git a/gui/nesia_assets/elements/IconEnergy.png.import b/gui/nesia_assets/elements/IconEnergy.png.import new file mode 100644 index 00000000..772bdc7b --- /dev/null +++ b/gui/nesia_assets/elements/IconEnergy.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dif27wf74itjw" +path="res://.godot/imported/IconEnergy.png-7d28592ea902d59f0c28cebbb0d83015.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://gui/nesia_assets/elements/IconEnergy.png" +dest_files=["res://.godot/imported/IconEnergy.png-7d28592ea902d59f0c28cebbb0d83015.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=1 +roughness/src_normal="" +process/fix_alpha_border=false +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/gui/player_vitals.gd b/gui/player_vitals.gd index ee2898c9..37537ca3 100644 --- a/gui/player_vitals.gd +++ b/gui/player_vitals.gd @@ -2,6 +2,7 @@ extends GridContainer @export var hull_bar: ProgressBar @export var shield_bar: ProgressBar +@export var energy_bar: ProgressBar func _on_player_ship_hull_changed(ship: Ship) -> void: assert(ship.ship_def.hull > 0.0, "Ship definition should not have 0 hull") @@ -11,3 +12,7 @@ func _on_player_ship_hull_changed(ship: Ship) -> void: func _on_player_ship_shield_changed(ship: Ship) -> void: self.shield_bar.max_value = 1.0 if is_zero_approx(ship.ship_def.shield) else ship.ship_def.shield self.shield_bar.value = ship.shield + +func _on_player_ship_energy_changed(ship: Ship) -> void: + self.energy_bar.max_value = 1.0 if is_zero_approx(ship.ship_def.energy) else ship.ship_def.energy + self.energy_bar.value = ship.energy diff --git a/gui/target_info.gd b/gui/target_info.gd index 3d04c478..5f0cd3de 100644 --- a/gui/target_info.gd +++ b/gui/target_info.gd @@ -6,6 +6,7 @@ extends VBoxContainer @export var vitals_container: Container @export var hull_bar: ProgressBar @export var shield_bar: ProgressBar +@export var energy_bar: ProgressBar @export var pick_sound: AudioStreamPlayer var _target: Ship = null @@ -17,6 +18,7 @@ func _on_player_ship_target_changed(_player_ship: Ship, targeted_ship: Ship) -> if self._target != null: self._target.ship_hull_changed.disconnect(_on_target_ship_hull_changed) self._target.ship_shield_changed.disconnect(_on_target_ship_shield_changed) + self._target.ship_energy_changed.disconnect(_on_target_ship_energy_changed) self._target = targeted_ship @@ -30,8 +32,10 @@ func _on_player_ship_target_changed(_player_ship: Ship, targeted_ship: Ship) -> targeted_ship.ship_hull_changed.connect(_on_target_ship_hull_changed) targeted_ship.ship_shield_changed.connect(_on_target_ship_shield_changed) + targeted_ship.ship_energy_changed.connect(_on_target_ship_energy_changed) self._update_hull() self._update_shield() + self._update_energy() func _on_target_ship_hull_changed(ship: Ship) -> void: assert(ship == self._target, "Should only be notified about changes to the target") @@ -41,6 +45,10 @@ func _on_target_ship_shield_changed(ship: Ship) -> void: assert(ship == self._target, "Should only be notified about changes to the target") self._update_shield() +func _on_target_ship_energy_changed(ship: Ship) -> void: + assert(ship == self._target, "Should only be notified about changes to the target") + self._update_energy() + func _update_hull() -> void: assert(self._target.ship_def.hull > 0.0, "Ship definition should not have 0 hull") self.hull_bar.max_value = self._target.ship_def.hull @@ -49,3 +57,7 @@ func _update_hull() -> void: func _update_shield() -> void: self.shield_bar.max_value = 1.0 if is_zero_approx(self._target.ship_def.shield) else self._target.ship_def.shield self.shield_bar.value = self._target.shield + +func _update_energy() -> void: + self.energy_bar.max_value = 1.0 if is_zero_approx(self._target.ship_def.energy) else self._target.ship_def.energy + self.energy_bar.value = self._target.energy diff --git a/ships/corvette01/corvette01.tres b/ships/corvette01/corvette01.tres index 0ea5f0f1..1b318e65 100644 --- a/ships/corvette01/corvette01.tres +++ b/ships/corvette01/corvette01.tres @@ -6,9 +6,13 @@ script = ExtResource("1_3j2tk") mass_kg = 10.0 hull = 40.0 -shield = 2.08165e-12 +shield = 0.0 thrust = 200.0 torque = 8.0 fire_interval_msec = 200 fire_force = 2.0 hyperspace_arrival_radius = 8.0 +energy = 100.0 +energy_recharge_rate = 5.0 +thrust_energy_consumption = 8.0 +shield_recharge_rate = 0.0 diff --git a/ships/frigate03/frigate03.tres b/ships/frigate03/frigate03.tres index 3e94de9b..9e8a5506 100644 --- a/ships/frigate03/frigate03.tres +++ b/ships/frigate03/frigate03.tres @@ -12,3 +12,7 @@ torque = 1.0 fire_interval_msec = 1000 fire_force = 2.0 hyperspace_arrival_radius = 8.0 +energy = 300.0 +energy_recharge_rate = 2.0 +thrust_energy_consumption = 5.0 +shield_recharge_rate = 10.0 diff --git a/ships/pirate.gd b/ships/pirate.gd index 0f5b7b2e..9ab2c82a 100644 --- a/ships/pirate.gd +++ b/ships/pirate.gd @@ -72,7 +72,7 @@ func _pointing_in_direction(direction: Vector3) -> bool: var current_direction = -self.global_transform.basis.z return current_direction.angle_to(direction) <= self._direction_tolerance_rad -func _patrol_behavior(_delta: float): +func _patrol_behavior(delta: float): self.set_target(self._find_closest_ship()) if self.target != null: self._current_state = State.ENGAGE @@ -82,9 +82,9 @@ func _patrol_behavior(_delta: float): if direction_to_target.length() < self.patrol_target_tolerance: # Close enough to current patrol point self._select_new_patrol_target() elif self._pointing_in_direction(direction_to_target.normalized()): - self.thrust_step(1.0) + self.thrust_step(1.0, delta) -func _engage_behavior(_delta: float): +func _engage_behavior(delta: float): if self.target == null: self._current_state = State.PATROL return @@ -96,25 +96,25 @@ func _engage_behavior(_delta: float): return if distance > self.preferred_distance + self.distance_hysteresis: - self.thrust_step(1.0) + self.thrust_step(1.0, delta) elif distance < self.preferred_distance - self.distance_hysteresis: self._current_state = State.RETREAT else: # Maintain distance if abs(distance - self.preferred_distance) > self.distance_hysteresis / 2: - self.thrust_step(0.5) # Use half thrust for small adjustments + self.thrust_step(0.5, delta) # Use half thrust for small adjustments if distance <= self.fire_range: self.fire() -func _retreat_behavior(_delta: float): +func _retreat_behavior(delta: float): if self.target == null: self._current_state = State.PATROL return var distance = self.global_transform.origin.distance_to(self.target.global_transform.origin) if self._pointing_in_direction(self._desired_direction()): - self.thrust_step(1.0) + self.thrust_step(1.0, delta) if distance >= self.preferred_distance: self._current_state = State.ENGAGE diff --git a/ships/player.gd b/ships/player.gd index 0825bbc9..f9d9a7c9 100644 --- a/ships/player.gd +++ b/ships/player.gd @@ -52,7 +52,9 @@ func _next_ship_target() -> Ship: return ships[index + 1] if index + 1 < ships.size() else null -func _process(_delta: float) -> void: +func _process(delta: float) -> void: + super(delta) + if self.hyperspace_controller.jumping: return @@ -66,14 +68,14 @@ func _absolute_input_direction() -> Vector3: var input_direction := Input.get_vector("move_left", "move_right", "move_up", "move_down") return Vector3(input_direction.x, 0, input_direction.y) -func _physics_process(_delta: float) -> void: +func _physics_process(delta: float) -> void: if self.hyperspace_controller.jumping: return match UserPreferences.control_scheme: UserPreferences.ControlScheme.RELATIVE: if Input.is_action_pressed("thrust"): - self.thrust_step(1.0) + self.thrust_step(1.0, delta) else: self.thrust_stopped() @@ -81,7 +83,7 @@ func _physics_process(_delta: float) -> void: var desired_direction := self._absolute_input_direction() var current_direction := - self.transform.basis.z if desired_direction != Vector3.ZERO and desired_direction.angle_to(current_direction) <= ABSOLUTE_DIRECTION_TOLERANCE_RAD: - self.thrust_step(desired_direction.length()) + self.thrust_step(desired_direction.length(), delta) else: self.thrust_stopped() diff --git a/ships/ship.gd b/ships/ship.gd index a02f31d7..e5430b85 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -36,6 +36,9 @@ signal ship_hull_changed(ship: Ship) ## Fires when this ship's shield strength changes (e.g., due to damage). signal ship_shield_changed(ship: Ship) +## Fires when this ship's energy level changes. +signal ship_energy_changed(ship: Ship) + ## Fires when this ship is destroyed, before the node is freed. signal ship_destroyed(ship: Ship) @@ -52,6 +55,11 @@ var hull: float ## This property should not be written to outside the class! var shield: float +## The ship's current energy level. +## +## This property should not be written to outside the class! +var energy: float + ## The ship's current target. ## ## This property should not be written to outside the class! @@ -67,9 +75,11 @@ func _ready() -> void: assert(self.hull > 0, "Hull must be greater than 0") self.shield = self.ship_def.shield + self.energy = self.ship_def.energy self.emit_signal("ship_hull_changed", self) self.emit_signal("ship_shield_changed", self) + self.emit_signal("ship_energy_changed", self) ## Damage this ship by the [code]shield[/code] and [code]hull[/code] amounts specified in the given dictionary. ## @@ -132,13 +142,21 @@ func _explode() -> void: ## Invoked by subclasses to apply a thrust force to the ship. ## ## This is expected to be invoked during a physics step (i.e., [code]_physics_process[/code]) and not otherwise. -func thrust_step(magnitude: float) -> void: +func thrust_step(magnitude: float, step_delta: float) -> void: + if is_zero_approx(self.energy): + self.thrust_stopped() + return + if self.thruster_audio.stream_paused: self.thruster_audio.stream_paused = false - elif !self.thruster_audio.playing: + if !self.thruster_audio.playing: self.thruster_audio.play() - self.apply_central_force(self.transform.basis * Vector3.FORWARD * self.ship_def.thrust * magnitude) + var desired_energy = self.ship_def.thrust_energy_consumption * magnitude * step_delta + var energy_consumed = minf(self.energy, desired_energy) + self.energy -= energy_consumed + self.apply_central_force(self.transform.basis * Vector3.FORWARD * self.ship_def.thrust * magnitude * (energy_consumed / desired_energy)) + self.emit_signal("ship_energy_changed", self) ## Invoked by subclasses on physics steps where no thrust should be applied. func thrust_stopped() -> void: @@ -167,3 +185,26 @@ func set_targeted_by_player(targeted: bool) -> void: func _on_ship_destroyed(ship: Ship) -> void: assert(ship == self.target, "Should only receive ship destruction notification from current target") self.set_target(null) + +func _process(delta: float) -> void: + # Ordering is important here: always recharge the shield and then recharge energy (if applicable). + self._recharge_shield(delta) + self._recharge_energy(delta) + +func _recharge_energy(delta: float) -> void: + if is_zero_approx(self.ship_def.energy_recharge_rate) or is_equal_approx(self.energy, self.ship_def.energy): + return + + self.energy = minf(self.energy + self.ship_def.energy_recharge_rate * delta, self.ship_def.energy) + self.emit_signal("ship_energy_changed", self) + +func _recharge_shield(delta: float) -> void: + if is_zero_approx(self.ship_def.shield_recharge_rate) or self.energy <= 0.0 or is_equal_approx(self.shield, self.ship_def.shield): + return + + var shield_increase = minf(self.energy, self.ship_def.shield_recharge_rate * delta) + self.shield = minf(self.shield + shield_increase, self.ship_def.shield) + self.energy -= shield_increase + + self.emit_signal("ship_shield_changed", self) + self.emit_signal("ship_energy_changed", self) diff --git a/ships/ship_def.gd b/ships/ship_def.gd index 57acb5f6..21af2534 100644 --- a/ships/ship_def.gd +++ b/ships/ship_def.gd @@ -28,3 +28,15 @@ class_name ShipDef ## When arriving from a hyperspace jump, the ship's position will be randomized around the system center. This is the maximum radius (in m) that the randomized position is allowed to occupy. @export var hyperspace_arrival_radius: float + +## The maximum energy capacity of the ship. +@export var energy: float + +## The rate at which the ship's energy recharges per second. +@export var energy_recharge_rate: float + +## The amount of energy consumed per second when thrusting at full power. +@export var thrust_energy_consumption: float + +## The rate at which shields recharge per second when energy is available. +@export var shield_recharge_rate: float