diff --git a/README.md b/README.md index cbc641a1..7b3d2885 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Instructions: * J to jump to hyperspace * ` (backtick) or ยง to select a planet to land on * L to land +* C to open the currency trading window * Scroll wheel to zoom in and out ## Development diff --git a/mechanics/economy/currencies/lumins.tres b/mechanics/economy/currencies/lumins.tres index 3733ac4b..f25b3de0 100644 --- a/mechanics/economy/currencies/lumins.tres +++ b/mechanics/economy/currencies/lumins.tres @@ -4,6 +4,6 @@ [resource] script = ExtResource("1_nu2tg") -price_in_credits = 23.0 +price_in_credits = 0.043 precision = 1 name = "Lumins" diff --git a/mechanics/economy/currency.gd b/mechanics/economy/currency.gd index 7ef0163b..5c5cac84 100644 --- a/mechanics/economy/currency.gd +++ b/mechanics/economy/currency.gd @@ -29,6 +29,10 @@ func add_exactly(amount: float, _cargo_hold: CargoHold, bank_account: BankAccoun bank_account.deposit(self, amount) return true +## Returns the cost of one unit of this currency, denominated in the price of [param currency]. +func price_in_other_currency(currency: Currency) -> float: + return currency.price_in_credits / self.price_in_credits + func price_converted_from_credits(price: float) -> float: return self.round(price / self.price_in_credits) diff --git a/project.godot b/project.godot index 09362a62..f1c92522 100644 --- a/project.godot +++ b/project.godot @@ -192,6 +192,11 @@ cycle_landing_target={ , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":167,"key_label":0,"unicode":167,"echo":false,"script":null) ] } +toggle_currency_trading={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"echo":false,"script":null) +] +} [layer_names] diff --git a/screens/game/currency_trading/currency_trading_window.gd b/screens/game/currency_trading/currency_trading_window.gd new file mode 100644 index 00000000..bfde75e1 --- /dev/null +++ b/screens/game/currency_trading/currency_trading_window.gd @@ -0,0 +1,144 @@ +extends Window +class_name CurrencyTradingWindow + +@export var buy_spin_box: SpinBox +@export var buy_option_button: OptionButton +@export var buy_balance_label: Label +@export var trade_button: Button +@export var sell_spin_box: SpinBox +@export var sell_option_button: OptionButton +@export var sell_balance_label: Label + +const CURRENCIES_DIRECTORY = "res://mechanics/economy/currencies/" + +var bank_account: BankAccount +var _currencies: Array[Currency] +var _updating: bool + +func _ready() -> void: + self._updating = true + self.bank_account.changed.connect(_update) + self._currencies = self._load_all_currencies() + + for currency in self._currencies: + self.buy_option_button.add_item(currency.name) + self.sell_option_button.add_item(currency.name) + + self.buy_option_button.select(1) + self.sell_option_button.select(0) + self._updating = false + + self._update() + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("toggle_currency_trading"): + self.get_viewport().set_input_as_handled() + self.queue_free() + +func _get_buy_currency() -> Currency: + return self._currencies[self.buy_option_button.selected] if self.buy_option_button.selected >= 0 else null + +func _get_sell_currency() -> Currency: + return self._currencies[self.sell_option_button.selected] if self.sell_option_button.selected >= 0 else null + +func _on_trade_button_pressed() -> void: + var buy_currency: Currency = self._get_buy_currency() + var sell_currency: Currency = self._get_sell_currency() + assert(buy_currency and sell_currency and buy_currency != sell_currency, "Expected to have distinct currencies set for buying and selling") + + self._updating = true + + var price := buy_currency.price_in_credits / sell_currency.price_in_credits + var withdrew := self.bank_account.withdraw_up_to(sell_currency, self.sell_spin_box.value) + self.bank_account.deposit(buy_currency, withdrew * price) + + self._updating = false + self._update() + +func _on_buy_option_button_item_selected(_index: int) -> void: + if self._updating: + return + + self._update() + self._update_buy_spin_box_value() + +func _on_buy_spin_box_value_changed(_value: float) -> void: + self._update_sell_spin_box_value() + +func _update_sell_spin_box_value() -> void: + if self._updating: + return + + self._updating = true + + var buy_currency: Currency = self._get_buy_currency() + var sell_currency: Currency = self._get_sell_currency() + if buy_currency and sell_currency and buy_currency != sell_currency: + self.sell_spin_box.value = sell_currency.round(self.buy_spin_box.value * buy_currency.price_in_other_currency(sell_currency)) + + self._updating = false + +func _on_sell_option_button_item_selected(_index: int) -> void: + if self._updating: + return + + self._update() + self._update_sell_spin_box_value() + +func _on_sell_spin_box_value_changed(_value: float) -> void: + self._update_buy_spin_box_value() + +func _update_buy_spin_box_value() -> void: + if self._updating: + return + + self._updating = true + + var buy_currency: Currency = self._get_buy_currency() + var sell_currency: Currency = self._get_sell_currency() + if buy_currency and sell_currency and buy_currency != sell_currency: + self.buy_spin_box.value = buy_currency.round(self.sell_spin_box.value * sell_currency.price_in_other_currency(buy_currency)) + + self._updating = false + +func _update() -> void: + self._updating = true + + var buy_currency: Currency = self._get_buy_currency() + var sell_currency: Currency = self._get_sell_currency() + + if buy_currency: + var balance: float = self.bank_account.currencies.get(buy_currency, 0.0) + self.buy_balance_label.text = "Balance: %s" % buy_currency.amount_as_string(balance) + self.buy_spin_box.step = pow(10, -buy_currency.precision) + else: + self.buy_balance_label.text = "" + + if sell_currency: + var balance: float = self.bank_account.currencies.get(sell_currency, 0.0) + self.sell_balance_label.text = "Balance: %s" % sell_currency.amount_as_string(balance) + self.sell_spin_box.step = pow(10, -sell_currency.precision) + else: + self.sell_balance_label.text = "" + + if buy_currency and sell_currency and buy_currency != sell_currency: + var sell_balance: float = self.bank_account.currencies.get(sell_currency, 0.0) + + self.trade_button.disabled = sell_balance < buy_currency.price_in_other_currency(sell_currency) + self.buy_spin_box.max_value = buy_currency.round(sell_balance * sell_currency.price_in_other_currency(buy_currency)) + self.sell_spin_box.max_value = sell_balance + else: + self.trade_button.disabled = true + + self._updating = false + +func _on_close_requested() -> void: + self.queue_free() + +func _load_all_currencies() -> Array[Currency]: + var result: Array[Currency] = [] + + for file in DirAccess.get_files_at(CURRENCIES_DIRECTORY): + result.push_back(load("%s/%s" % [CURRENCIES_DIRECTORY, file])) + + return result diff --git a/screens/game/currency_trading/currency_trading_window.tscn b/screens/game/currency_trading/currency_trading_window.tscn new file mode 100644 index 00000000..b56ee829 --- /dev/null +++ b/screens/game/currency_trading/currency_trading_window.tscn @@ -0,0 +1,84 @@ +[gd_scene load_steps=2 format=3 uid="uid://b1yvdsvgyesj5"] + +[ext_resource type="Script" path="res://screens/game/currency_trading/currency_trading_window.gd" id="1_a2pkb"] + +[node name="CurrencyTradingWindow" type="Window" node_paths=PackedStringArray("buy_spin_box", "buy_option_button", "buy_balance_label", "trade_button", "sell_spin_box", "sell_option_button", "sell_balance_label")] +title = "Currency Exchange" +initial_position = 2 +size = Vector2i(600, 200) +script = ExtResource("1_a2pkb") +buy_spin_box = NodePath("PanelContainer/GridContainer/BuyContainer/BuySpinBox") +buy_option_button = NodePath("PanelContainer/GridContainer/BuyContainer/BuyOptionButton") +buy_balance_label = NodePath("PanelContainer/GridContainer/BuyBalanceLabel") +trade_button = NodePath("PanelContainer/GridContainer/SellContainer/TradeButton") +sell_spin_box = NodePath("PanelContainer/GridContainer/SellContainer/SellSpinBox") +sell_option_button = NodePath("PanelContainer/GridContainer/SellContainer/SellOptionButton") +sell_balance_label = NodePath("PanelContainer/GridContainer/SellBalanceLabel") + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="GridContainer" type="GridContainer" parent="PanelContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +columns = 4 + +[node name="BuyLabel" type="Label" parent="PanelContainer/GridContainer"] +layout_mode = 2 +text = "BUY " + +[node name="BuyContainer" type="HBoxContainer" parent="PanelContainer/GridContainer"] +layout_mode = 2 + +[node name="BuySpinBox" type="SpinBox" parent="PanelContainer/GridContainer/BuyContainer"] +layout_mode = 2 +custom_arrow_step = 1.0 + +[node name="BuyOptionButton" type="OptionButton" parent="PanelContainer/GridContainer/BuyContainer"] +layout_mode = 2 + +[node name="SellLabel" type="Label" parent="PanelContainer/GridContainer"] +layout_mode = 2 +text = " FOR " + +[node name="SellContainer" type="HBoxContainer" parent="PanelContainer/GridContainer"] +layout_mode = 2 + +[node name="SellSpinBox" type="SpinBox" parent="PanelContainer/GridContainer/SellContainer"] +layout_mode = 2 +custom_arrow_step = 1.0 + +[node name="SellOptionButton" type="OptionButton" parent="PanelContainer/GridContainer/SellContainer"] +layout_mode = 2 + +[node name="TradeButton" type="Button" parent="PanelContainer/GridContainer/SellContainer"] +layout_mode = 2 +text = "TRADE" + +[node name="Empty" type="Control" parent="PanelContainer/GridContainer"] +layout_mode = 2 + +[node name="BuyBalanceLabel" type="Label" parent="PanelContainer/GridContainer"] +layout_mode = 2 +text = "Balance: 123 credits" + +[node name="Empty2" type="Control" parent="PanelContainer/GridContainer"] +layout_mode = 2 + +[node name="SellBalanceLabel" type="Label" parent="PanelContainer/GridContainer"] +layout_mode = 2 +text = "Balance: 123 lumins" + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] +[connection signal="value_changed" from="PanelContainer/GridContainer/BuyContainer/BuySpinBox" to="." method="_on_buy_spin_box_value_changed"] +[connection signal="item_selected" from="PanelContainer/GridContainer/BuyContainer/BuyOptionButton" to="." method="_on_buy_option_button_item_selected"] +[connection signal="value_changed" from="PanelContainer/GridContainer/SellContainer/SellSpinBox" to="." method="_on_sell_spin_box_value_changed"] +[connection signal="item_selected" from="PanelContainer/GridContainer/SellContainer/SellOptionButton" to="." method="_on_sell_option_button_item_selected"] +[connection signal="pressed" from="PanelContainer/GridContainer/SellContainer/TradeButton" to="." method="_on_trade_button_pressed"] diff --git a/screens/game/game.tscn b/screens/game/game.tscn index ad40d825..1b98cd42 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=69 format=3 uid="uid://sunhu71swcs2"] +[gd_scene load_steps=70 format=3 uid="uid://sunhu71swcs2"] [ext_resource type="Environment" uid="uid://b7t3opwr35xen" path="res://fx/environment.tres" id="1_pqled"] [ext_resource type="Script" path="res://mechanics/hyperspace/hyperspace_scene_switcher.gd" id="4_yyrkm"] @@ -29,6 +29,7 @@ [ext_resource type="Script" path="res://screens/game/hud/jump_destination_name.gd" id="23_1qfwo"] [ext_resource type="StyleBox" uid="uid://bu1t76eyvll57" path="res://screens/game/hud/hud_panel_texture.tres" id="23_lvs05"] [ext_resource type="AudioStream" uid="uid://dqsuscukdo5oa" path="res://screens/shared_ui/audio/itempick1.wav" id="24_oh7qv"] +[ext_resource type="PackedScene" uid="uid://b1yvdsvgyesj5" path="res://screens/game/currency_trading/currency_trading_window.tscn" id="25_57wc6"] [ext_resource type="Texture2D" uid="uid://bl3sv43f2a23p" path="res://screens/game/hud/images/player_vitals/node_unpowered.png" id="25_d2hsf"] [ext_resource type="Script" path="res://screens/game/hud/fuel_bar.gd" id="25_hno5u"] [ext_resource type="Texture2D" uid="uid://c2nr462gj84yf" path="res://screens/game/hud/images/player_vitals/heading_bg.png" id="26_mxja1"] @@ -330,6 +331,7 @@ script = ExtResource("18_y74ml") galaxy_map_scene = ExtResource("17_4w17v") game_over_scene = ExtResource("17_yidh7") exit_dialog_scene = ExtResource("18_hl4w3") +currency_trading_scene = ExtResource("25_57wc6") player = NodePath("../HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player") [node name="MarginContainer" type="MarginContainer" parent="InGameGUI"] diff --git a/screens/game/in_game_gui.gd b/screens/game/in_game_gui.gd index 0f21ab48..dd16436a 100644 --- a/screens/game/in_game_gui.gd +++ b/screens/game/in_game_gui.gd @@ -3,6 +3,7 @@ extends CanvasLayer @export var galaxy_map_scene: PackedScene @export var game_over_scene: PackedScene @export var exit_dialog_scene: PackedScene +@export var currency_trading_scene: PackedScene @export var player: Player func _on_player_ship_destroyed(_player: Player) -> void: @@ -14,6 +15,11 @@ func _unhandled_input(event: InputEvent) -> void: galaxy_map.hyperdrive_system = self.player.ship.hyperdrive_system self.add_child(galaxy_map) galaxy_map.show() + elif event.is_action_pressed("toggle_currency_trading") and is_instance_valid(self.player): + var currency_trading: CurrencyTradingWindow = self.currency_trading_scene.instantiate() + currency_trading.bank_account = self.player.bank_account + self.add_child(currency_trading) + currency_trading.show() elif event.is_action_pressed("exit"): self._instantiate_and_show_window(self.exit_dialog_scene) diff --git a/screens/landing/trading_window.tscn b/screens/landing/trading_window.tscn index 12d35146..373d8e69 100644 --- a/screens/landing/trading_window.tscn +++ b/screens/landing/trading_window.tscn @@ -1,26 +1,16 @@ -[gd_scene load_steps=6 format=3 uid="uid://dpihtkm0u6kmb"] +[gd_scene load_steps=3 format=3 uid="uid://dpihtkm0u6kmb"] [ext_resource type="Script" path="res://screens/landing/trading_window.gd" id="1_b1kwq"] -[ext_resource type="Resource" uid="uid://b764gd8q3il6s" path="res://mechanics/economy/currencies/credits.tres" id="15_5n2yb"] -[ext_resource type="Script" path="res://mechanics/economy/market.gd" id="16_so5ie"] [ext_resource type="PackedScene" uid="uid://b04gt254vhyra" path="res://screens/landing/trade_buttons.tscn" id="17_j3rf1"] -[sub_resource type="Resource" id="Resource_a521o"] -script = ExtResource("16_so5ie") -money = ExtResource("15_5n2yb") -commodities = {} - -[node name="TradingWindow" type="Window" node_paths=PackedStringArray("tab_container", "commodities_container", "currencies_container")] +[node name="TradingWindow" type="Window" node_paths=PackedStringArray("commodities_container")] title = "Trade Computer" initial_position = 1 size = Vector2i(600, 300) transient = true script = ExtResource("1_b1kwq") -market = SubResource("Resource_a521o") -trade_buttons_scene = ExtResource("17_j3rf1") -tab_container = NodePath("") commodities_container = NodePath("PanelContainer/Commodities/MarginContainer/ScrollContainer/GridContainer") -currencies_container = NodePath("") +trade_buttons_scene = ExtResource("17_j3rf1") [node name="PanelContainer" type="PanelContainer" parent="."] anchors_preset = 15