diff --git a/Zennysoft.Game.Ma.Implementation/App/State/States/AppLogic.State.MainMenu.cs b/Zennysoft.Game.Ma.Implementation/App/State/States/AppLogic.State.MainMenu.cs index 75be175b..a449831e 100644 --- a/Zennysoft.Game.Ma.Implementation/App/State/States/AppLogic.State.MainMenu.cs +++ b/Zennysoft.Game.Ma.Implementation/App/State/States/AppLogic.State.MainMenu.cs @@ -7,7 +7,7 @@ public partial class AppLogic public partial record State { [Meta] - public partial record MainMenu : State, IGet, IGet, IGet + public partial record MainMenu : State, IGet, IGet, IGet { public MainMenu() { @@ -16,7 +16,11 @@ public partial class AppLogic public Transition On(in Input.NewGame input) => To(); public Transition On(in Input.EnemyViewerOpened input) => To(); - public Transition On(in Input.LoadGame input) => To(); + public Transition On(in Input.QuitGame input) + { + Output(new Output.ExitGame()); + return ToSelf(); + } } } } diff --git a/Zennysoft.Game.Ma.Implementation/Components/IEquipmentComponent.cs b/Zennysoft.Game.Ma.Implementation/Components/IEquipmentComponent.cs index fbdae434..9cf42ca3 100644 --- a/Zennysoft.Game.Ma.Implementation/Components/IEquipmentComponent.cs +++ b/Zennysoft.Game.Ma.Implementation/Components/IEquipmentComponent.cs @@ -1,4 +1,5 @@ using Chickensoft.Collections; +using Godot; using Zennysoft.Ma.Adapter.Entity; namespace Zennysoft.Ma.Adapter; @@ -28,4 +29,5 @@ public interface IEquipmentComponent : IEntityComponent public ElementalResistanceSet ElementalResistance { get; } + public event Action EquipmentChanged; } diff --git a/Zennysoft.Game.Ma.Implementation/Components/IHealthComponent.cs b/Zennysoft.Game.Ma.Implementation/Components/IHealthComponent.cs index ecc9aeca..c88a870e 100644 --- a/Zennysoft.Game.Ma.Implementation/Components/IHealthComponent.cs +++ b/Zennysoft.Game.Ma.Implementation/Components/IHealthComponent.cs @@ -21,5 +21,5 @@ public interface IHealthComponent : IEntityComponent public void SetMaximumHealth(int health); - public void RaiseMaximumHP(int raiseAmount); + public void RaiseMaximumHP(int raiseAmount, bool restoreHP = false); } \ No newline at end of file diff --git a/Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs b/Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs index 76e9bfcd..615214b4 100644 --- a/Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs +++ b/Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs @@ -16,5 +16,7 @@ public interface IVTComponent : IEntityComponent public void SetVT(int vt); - public void RaiseMaximumVT(int raiseAmount); + public void RaiseMaximumVT(int raiseAmount, bool restoreVT = true); + + public void SetMaximumVT(int vt); } diff --git a/Zennysoft.Game.Ma.Implementation/Item/IInventory.cs b/Zennysoft.Game.Ma.Implementation/Item/IInventory.cs index e55ab2c6..4a71eb49 100644 --- a/Zennysoft.Game.Ma.Implementation/Item/IInventory.cs +++ b/Zennysoft.Game.Ma.Implementation/Item/IInventory.cs @@ -15,4 +15,5 @@ public interface IInventory public void Sort(EquipableItem currentWeapon, EquipableItem currentArmor, EquipableItem currentAccessory); public event Action BroadcastMessage; + public event Action InventoryChanged; } diff --git a/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs b/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs new file mode 100644 index 00000000..a578125a --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs @@ -0,0 +1,287 @@ +using Godot; +using Godot.Collections; + +namespace NathanHoad +{ + public static class InputHelper + { + public delegate void DeviceChangedEventHandler(string device, int deviceIndex); + public delegate void KeyboardInputChangedEventHandler(string action, InputEvent input); + public delegate void JoypadInputChangedEventHandler(string action, InputEvent input); + public delegate void JoypadChangedEventHandler(int deviceIndex, bool isConnected); + + public static DeviceChangedEventHandler? DeviceChanged; + public static KeyboardInputChangedEventHandler? KeyboardInputChanged; + public static JoypadInputChangedEventHandler? JoypadInputChanged; + public static JoypadChangedEventHandler? JoypadChanged; + + + public const string DEVICE_KEYBOARD = "keyboard"; + public const string DEVICE_XBOX_CONTROLLER = "xbox"; + public const string DEVICE_SWITCH_CONTROLLER = "switch"; + public const string DEVICE_PLAYSTATION_CONTROLLER = "playstation"; + public const string DEVICE_STEAMDECK_CONTROLLER = "steamdeck"; + public const string DEVICE_GENERIC = "generic"; + + public const string SUB_DEVICE_XBOX_ONE_CONTROLLER = "xbox_one"; + public const string SUB_DEVICE_XBOX_SERIES_CONTROLLER = "xbox_series"; + public const string SUB_DEVICE_PLAYSTATION3_CONTROLLER = "playstation3"; + public const string SUB_DEVICE_PLAYSTATION4_CONTROLLER = "playstation4"; + public const string SUB_DEVICE_PLAYSTATION5_CONTROLLER = "playstation5"; + public const string SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "switch_left_joycon"; + public const string SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "switch_right_joycon"; + + + private static Node instance; + public static Node Instance + { + get + { + if (instance == null) + { + instance = (Node)Engine.GetSingleton("InputHelper"); + instance.Connect("device_changed", Callable.From((string device, int deviceIndex) => DeviceChanged?.Invoke(device, deviceIndex))); + instance.Connect("keyboard_input_changed", Callable.From((string action, InputEvent input) => KeyboardInputChanged?.Invoke(action, input))); + instance.Connect("joypad_input_changed", Callable.From((string action, InputEvent input) => JoypadInputChanged?.Invoke(action, input))); + instance.Connect("joypad_changed", Callable.From((int deviceIndex, bool isConnected) => JoypadChanged?.Invoke(deviceIndex, isConnected))); + } + return instance; + } + } + + + public static string Device + { + get => (string)Instance.Get("device"); + } + + + public static int DeviceIndex + { + get => (int)Instance.Get("device_index"); + } + + + public static string LastKnownJoypadDevice + { + get => (string)Instance.Get("last_known_joypad_device"); + } + + + public static string LastKnownJoypadIndex + { + get => (string)Instance.Get("last_known_joypad_index"); + } + + + public static float Deadzone + { + get => (float)Instance.Get("deadzone"); + set => Instance.Set("deadzone", value); + } + + + public static int MouseMotionThreshold + { + get => (int)Instance.Get("mouse_motion_threshold"); + set => Instance.Set("mouse_motion_threshold", value); + } + + + public static string GetSimplifiedDeviceName(string rawName) + { + return (string)Instance.Call("get_simplified_device_name", rawName); + } + + + public static string GetDeviceFromEvent(InputEvent @event) + { + return (string)Instance.Call("get_device_from_event", @event); + } + + + public static int GetDeviceIndexFromEvent(InputEvent @event) + { + return (int)Instance.Call("get_device_index_from_event", @event); + } + + + public static bool HasJoypad() + { + return (bool)Instance.Call("has_joypad"); + } + + + public static string GuessDeviceName() + { + return (string)Instance.Call("guess_device_name"); + } + + + public static void ResetAllActions() + { + Instance.Call("reset_all_actions"); + } + + + public static void SetKeyboardOrJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("set_keyboard_or_joypad_input_for_action", action, input, swapIfTaken); + } + + + public static InputEvent GetKeyboardOrJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true) + { + return (InputEvent)Instance.Call("get_keyboard_or_joypad_input_for_action", action, input, swapIfTaken); + } + + + public static Array GetKeyboardOrJoypadInputsForAction(string action) + { + return (Array)Instance.Call("get_keyboard_or_joypad_inputs_for_action", action); + } + + + public static string GetLabelForInput(InputEvent input) + { + return (string)Instance.Call("get_label_for_input", input); + } + + + public static string SerializeInputsForAction(string action) + { + return (string)Instance.Call("serialize_inputs_for_action", action); + } + + + public static string SerializeInputsForActions(Array actions = null) + { + if (actions == null) + { + actions = new Array(); + } + return (string)Instance.Call("serialize_inputs_for_actions", actions); + } + + + public static void DeserializeInputsForAction(string action, string serializedInputs) + { + Instance.Call("desserialize_inputs_for_action", action, serializedInputs); + } + + public static void DeserializeInputsForActions(string serializedInputs) + { + Instance.Call("deserialize_inputs_for_actions", serializedInputs); + } + + + #region Keyboard/Mouse + + public static Array GetKeyboardInputsForAction(string action) + { + return (Array)Instance.Call("get_keyboard_inputs_for_action", action); + } + + + public static InputEvent GetKeyboardInputForAction(string action) + { + return (InputEvent)Instance.Call("get_keyboard_input_for_action", action); + } + + + public static void SetKeyboardInputForAction(string action, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("set_keyboard_input_for_action", action, input, swapIfTaken); + } + + + public static void ReplaceKeyboardInputForAction(string action, InputEvent currentInput, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("replace_keyboard_input_for_action", action, currentInput, input, swapIfTaken); + } + + + public static void ReplaceKeyboardInputAtIndex(string action, int index, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("replace_keyboard_input_at_index", action, index, input, swapIfTaken); + } + + #endregion + + + #region Joypad + + public static Array GetJoypadInputsForAction(string action) + { + return (Array)Instance.Call("get_joypad_inputs_for_action", action); + } + + + public static InputEvent GetJoypadInputForAction(string action) + { + return (InputEvent)Instance.Call("get_joypad_input_for_action", action); + } + + + public static void SetJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("set_joypad_input_for_action", action, input, swapIfTaken); + } + + + public static void ReplaceJoypadInputForAction(string action, InputEvent currentInput, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("replace_joypad_input_for_action", action, currentInput, input, swapIfTaken); + } + + + public static void ReplaceJoypadInputAtIndex(string action, int index, InputEvent input, bool swapIfTaken = true) + { + Instance.Call("replace_joypad_input_at_index", action, index, input, swapIfTaken); + } + + + public static void RumbleSmall(int targetDevice = 0) + { + Instance.Call("rumble_small", targetDevice); + } + + + public static void RumbleMedium(int targetDevice = 0) + { + Instance.Call("rumble_medium", targetDevice); + } + + + public static void RumbleLarge(int targetDevice = 0) + { + Instance.Call("rumble_large", targetDevice); + } + + + public static void StartRumbleSmall(int targetDevice = 0) + { + Instance.Call("start_rumble_small", targetDevice); + } + + + public static void StartRumbleMedium(int targetDevice = 0) + { + Instance.Call("start_rumble_medium", targetDevice); + } + + + public static void StartRumbleLarge(int targetDevice = 0) + { + Instance.Call("start_rumble_large", targetDevice); + } + + + public static void StopRumble(int targetDevice = 0) + { + Instance.Call("stop_rumble", targetDevice); + } + + #endregion + } +} diff --git a/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs.uid b/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs.uid new file mode 100644 index 00000000..e80a4801 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs.uid @@ -0,0 +1 @@ +uid://cfs4dgwrfvb11 diff --git a/Zennysoft.Game.Ma/addons/input_helper/LICENSE b/Zennysoft.Game.Ma/addons/input_helper/LICENSE new file mode 100644 index 00000000..393b4deb --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-present Nathan Hoad + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg b/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg new file mode 100644 index 00000000..3c86fc3a --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg.import b/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg.import new file mode 100644 index 00000000..c4dde1cb --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/assets/update.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddixs2ish5bi6" +path="res://.godot/imported/update.svg-3137f1f7d53c08c0ae65aabe138d898b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/input_helper/assets/update.svg" +dest_files=["res://.godot/imported/update.svg-3137f1f7d53c08c0ae65aabe138d898b.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=0 +roughness/src_normal="" +process/fix_alpha_border=true +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=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd new file mode 100644 index 00000000..96fa1614 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd @@ -0,0 +1,83 @@ +@tool + +extends Control + + +signal failed() +signal updated(updated_to_version: String) + + +const TEMP_FILE_NAME = "user://temp.zip" + + +@onready var logo: TextureRect = %Logo +@onready var label: Label = $VBox/Label +@onready var http_request: HTTPRequest = $HTTPRequest +@onready var download_button: Button = %DownloadButton + +var next_version: String = "": + set(next_next_version): + next_version = next_next_version + label.text = "Version %s is available for download!" % next_version + get: + return next_version + + +func save_zip(bytes: PackedByteArray) -> void: + var file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE) + file.store_buffer(bytes) + file.flush() + + +### Signals + + +func _on_download_button_pressed() -> void: + # Safeguard the actual input helper repo from accidentally updating itself + if FileAccess.file_exists("res://examples/device_tester.gd"): + prints("You can't update the input helper from within itself.") + failed.emit() + return + + http_request.request("https://github.com/nathanhoad/godot_input_helper/archive/refs/tags/v%s.zip" % next_version) + download_button.disabled = true + download_button.text = "Downloading..." + + +func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: + if result != HTTPRequest.RESULT_SUCCESS: + failed.emit() + return + + # Save the downloaded zip + save_zip(body) + + OS.move_to_trash(ProjectSettings.globalize_path("res://addons/input_helper")) + + var zip_reader: ZIPReader = ZIPReader.new() + zip_reader.open(TEMP_FILE_NAME) + var files: PackedStringArray = zip_reader.get_files() + + var base_path = files[1] + # Remove archive folder + files.remove_at(0) + # Remove assets folder + files.remove_at(0) + + for path in files: + var new_file_path: String = path.replace(base_path, "") + if path.ends_with("/"): + DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path) + else: + var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE) + file.store_buffer(zip_reader.read_file(path)) + + zip_reader.close() + + DirAccess.remove_absolute(TEMP_FILE_NAME) + + updated.emit(next_version) + + +func _on_notes_button_pressed() -> void: + OS.shell_open("https://github.com/nathanhoad/godot_input_helper/releases/tag/v%s" % next_version) diff --git a/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd.uid b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd.uid new file mode 100644 index 00000000..248665be --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.gd.uid @@ -0,0 +1 @@ +uid://dcff0mowkn6km diff --git a/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.tscn b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.tscn new file mode 100644 index 00000000..9d9bf3f0 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/components/download_update_panel.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=3 format=3 uid="uid://b7mst0qu7vjk1"] + +[ext_resource type="Script" uid="uid://dcff0mowkn6km" path="res://addons/input_helper/components/download_update_panel.gd" id="1_4tm1k"] +[ext_resource type="Texture2D" uid="uid://ddixs2ish5bi6" path="res://addons/input_helper/assets/update.svg" id="2_j7shv"] + +[node name="DownloadUpdatePanel" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_4tm1k") + +[node name="HTTPRequest" type="HTTPRequest" parent="."] + +[node name="VBox" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -1.0 +offset_top = 9.0 +offset_right = -1.0 +offset_bottom = 9.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 10 + +[node name="Logo" type="TextureRect" parent="VBox"] +unique_name_in_owner = true +clip_contents = true +custom_minimum_size = Vector2(300, 80) +layout_mode = 2 +texture = ExtResource("2_j7shv") +stretch_mode = 5 + +[node name="Label" type="Label" parent="VBox"] +layout_mode = 2 +text = "v1.2.3 is available for download." +horizontal_alignment = 1 + +[node name="Center" type="CenterContainer" parent="VBox"] +layout_mode = 2 + +[node name="DownloadButton" type="Button" parent="VBox/Center"] +unique_name_in_owner = true +layout_mode = 2 +text = "Download and install update" + +[node name="Center2" type="CenterContainer" parent="VBox"] +layout_mode = 2 + +[node name="NotesButton" type="LinkButton" parent="VBox/Center2"] +layout_mode = 2 +text = "Read release notes..." + +[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"] +[connection signal="pressed" from="VBox/Center/DownloadButton" to="." method="_on_download_button_pressed"] +[connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"] diff --git a/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd b/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd new file mode 100644 index 00000000..06d70d30 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd @@ -0,0 +1,601 @@ +extends Node + + +signal device_changed(device: String, device_index: int) +signal keyboard_input_changed(action: String, input: InputEvent) +signal joypad_input_changed(action: String, input: InputEvent) +signal joypad_changed(device_index: int, is_connected: bool) + + +const DEVICE_KEYBOARD = "keyboard" +const DEVICE_XBOX_CONTROLLER = "xbox" +const DEVICE_SWITCH_CONTROLLER = "switch" +const DEVICE_PLAYSTATION_CONTROLLER = "playstation" +const DEVICE_STEAMDECK_CONTROLLER = "steamdeck" +const DEVICE_GENERIC = "generic" + +const SUB_DEVICE_XBOX_ONE_CONTROLLER = "xbox_one" +const SUB_DEVICE_XBOX_SERIES_CONTROLLER = "xbox_series" + +const SUB_DEVICE_PLAYSTATION3_CONTROLLER = "playstation3" +const SUB_DEVICE_PLAYSTATION4_CONTROLLER = "playstation4" +const SUB_DEVICE_PLAYSTATION5_CONTROLLER = "playstation5" + +const SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "switch_left_joycon" +const SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "switch_right_joycon" + +const XBOX_BUTTON_LABELS = ["A", "B", "X", "Y", "Back", "Guide", "Start", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"] +const XBOX_ONE_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "Guide", "Menu", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"] +const XBOX_SERIES_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "Guide", "Menu", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"] +const STEAMDECK_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "?", "Options", "Left Stick", "Right Stick", "L1", "R1", "Up", "Down", "Left", "Right", "", "", "", "", ""] +# Note: share and home buttons are not recognized +const SWITCH_BUTTON_LABELS = ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick", "Right Stick", "LS", "RS", "Up", "Down", "Left", "Right", "Capture"] +# Mapping for left and right joypad connected together (extended gamepad) +# Left Stick is Axis 0 and 1 +# Right Stick is Axis 2 and 3 +# ZL and ZR are Axis 4 and 5 +const SWITCH_EXTENDED_GAMEPAD_BUTTON_LABELS = ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick", "Right Stick", "L", "R", "Up", "Down", "Left", "Right", "Capture"] +const PLAYSTATION_3_4_BUTTON_LABELS = ["Cross", "Circle", "Square", "Triangle", "Share", "PS", "Options", "L3", "R3", "L1", "R1", "Up", "Down", "Left", "Right", "Microphone", "", "", "", "", "Touchpad"] +# Note: Microphone does not work on PC / touchpad is not recognized +const PLAYSTATION_5_BUTTON_LABELS = ["Cross", "Circle", "Square", "Triangle", "Create", "PS", "Options", "L3", "R3", "L1", "R1", "Up", "Down", "Left", "Right", "Microphone", "", "", "", "", "Touchpad"] + +const SERIAL_VERSION = 1 + +## The deadzone to ignore for joypad motion +var deadzone: float = 0.5 +## The mouse distance to ignore before movement is assumed +var mouse_motion_threshold: int = 100 +## The last known joypad device name (or "" if no joypad detected) +var last_known_joypad_device: String = get_simplified_device_name(Input.get_joy_name(0)) +## The last known joypad index +var last_known_joypad_index: int = 0 if Input.get_connected_joypads().size() > 0 else -1 + +## Used internally +var device_last_changed_at: int = 0 +var _last_known_granular_joypad_device: String = get_simplified_device_name(Input.get_joy_name(0), true) + +@onready var device: String = guess_device_name() +@onready var device_index: int = 0 if has_joypad() else -1 + + +func _ready() -> void: + process_mode = Node.PROCESS_MODE_ALWAYS + + if not Engine.has_singleton("InputHelper"): + Engine.register_singleton("InputHelper", self) + + Input.joy_connection_changed.connect(func(device_index, is_connected): joypad_changed.emit(device_index, is_connected)) + + +func _input(event: InputEvent) -> void: + var next_device: String = device + var next_device_index: int = device_index + + # Did we just press a key on the keyboard or move the mouse? + if event is InputEventKey \ + or event is InputEventMouseButton \ + or (event is InputEventMouseMotion and (event as InputEventMouseMotion).relative.length_squared() > mouse_motion_threshold): + next_device = DEVICE_KEYBOARD + next_device_index = -1 + + # Did we just use a joypad? + elif event is InputEventJoypadButton \ + or (event is InputEventJoypadMotion and abs(event.axis_value) > deadzone): + next_device = get_simplified_device_name(get_joy_name(event.device)) + last_known_joypad_device = next_device + next_device_index = event.device + last_known_joypad_index = next_device_index + + _last_known_granular_joypad_device = get_simplified_device_name(get_joy_name(event.device), true) + + # Debounce changes for 1 second because some joypads register twice in Windows for some reason + var not_changed_in_last_second = Engine.get_frames_drawn() - device_last_changed_at > Engine.get_frames_per_second() + if (next_device != device or next_device_index != device_index) and not_changed_in_last_second: + device_last_changed_at = Engine.get_frames_drawn() + + device = next_device + device_index = next_device_index + device_changed.emit(device, device_index) + + +## Get the name of a joypad +func get_joy_name(at_device_index: int) -> String: + var joy_name: String = Input.get_joy_name(at_device_index) + if joy_name == "" and Input.get_joy_info(at_device_index).size() > 0 and "xinput" in Input.get_joy_info(at_device_index).keys()[0]: + joy_name = "XInput" + return joy_name + + +## Get the device name for an event +func get_device_from_event(event: InputEvent) -> String: + if event is InputEventKey or event is InputEventMouseButton or event is InputEventMouseMotion: + return DEVICE_KEYBOARD + elif event is InputEventJoypadButton or event is InputEventJoypadMotion: + return get_simplified_device_name(get_joy_name(event.device)) + else: + return DEVICE_GENERIC + + +## Get the device name for an event +func get_device_index_from_event(event: InputEvent) -> int: + if event is InputEventJoypadButton or event is InputEventJoypadMotion: + return event.device + else: + return -1 + + +## Convert a Godot device identifier to a simplified string +func get_simplified_device_name(raw_name: String, force_granular_identifier: bool = false) -> String: + var use_granular_identifier: bool = force_granular_identifier or InputHelperSettings.get_setting(InputHelperSettings.USE_GRANULAR_DEVICE_IDENTIFIERS, false) + + var keywords: Dictionary = { + SUB_DEVICE_XBOX_ONE_CONTROLLER: ["Xbox One Controller"], + SUB_DEVICE_XBOX_SERIES_CONTROLLER: ["Xbox Series Controller", "Xbox Wireless Controller"], + DEVICE_XBOX_CONTROLLER: ["XInput", "XBox"], + SUB_DEVICE_PLAYSTATION3_CONTROLLER: ["PS3"], + SUB_DEVICE_PLAYSTATION4_CONTROLLER:["Nacon Revolution Unlimited Pro Controller", "PS4", "DUALSHOCK 4"], + SUB_DEVICE_PLAYSTATION5_CONTROLLER:["Sony DualSense", "PS5", "DualSense Wireless Controller"], + DEVICE_STEAMDECK_CONTROLLER: ["Steam"], + DEVICE_SWITCH_CONTROLLER: ["Switch", "Joy-Con (L/R)", "PowerA Core Controller"], + SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER: ["Joy-Con (L)"], + SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER: ["joy-Con (R)"], + } if use_granular_identifier else { + DEVICE_XBOX_CONTROLLER: ["XBox", "XInput"], + DEVICE_PLAYSTATION_CONTROLLER: ["Sony", "PS3", "PS5", "PS4", "DUALSHOCK 4", "DualSense", "Nacon Revolution Unlimited Pro Controller"], + DEVICE_STEAMDECK_CONTROLLER: ["Steam"], + DEVICE_SWITCH_CONTROLLER: ["Switch", "Joy-Con", "PowerA Core Controller"], + } + + for device_key in keywords: + for keyword in keywords[device_key]: + if keyword.to_lower() in raw_name.to_lower(): + return device_key + + return DEVICE_GENERIC + + +## Check if there is a connected joypad +func has_joypad() -> bool: + return Input.get_connected_joypads().size() > 0 + + +## Guess the initial input device +func guess_device_name() -> String: + if has_joypad(): + return get_simplified_device_name(get_joy_name(0)) + else: + return DEVICE_KEYBOARD + + +#region Mapping + + +func reset_all_actions() -> void: + InputMap.load_from_project_settings() + for action in InputMap.get_actions(): + var input: InputEvent = get_joypad_input_for_action(action) + if input != null: + joypad_input_changed.emit(action, input) + + input = get_keyboard_input_for_action(action) + if input != null: + keyboard_input_changed.emit(action, input) + + +## Set the key or button for an action +func set_keyboard_or_joypad_input_for_action(action: String, event: InputEvent, swap_if_taken: bool = true) -> void: + if event is InputEventKey or event is InputEventMouse: + set_keyboard_input_for_action(action, event, swap_if_taken) + elif event is InputEventJoypadButton: + set_joypad_input_for_action(action, event, swap_if_taken) + + +## Get the key or button for a given action depending on the current device +func get_keyboard_or_joypad_input_for_action(action: String) -> InputEvent: + if device == DEVICE_KEYBOARD: + return get_keyboard_input_for_action(action) + else: + return get_joypad_input_for_action(action) + + +## Get the key or button for a given action depending on the current device +func get_keyboard_or_joypad_inputs_for_action(action: String) -> Array[InputEvent]: + if device == DEVICE_KEYBOARD: + return get_keyboard_inputs_for_action(action) + else: + return get_joypad_inputs_for_action(action) + + +## Get a text label for a given input +func get_label_for_input(input: InputEvent) -> String: + if input == null: return "" + + if input is InputEventKey: + if input.physical_keycode > 0 : + var keycode: Key = DisplayServer.keyboard_get_keycode_from_physical(input.physical_keycode) if DisplayServer.keyboard_get_current_layout() > -1 else input.physical_keycode + return OS.get_keycode_string(keycode) + elif input.keycode > 0: + return OS.get_keycode_string(input.keycode) + else: + return input.as_text() + + elif input is InputEventMouseButton: + match input.button_index: + MOUSE_BUTTON_LEFT: + return "Mouse Left Button" + MOUSE_BUTTON_MIDDLE: + return "Mouse Middle Button" + MOUSE_BUTTON_RIGHT: + return "Mouse Right Button" + return "Mouse Button %d" % input.button_index + + elif input is InputEventJoypadButton: + var labels = [] + match _last_known_granular_joypad_device: + DEVICE_XBOX_CONTROLLER, DEVICE_GENERIC: + labels = XBOX_BUTTON_LABELS + SUB_DEVICE_XBOX_ONE_CONTROLLER: + labels = XBOX_ONE_BUTTON_LABELS + SUB_DEVICE_XBOX_SERIES_CONTROLLER: + labels = XBOX_SERIES_BUTTON_LABELS + SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER, SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER: + labels = SWITCH_BUTTON_LABELS + DEVICE_SWITCH_CONTROLLER: + labels = SWITCH_EXTENDED_GAMEPAD_BUTTON_LABELS + SUB_DEVICE_PLAYSTATION3_CONTROLLER, SUB_DEVICE_PLAYSTATION4_CONTROLLER: + labels = PLAYSTATION_3_4_BUTTON_LABELS + DEVICE_PLAYSTATION_CONTROLLER, SUB_DEVICE_PLAYSTATION5_CONTROLLER: + labels = PLAYSTATION_5_BUTTON_LABELS + DEVICE_STEAMDECK_CONTROLLER: + labels = STEAMDECK_BUTTON_LABELS + if input.button_index < labels.size(): + return "%s Button" % labels[input.button_index] + else: + return "Button %d" % input.button_index + + elif input is InputEventJoypadMotion: + var motion: InputEventJoypadMotion = input as InputEventJoypadMotion + match motion.axis: + JOY_AXIS_LEFT_X: + return "Left Stick %s" % ("Left" if motion.axis_value < 0 else "Right") + JOY_AXIS_LEFT_Y: + return "Left Stick %s" % ("Up" if motion.axis_value < 0 else "Down") + JOY_AXIS_RIGHT_X: + return "Right Stick %s" % ("Left" if motion.axis_value < 0 else "Right") + JOY_AXIS_RIGHT_Y: + return "Right Stick %s" % ("Up" if motion.axis_value < 0 else "Down") + JOY_AXIS_TRIGGER_LEFT: + return "Left Trigger" + JOY_AXIS_TRIGGER_RIGHT: + return "Right Trigger" + + return input.as_text() + + +## Serialize a single action's inputs. +func serialize_inputs_for_action(action: StringName) -> String: + var action_inputs: PackedStringArray = [] + var inputs: Array[InputEvent] = InputMap.action_get_events(action) + for input in inputs: + if input is InputEventKey: + var s: String = get_label_for_input(input) + var modifiers: Array[String] = [] + if input.alt_pressed: + modifiers.append("alt") + if input.shift_pressed: + modifiers.append("shift") + if input.ctrl_pressed: + modifiers.append("ctrl") + if input.meta_pressed: + modifiers.append("meta") + if not modifiers.is_empty(): + s += "|" + ",".join(modifiers) + action_inputs.append("key:%s" % s) + elif input is InputEventMouseButton: + action_inputs.append("mouse:%d" % input.button_index) + elif input is InputEventJoypadButton: + action_inputs.append("joypad:%d" % input.button_index) + elif input is InputEventJoypadMotion: + action_inputs.append("joypad:%d|%f" % [input.axis, input.axis_value]) + + return ";".join(action_inputs) + + +## Serialize a list of action inputs to string. If actions is empty then it will serialize +## all actions. +func serialize_inputs_for_actions(actions: PackedStringArray = []) -> String: + if actions == null or actions.is_empty(): + actions = InputMap.get_actions() + + var map: Dictionary = {} + for action in actions: + map[action] = serialize_inputs_for_action(action) + + return JSON.stringify({ + version = SERIAL_VERSION, + map = map + }) + + +## Deserialize a single action's inputs. +func deserialize_inputs_for_action(action: String, string: String) -> void: + InputMap.action_erase_events(action) + var action_inputs: PackedStringArray = string.split(";") + for action_input in action_inputs: + var bits: PackedStringArray = action_input.split(":") + + # Ignore any empty actions + if bits.size() < 2: continue + + var input_type: String = bits[0] + var input_details: String = bits[1] + + match input_type: + "key": + var keyboard_input = InputEventKey.new() + if "|" in input_details: + var detail_bits = input_details.split("|") + keyboard_input.keycode = OS.find_keycode_from_string(detail_bits[0]) + detail_bits = detail_bits[1].split(",") + if detail_bits.has("alt"): + keyboard_input.alt_pressed = true + if detail_bits.has("shift"): + keyboard_input.shift_pressed = true + if detail_bits.has("ctrl"): + keyboard_input.ctrl_pressed = true + if detail_bits.has("meta"): + keyboard_input.meta_pressed = true + else: + keyboard_input.keycode = OS.find_keycode_from_string(input_details) + InputMap.action_add_event(action, keyboard_input) + keyboard_input_changed.emit(action, keyboard_input) + + "mouse": + var mouse_input = InputEventMouseButton.new() + mouse_input.button_index = int(input_details) + InputMap.action_add_event(action, mouse_input) + keyboard_input_changed.emit(action, mouse_input) + + "joypad": + if "|" in str(input_details): + var joypad_motion_input = InputEventJoypadMotion.new() + var joypad_bits = input_details.split("|") + joypad_motion_input.axis = int(joypad_bits[0]) + joypad_motion_input.axis_value = float(joypad_bits[1]) + InputMap.action_add_event(action, joypad_motion_input) + joypad_input_changed.emit(action, joypad_motion_input) + else: + var joypad_input = InputEventJoypadButton.new() + joypad_input.button_index = int(input_details) + InputMap.action_add_event(action, joypad_input) + joypad_input_changed.emit(action, joypad_input) + + +## Deserialise a list of actions' inputs. +func deserialize_inputs_for_actions(string: String) -> void: + var data: Dictionary = JSON.parse_string(string) + + # Use legacy deserialization + if not data.has("version"): + _deprecated_deserialize_inputs_for_actions(string) + return + + # Version 1 + for action in data.map.keys(): + deserialize_inputs_for_action(action, data.map[action]) + + +# Load inputs from a serialized string. [deprecated] +func _deprecated_deserialize_inputs_for_actions(string: String) -> void: + var map: Dictionary = JSON.parse_string(string) + for action in map.keys(): + InputMap.action_erase_events(action) + + for key in map[action]["keyboard"]: + var keyboard_input = InputEventKey.new() + if "|" in key: + var bits = key.split("|") + keyboard_input.keycode = OS.find_keycode_from_string(bits[0]) + bits = bits[1].split(",") + if bits.has("alt"): + keyboard_input.alt_pressed = true + if bits.has("shift"): + keyboard_input.shift_pressed = true + if bits.has("ctrl"): + keyboard_input.ctrl_pressed = true + if bits.has("meta"): + keyboard_input.meta_pressed = true + else: + keyboard_input.keycode = OS.find_keycode_from_string(key) + InputMap.action_add_event(action, keyboard_input) + + for button_index in map[action]["mouse"]: + var mouse_input = InputEventMouseButton.new() + mouse_input.button_index = int(button_index) + InputMap.action_add_event(action, mouse_input) + + for button_index_or_motion in map[action]["joypad"]: + if "|" in str(button_index_or_motion): + var joypad_motion_input = InputEventJoypadMotion.new() + var bits = button_index_or_motion.split("|") + joypad_motion_input.axis = int(bits[0]) + joypad_motion_input.axis_value = float(bits[1]) + InputMap.action_add_event(action, joypad_motion_input) + else: + var joypad_input = InputEventJoypadButton.new() + joypad_input.button_index = int(button_index_or_motion) + InputMap.action_add_event(action, joypad_input) + + +#endregion + +#region Keyboard/mouse input + + +## Get all of the keys/mouse buttons used for an action. +func get_keyboard_inputs_for_action(action: String) -> Array[InputEvent]: + return InputMap.action_get_events(action).filter(func(event): + return event is InputEventKey or event is InputEventMouseButton + ) + + +## Get the first key for an action +func get_keyboard_input_for_action(action: String) -> InputEvent: + var inputs: Array[InputEvent] = get_keyboard_inputs_for_action(action) + return null if inputs.is_empty() else inputs[0] + + +## Set the key used for an action +func set_keyboard_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true) -> Error: + return _update_keyboard_input_for_action(action, input, swap_if_taken, null) + + +## Replace a specific key with another key +func replace_keyboard_input_for_action(action: String, current_input: InputEvent, input: InputEvent, swap_if_taken: bool = true) -> Error: + return _update_keyboard_input_for_action(action, input, swap_if_taken, current_input) + + +## Replace a specific key, given its index +func replace_keyboard_input_at_index(action: String, index: int, input: InputEvent, swap_if_taken: bool = true) -> Error: + var inputs: Array[InputEvent] = get_keyboard_inputs_for_action(action) + var replacing_input = InputEventKey.new() if (inputs.is_empty() or inputs.size() <= index) else inputs[index] + return _update_keyboard_input_for_action(action, input, swap_if_taken, replacing_input) + + +func _update_keyboard_input_for_action(action: String, input: InputEvent, swap_if_taken: bool, replacing_input: InputEvent = null) -> Error: + if not (input is InputEventKey or input is InputEventMouseButton): return ERR_INVALID_DATA + + var is_valid_keyboard_event = func(event): + return event is InputEventKey or event is InputEventMouseButton + + return _update_input_for_action(action, input, swap_if_taken, replacing_input, is_valid_keyboard_event, keyboard_input_changed) + + +#endregion + +#region Joypad input + + +## Get all buttons used for an action +func get_joypad_inputs_for_action(action: String) -> Array[InputEvent]: + return InputMap.action_get_events(action).filter(func(event): + return event is InputEventJoypadButton or event is InputEventJoypadMotion + ) + + +## Get the first button for an action +func get_joypad_input_for_action(action: String) -> InputEvent: + var buttons: Array[InputEvent] = get_joypad_inputs_for_action(action) + return null if buttons.is_empty() else buttons[0] + + +## Set the button for an action +func set_joypad_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true) -> Error: + return _update_joypad_input_for_action(action, input, swap_if_taken, null) + + +## Replace a specific button for an action +func replace_joypad_input_for_action(action: String, current_input: InputEvent, input: InputEventJoypadButton, swap_if_taken: bool = true) -> Error: + return _update_joypad_input_for_action(action, input, swap_if_taken, current_input) + + +## Replace a button, given its index +func replace_joypad_input_at_index(action: String, index: int, input: InputEvent, swap_if_taken: bool = true) -> Error: + var inputs: Array[InputEvent] = get_joypad_inputs_for_action(action) + var replacing_input + if inputs.is_empty() or inputs.size() <= index: + replacing_input = InputEventJoypadButton.new() + replacing_input.button_index = JOY_BUTTON_INVALID + else: + replacing_input = inputs[index] + return _update_joypad_input_for_action(action, input, swap_if_taken, replacing_input) + + +## Set the action used for a button +func _update_joypad_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true, replacing_input: InputEvent = null) -> Error: + var is_valid_keyboard_event = func(event): + return event is InputEventJoypadButton or event is InputEventJoypadMotion + + return _update_input_for_action(action, input, swap_if_taken, replacing_input, is_valid_keyboard_event, joypad_input_changed) + + +func _update_input_for_action(action: String, input: InputEvent, swap_if_taken: bool, replacing_input: InputEvent, check_is_valid: Callable, did_change_signal: Signal) -> Error: + # Find any action that is already mapped to this input + var clashing_action = "" + var clashing_event + if swap_if_taken: + for other_action in InputMap.get_actions(): + if other_action == action: continue + + for event in InputMap.action_get_events(other_action): + if event.is_match(input): + clashing_action = other_action + clashing_event = event + + # Find the key based event for the target action + var action_events: Array[InputEvent] = InputMap.action_get_events(action) + var is_replacing: bool = false + for i in range(0, action_events.size()): + var event: InputEvent = action_events[i] + if check_is_valid.call(event): + if replacing_input != null and not event.is_match(replacing_input): + continue + + # Remap the other event if there is a clashing one + if clashing_action: + _update_input_for_action(clashing_action, event, false, clashing_event, check_is_valid, did_change_signal) + + # Replace the event + action_events[i] = input + is_replacing = true + break + + # If we were trying to replace something but didn't find it then just add it to the end + if not is_replacing: + action_events.append(input) + + # Apply the changes + InputMap.action_erase_events(action) + for event in action_events: + if event != null: + InputMap.action_add_event(action, event) + + did_change_signal.emit(action, input) + + return OK + + +#endregion + +#region Rumbling + + +func rumble_small(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0.4, 0, 0.1) + + +func rumble_medium(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0, 0.7, 0.1) + + +func rumble_large(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0, 1, 0.1) + + +func start_rumble_small(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0.4, 0, 0) + + +func start_rumble_medium(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0, 0.7, 0) + + +func start_rumble_large(target_device: int = 0) -> void: + Input.start_joy_vibration(target_device, 0, 1, 0) + + +func stop_rumble(target_device: int = 0) -> void: + Input.stop_joy_vibration(target_device) + + +#endregion diff --git a/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd.uid b/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd.uid new file mode 100644 index 00000000..3286da83 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/input_helper.gd.uid @@ -0,0 +1 @@ +uid://cholww48njaeh diff --git a/Zennysoft.Game.Ma/addons/input_helper/plugin.cfg b/Zennysoft.Game.Ma/addons/input_helper/plugin.cfg new file mode 100644 index 00000000..3953a3ee --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Input Helper" +description="Detect which input device the player is using and manage input actions" +author="Nathan Hoad" +version="4.7.0" +script="plugin.gd" diff --git a/Zennysoft.Game.Ma/addons/input_helper/plugin.gd b/Zennysoft.Game.Ma/addons/input_helper/plugin.gd new file mode 100644 index 00000000..ea127b7e --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/plugin.gd @@ -0,0 +1,112 @@ +@tool +extends EditorPlugin + + +const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_input_helper/releases" +const LOCAL_CONFIG_PATH = "res://addons/input_helper/plugin.cfg" + +const DownloadDialogScene = preload("res://addons/input_helper/views/download_dialog.tscn") + + +var http_request: HTTPRequest = HTTPRequest.new() +var next_version: String = "" + + +func _enter_tree(): + add_autoload_singleton("InputHelper", "res://addons/input_helper/input_helper.gd") + + # Configure settings + InputHelperSettings.prepare() + + # Check for updates on GitHub + get_editor_interface().get_base_control().add_child(http_request) + http_request.request_completed.connect(_on_http_request_request_completed) + http_request.request(REMOTE_RELEASES_URL) + + +func _exit_tree(): + remove_autoload_singleton("InputHelper") + + if next_version != "": + remove_tool_menu_item("Update Input Helper to v%s" % next_version) + + +# Get the current version +func get_version() -> String: + var config: ConfigFile = ConfigFile.new() + config.load(LOCAL_CONFIG_PATH) + return config.get_value("plugin", "version") + + +# Convert a version number to an actually comparable number +func version_to_number(version: String) -> int: + var bits = version.split(".") + return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int() + + +### Signals + + +func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void: + http_request.queue_free() + + if result != HTTPRequest.RESULT_SUCCESS: return + + var current_version: String = get_version() + + # Work out the next version from the releases information on GitHub + var response = JSON.parse_string(body.get_string_from_utf8()) + if typeof(response) != TYPE_ARRAY: return + + # GitHub releases are in order of creation, not order of version + var versions = (response as Array).filter(func(release): + var version: String = release.tag_name.substr(1) + return version_to_number(version) > version_to_number(current_version) + ) + if versions.size() > 0: + next_version = versions[0].tag_name.substr(1) + add_tool_menu_item("Update Input Helper to v%s" % next_version, _update_input_helper) + + +func _update_input_helper() -> void: + var download_dialog := DownloadDialogScene.instantiate() + download_dialog.next_version = next_version + + var scale: float = get_editor_interface().get_editor_scale() + download_dialog.min_size = Vector2(300, 250) * scale + + download_dialog.update_finished.connect(_on_download_dialog_update_finished) + download_dialog.update_failed.connect(_on_download_dialog_update_failed) + + get_editor_interface().get_base_control().add_child(download_dialog) + download_dialog.show() + + +func _on_download_dialog_update_finished() -> void: + remove_tool_menu_item("Update Input Helper to v%s" % next_version) + + get_editor_interface().get_resource_filesystem().scan() + + print_rich("\n[b]Updated Input Helper to v%s[/b]\n" % next_version) + + var finished_dialog: AcceptDialog = AcceptDialog.new() + finished_dialog.dialog_text = "Your Input Helper is now up to date." + + var restart_addon = func(): + finished_dialog.queue_free() + get_editor_interface().call_deferred("set_plugin_enabled", "input_helper", true) + get_editor_interface().set_plugin_enabled("input_helper", false) + + finished_dialog.canceled.connect(restart_addon) + finished_dialog.confirmed.connect(restart_addon) + get_editor_interface().get_base_control().add_child(finished_dialog) + finished_dialog.popup_centered() + + +func _on_download_dialog_update_failed() -> void: + var failed_dialog: AcceptDialog = AcceptDialog.new() + failed_dialog.dialog_text = "There was a problem downloading the update." + failed_dialog.canceled.connect(func(): failed_dialog.queue_free()) + failed_dialog.confirmed.connect(func(): failed_dialog.queue_free()) + get_editor_interface().get_base_control().add_child(failed_dialog) + failed_dialog.popup_centered() diff --git a/Zennysoft.Game.Ma/addons/input_helper/plugin.gd.uid b/Zennysoft.Game.Ma/addons/input_helper/plugin.gd.uid new file mode 100644 index 00000000..a13a6434 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/plugin.gd.uid @@ -0,0 +1 @@ +uid://cul6evy00vr55 diff --git a/Zennysoft.Game.Ma/addons/input_helper/settings.gd b/Zennysoft.Game.Ma/addons/input_helper/settings.gd new file mode 100644 index 00000000..d4f48176 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/settings.gd @@ -0,0 +1,42 @@ +class_name InputHelperSettings extends Node + +const USE_GRANULAR_DEVICE_IDENTIFIERS = "devices/use_granular_device_identifiers" + +const SETTINGS_CONFIGURATION = { + USE_GRANULAR_DEVICE_IDENTIFIERS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, +} + + +static func prepare() -> void: + for key: String in SETTINGS_CONFIGURATION: + var setting_config: Dictionary = SETTINGS_CONFIGURATION[key] + var setting_name: String = "input_helper/%s" % key + if not ProjectSettings.has_setting(setting_name): + ProjectSettings.set_setting(setting_name, setting_config.value) + ProjectSettings.set_initial_value(setting_name, setting_config.value) + ProjectSettings.add_property_info({ + "name" = setting_name, + "type" = setting_config.type, + "hint" = setting_config.get("hint", PROPERTY_HINT_NONE), + "hint_string" = setting_config.get("hint_string", "") + }) + ProjectSettings.set_as_basic(setting_name, not setting_config.has("is_advanced")) + ProjectSettings.set_as_internal(setting_name, setting_config.has("is_hidden")) + + +static func set_setting(key: String, value) -> void: + if get_setting(key, value) != value: + ProjectSettings.set_setting("input_helper/%s" % key, value) + ProjectSettings.set_initial_value("input_helper/%s" % key, SETTINGS_CONFIGURATION[key].value) + ProjectSettings.save() + + +static func get_setting(key: String, default): + if ProjectSettings.has_setting("input_helper/%s" % key): + return ProjectSettings.get_setting("input_helper/%s" % key) + else: + return default diff --git a/Zennysoft.Game.Ma/addons/input_helper/settings.gd.uid b/Zennysoft.Game.Ma/addons/input_helper/settings.gd.uid new file mode 100644 index 00000000..5880d1b9 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/settings.gd.uid @@ -0,0 +1 @@ +uid://yqplm6a6focp diff --git a/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd new file mode 100644 index 00000000..13503ae4 --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd @@ -0,0 +1,30 @@ +@tool + +extends AcceptDialog + + +signal update_finished() +signal update_failed() + + +@onready var download_update_panel := $DownloadUpdatePanel + + +var next_version: String + + +func _ready() -> void: + download_update_panel.next_version = next_version + + +### Signals + + +func _on_download_update_panel_updated(updated_to_version) -> void: + update_finished.emit() + queue_free() + + +func _on_download_update_panel_failed() -> void: + update_failed.emit() + queue_free() diff --git a/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd.uid b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd.uid new file mode 100644 index 00000000..ac63efac --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.gd.uid @@ -0,0 +1 @@ +uid://1t3qhgrro2es diff --git a/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.tscn b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.tscn new file mode 100644 index 00000000..451a451d --- /dev/null +++ b/Zennysoft.Game.Ma/addons/input_helper/views/download_dialog.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=3 uid="uid://bownbkcmm43gn"] + +[ext_resource type="PackedScene" uid="uid://b7mst0qu7vjk1" path="res://addons/input_helper/components/download_update_panel.tscn" id="1_37q37"] +[ext_resource type="Script" uid="uid://1t3qhgrro2es" path="res://addons/input_helper/views/download_dialog.gd" id="1_ltktf"] + +[node name="DownloadDialog" type="AcceptDialog"] +initial_position = 2 +ok_button_text = "Close" +script = ExtResource("1_ltktf") + +[node name="DownloadUpdatePanel" parent="." instance=ExtResource("1_37q37")] + +[connection signal="failed" from="DownloadUpdatePanel" to="." method="_on_download_update_panel_failed"] +[connection signal="updated" from="DownloadUpdatePanel" to="." method="_on_download_update_panel_updated"] diff --git a/Zennysoft.Game.Ma/project.godot b/Zennysoft.Game.Ma/project.godot index 446cf58a..e9463fe2 100644 --- a/Zennysoft.Game.Ma/project.godot +++ b/Zennysoft.Game.Ma/project.godot @@ -23,6 +23,7 @@ DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd" DialogueController="*res://src/game/DialogueController.cs" AudioManager="*res://src/audio/AudioManager.cs" BgmPlayer="*res://src/audio/BGMPlayer.cs" +InputHelper="*res://addons/input_helper/input_helper.gd" [dialogue_manager] @@ -43,7 +44,7 @@ project/assembly_name="Ma" [editor_plugins] -enabled=PackedStringArray("res://addons/dialogue_manager/plugin.cfg", "res://addons/dungeon_floor_layout/plugin.cfg", "res://addons/special_floor_layout_node/plugin.cfg") +enabled=PackedStringArray("res://addons/dialogue_manager/plugin.cfg", "res://addons/input_helper/plugin.cfg") [file_customization] @@ -72,9 +73,7 @@ texture={ ui_accept={ "deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"pressure":0.0,"pressed":false,"script":null) -, 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":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) ] } ui_select={ @@ -83,34 +82,40 @@ ui_select={ } ui_cancel={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -, 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":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) -, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":false,"script":null) +"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) , 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":70,"key_label":0,"unicode":102,"location":0,"echo":false,"script":null) ] } +ui_focus_next={ +"deadzone": 0.5, +"events": [] +} +ui_focus_prev={ +"deadzone": 0.5, +"events": [] +} ui_left={ "deadzone": 0.5, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) -, 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":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) ] } ui_right={ "deadzone": 0.5, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) -, 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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) ] } ui_up={ "deadzone": 0.5, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) -, 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":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":11,"pressure":0.0,"pressed":false,"script":null) ] } ui_down={ "deadzone": 0.5, -"events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) -, 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":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) ] } MoveUp={ diff --git a/Zennysoft.Game.Ma/src/Components/EquipmentComponent.cs b/Zennysoft.Game.Ma/src/Components/EquipmentComponent.cs index f1bfd4d2..cbf1bec6 100644 --- a/Zennysoft.Game.Ma/src/Components/EquipmentComponent.cs +++ b/Zennysoft.Game.Ma/src/Components/EquipmentComponent.cs @@ -1,4 +1,6 @@ using Chickensoft.Collections; +using Godot; +using System; using Zennysoft.Ma.Adapter; using Zennysoft.Ma.Adapter.Entity; @@ -17,6 +19,8 @@ public class EquipmentComponent : IEquipmentComponent public AutoProp _equippedAccessory; + public event Action EquipmentChanged; + public int BonusAttack => _equippedWeapon.Value.BonusAttack + _equippedArmor.Value.BonusAttack + _equippedAccessory.Value.BonusAttack; public int BonusDefense => _equippedWeapon.Value.BonusDefense + _equippedArmor.Value.BonusDefense + _equippedAccessory.Value.BonusDefense; @@ -51,6 +55,7 @@ public class EquipmentComponent : IEquipmentComponent _equippedArmor.OnNext(armor); if (equipable is Accessory accessory) _equippedAccessory.OnNext(accessory); + EquipmentChanged?.Invoke(equipable); } public void Unequip(EquipableItem equipable) @@ -61,6 +66,7 @@ public class EquipmentComponent : IEquipmentComponent _equippedArmor.OnNext(new Armor()); if (equipable is Accessory accessory) _equippedAccessory.OnNext(new Accessory()); + EquipmentChanged?.Invoke(equipable); } public bool IsItemEquipped(InventoryItem item) diff --git a/Zennysoft.Game.Ma/src/Components/HealthComponent.cs b/Zennysoft.Game.Ma/src/Components/HealthComponent.cs index 6a5d7183..e1b9350f 100644 --- a/Zennysoft.Game.Ma/src/Components/HealthComponent.cs +++ b/Zennysoft.Game.Ma/src/Components/HealthComponent.cs @@ -60,11 +60,15 @@ public class HealthComponent : IHealthComponent public void SetMaximumHealth(int health) { _maximumHP.OnNext(health); + + if (_currentHP.Value > _maximumHP.Value) + _currentHP.OnNext(_maximumHP.Value); } - public void RaiseMaximumHP(int raiseAmount) + public void RaiseMaximumHP(int raiseAmount, bool restoreHP = true) { - _maximumHP.OnNext(raiseAmount); - Heal(raiseAmount); + _maximumHP.OnNext(_maximumHP.Value + raiseAmount); + if (restoreHP) + Heal(raiseAmount); } } diff --git a/Zennysoft.Game.Ma/src/Components/VTComponent.cs b/Zennysoft.Game.Ma/src/Components/VTComponent.cs index fa9c137d..458306fd 100644 --- a/Zennysoft.Game.Ma/src/Components/VTComponent.cs +++ b/Zennysoft.Game.Ma/src/Components/VTComponent.cs @@ -49,9 +49,18 @@ public class VTComponent : IVTComponent _currentVT.OnNext(cappedAmount); } - public void RaiseMaximumVT(int raiseAmount) + public void RaiseMaximumVT(int raiseAmount, bool restoreVT = true) { - _maximumVT.OnNext(raiseAmount); - Restore(raiseAmount); + _maximumVT.OnNext(_maximumVT.Value + raiseAmount); + if (restoreVT) + Restore(raiseAmount); + } + + public void SetMaximumVT(int vt) + { + _maximumVT.OnNext(vt); + + if (_currentVT.Value > _maximumVT.Value) + _currentVT.OnNext(_maximumVT.Value); } } diff --git a/Zennysoft.Game.Ma/src/app/App.cs b/Zennysoft.Game.Ma/src/app/App.cs index b6831d8b..bf18afe4 100644 --- a/Zennysoft.Game.Ma/src/app/App.cs +++ b/Zennysoft.Game.Ma/src/app/App.cs @@ -4,9 +4,13 @@ using Chickensoft.GodotNodeInterfaces; using Chickensoft.Introspection; using Godot; using Godot.Collections; +using NathanHoad; using SimpleInjector.Lifestyles; +using System.IO.Abstractions; using System.Linq; +using System.Threading.Tasks; using Zennysoft.Game.Abstractions; +using Zennysoft.Game.Implementation; using Zennysoft.Ma.Adapter; namespace Zennysoft.Game.Ma; @@ -26,6 +30,8 @@ public partial class App : Node, IApp [Node] private LoadingScreen LoadingScreen { get; set; } = default!; + [Node] private OptionsMenu OptionsMenu { get; set; } + public IInstantiator Instantiator { get; set; } = default!; IAppRepo IProvide.Value() => AppRepo; @@ -35,25 +41,50 @@ public partial class App : Node, IApp public AppLogic.IBinding AppBinding { get; set; } = default!; private Array _progress; + private SimpleInjector.Container _container; private AutoProp _loadedScene = new(string.Empty); private bool _loadingGame = false; private bool _loadingEnemyViewer = false; + private string _optionsSavePath = string.Empty; + private string _controllerSavePath = string.Empty; + private ISaveFileManager _saveFileManager; public void Initialize() { - var container = new SimpleInjector.Container(); - container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle(); - container.RegisterSingleton(); - container.RegisterSingleton(); + _container = new SimpleInjector.Container(); + _container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle(); + _container.RegisterSingleton(); + _container.RegisterSingleton(); + _container.RegisterSingleton(); + _container.RegisterSingleton(); + + _saveFileManager = _container.GetInstance(); + _optionsSavePath = $"{OS.GetUserDataDir()}/options.json"; + _controllerSavePath = $"{OS.GetUserDataDir()}/controls.json"; + + Task.Run(() => _saveFileManager.ReadFromFile(_optionsSavePath).ContinueWith((data) => + { + if (data.IsCompletedSuccessfully) + OptionsMenu.CallDeferred("Load", data.Result); + })); + + Task.Run(() => _saveFileManager.ReadFromFile(_controllerSavePath).ContinueWith((data) => + { + if (data.IsCompletedSuccessfully) + OptionsMenu.Controller.CallDeferred(nameof(OptionsMenu.Controller.LoadControllerInput), data.Result); + })); MainMenu.StartGame += OnStartGame; MainMenu.EnemyViewer += OnEnemyViewer; + MainMenu.Options += OnOptions; MainMenu.Quit += OnQuit; _loadedScene.Changed += OnGameLoaded; - AppRepo = container.GetInstance(); - AppLogic = container.GetInstance(); + OptionsMenu.OptionsMenuExited += OptionsMenu_OptionsMenuExited; + + AppRepo = _container.GetInstance(); + AppLogic = _container.GetInstance(); AppLogic.Set(AppRepo); AppLogic.Set(new AppLogic.Data()); @@ -63,6 +94,16 @@ public partial class App : Node, IApp this.Provide(); } + private async void OptionsMenu_OptionsMenuExited() + { + var saveFileManager = _container.GetInstance(); + await saveFileManager.WriteToFile(OptionsMenu.OptionsData, _optionsSavePath); + var controllerOutput = InputHelper.SerializeInputsForActions(); + await saveFileManager.WriteToFile(controllerOutput, _controllerSavePath); + OptionsMenu.Hide(); + MainMenu.OptionsButton.GrabFocus(); + } + private void OnGameLoaded(string sceneName) { LoadingScreen.Hide(); @@ -86,6 +127,7 @@ public partial class App : Node, IApp { ResourceLoader.LoadThreadedRequest(GAME_SCENE_PATH); _loadingGame = true; + MainMenu.ReleaseFocus(); MainMenu.Hide(); }) .Handle((in AppLogic.Output.ShowMainMenu _) => @@ -109,7 +151,6 @@ public partial class App : Node, IApp }); AppLogic.Start(); - MainMenu.Show(); } public override void _Process(double delta) @@ -135,7 +176,11 @@ public partial class App : Node, IApp private void OnEnemyViewer() => AppLogic.Input(new AppLogic.Input.EnemyViewerOpened()); - private void OnLoadGame() => AppLogic.Input(new AppLogic.Input.LoadGame()); + private async void OnOptions() + { + OptionsMenu.Show(); + OptionsMenu.MasterVolumeSlider.GrabFocus(); + } public void OnQuit() => AppLogic.Input(new AppLogic.Input.QuitGame()); diff --git a/Zennysoft.Game.Ma/src/app/App.tscn b/Zennysoft.Game.Ma/src/app/App.tscn index 19604b25..45f7334d 100644 --- a/Zennysoft.Game.Ma/src/app/App.tscn +++ b/Zennysoft.Game.Ma/src/app/App.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=4 format=3 uid="uid://cagfc5ridmteu"] +[gd_scene load_steps=5 format=3 uid="uid://cagfc5ridmteu"] [ext_resource type="Script" uid="uid://d1f8blk5ucqvq" path="res://src/app/App.cs" id="1_rt73h"] [ext_resource type="PackedScene" uid="uid://rfvnddfqufho" path="res://src/menu/MainMenu.tscn" id="2_1uiag"] +[ext_resource type="PackedScene" uid="uid://drkl3btdy6uxj" path="res://src/options/OptionsMenu.tscn" id="2_v0mgf"] [ext_resource type="PackedScene" uid="uid://cpjlj7kxdhv16" path="res://src/menu/LoadingScreen.tscn" id="3_3st5l"] [node name="App" type="Node"] @@ -11,6 +12,9 @@ script = ExtResource("1_rt73h") [node name="LoadingScreen" parent="." instance=ExtResource("3_3st5l")] unique_name_in_owner = true -[node name="MainMenu" parent="." instance=ExtResource("2_1uiag")] +[node name="OptionsMenu" parent="." instance=ExtResource("2_v0mgf")] unique_name_in_owner = true visible = false + +[node name="MainMenu" parent="." instance=ExtResource("2_1uiag")] +unique_name_in_owner = true diff --git a/Zennysoft.Game.Ma/src/audio/AMB/amb_beach.wav.import b/Zennysoft.Game.Ma/src/audio/AMB/amb_beach.wav.import index 8815f991..9fa43bf5 100644 --- a/Zennysoft.Game.Ma/src/audio/AMB/amb_beach.wav.import +++ b/Zennysoft.Game.Ma/src/audio/AMB/amb_beach.wav.import @@ -3,12 +3,12 @@ importer="wav" type="AudioStreamWAV" uid="uid://ddii3pi8x75xc" -path="res://.godot/imported/amb_beach.wav-e64adf8f733e6a108ae15edd5f0499ab.sample" +path="res://.godot/imported/amb_beach.wav-046e4f838e50e43a1aba1a754b92aad6.sample" [deps] -source_file="res://src/audio/amb/amb_beach.wav" -dest_files=["res://.godot/imported/amb_beach.wav-e64adf8f733e6a108ae15edd5f0499ab.sample"] +source_file="res://src/audio/AMB/amb_beach.wav" +dest_files=["res://.godot/imported/amb_beach.wav-046e4f838e50e43a1aba1a754b92aad6.sample"] [params] diff --git a/Zennysoft.Game.Ma/src/audio/AMB/amb_perlin.wav.import b/Zennysoft.Game.Ma/src/audio/AMB/amb_perlin.wav.import index 2ebba139..008a47dd 100644 --- a/Zennysoft.Game.Ma/src/audio/AMB/amb_perlin.wav.import +++ b/Zennysoft.Game.Ma/src/audio/AMB/amb_perlin.wav.import @@ -3,12 +3,12 @@ importer="wav" type="AudioStreamWAV" uid="uid://ym4ur8a2qxhp" -path="res://.godot/imported/amb_perlin.wav-dea63667b2a56d37d48ba209f56f8900.sample" +path="res://.godot/imported/amb_perlin.wav-ba6da0d5591f392e4aca7d2f85c4dfc2.sample" [deps] -source_file="res://src/audio/amb/amb_perlin.wav" -dest_files=["res://.godot/imported/amb_perlin.wav-dea63667b2a56d37d48ba209f56f8900.sample"] +source_file="res://src/audio/AMB/amb_perlin.wav" +dest_files=["res://.godot/imported/amb_perlin.wav-ba6da0d5591f392e4aca7d2f85c4dfc2.sample"] [params] diff --git a/Zennysoft.Game.Ma/src/audio/AMB/amb_white_noise.wav.import b/Zennysoft.Game.Ma/src/audio/AMB/amb_white_noise.wav.import index b24cc4fc..09f8922b 100644 --- a/Zennysoft.Game.Ma/src/audio/AMB/amb_white_noise.wav.import +++ b/Zennysoft.Game.Ma/src/audio/AMB/amb_white_noise.wav.import @@ -3,12 +3,12 @@ importer="wav" type="AudioStreamWAV" uid="uid://b7wxddjx3qw5o" -path="res://.godot/imported/amb_white_noise.wav-c98b45aa94120bc0c660bf2d6af1c696.sample" +path="res://.godot/imported/amb_white_noise.wav-d316dd05afe429f6bcdda594285ad718.sample" [deps] -source_file="res://src/audio/amb/amb_white_noise.wav" -dest_files=["res://.godot/imported/amb_white_noise.wav-c98b45aa94120bc0c660bf2d6af1c696.sample"] +source_file="res://src/audio/AMB/amb_white_noise.wav" +dest_files=["res://.godot/imported/amb_white_noise.wav-d316dd05afe429f6bcdda594285ad718.sample"] [params] diff --git a/Zennysoft.Game.Ma/src/audio/AMB/amb_wind_loop_altar.wav.import b/Zennysoft.Game.Ma/src/audio/AMB/amb_wind_loop_altar.wav.import index 3cce56a9..23099e87 100644 --- a/Zennysoft.Game.Ma/src/audio/AMB/amb_wind_loop_altar.wav.import +++ b/Zennysoft.Game.Ma/src/audio/AMB/amb_wind_loop_altar.wav.import @@ -3,12 +3,12 @@ importer="wav" type="AudioStreamWAV" uid="uid://bmiitw4fcs68e" -path="res://.godot/imported/amb_wind_loop_altar.wav-b9d60e3c3c10ec00833903539a7f3796.sample" +path="res://.godot/imported/amb_wind_loop_altar.wav-e766e3db29faa01ad6dbaa8cb18d7de6.sample" [deps] -source_file="res://src/audio/amb/amb_wind_loop_altar.wav" -dest_files=["res://.godot/imported/amb_wind_loop_altar.wav-b9d60e3c3c10ec00833903539a7f3796.sample"] +source_file="res://src/audio/AMB/amb_wind_loop_altar.wav" +dest_files=["res://.godot/imported/amb_wind_loop_altar.wav-e766e3db29faa01ad6dbaa8cb18d7de6.sample"] [params] diff --git a/Zennysoft.Game.Ma/src/audio/AudioManager.cs b/Zennysoft.Game.Ma/src/audio/AudioManager.cs index 73d3c0c4..96323cb8 100644 --- a/Zennysoft.Game.Ma/src/audio/AudioManager.cs +++ b/Zennysoft.Game.Ma/src/audio/AudioManager.cs @@ -18,6 +18,7 @@ public partial class AudioManager : Node var soundEffects = Enum.GetValues(typeof(SoundEffect)); foreach (var effect in soundEffects) _sfxDictionary.Add((SoundEffect)effect, GD.Load(sfxPath + effect + ".ogg")); + _audioPlayer.Bus = "SFX"; } public void Play(SoundEffect soundEffect) diff --git a/Zennysoft.Game.Ma/src/enemy/behaviors/IHaveFleeBehavior.cs.uid b/Zennysoft.Game.Ma/src/enemy/behaviors/IHaveFleeBehavior.cs.uid index 646ae4f5..b682b20c 100644 --- a/Zennysoft.Game.Ma/src/enemy/behaviors/IHaveFleeBehavior.cs.uid +++ b/Zennysoft.Game.Ma/src/enemy/behaviors/IHaveFleeBehavior.cs.uid @@ -1 +1 @@ -uid://dnh47mgvakhvh +uid://ry5iewox61vs diff --git a/Zennysoft.Game.Ma/src/enemy/state/states/EnemyLogic.State.Flee.cs.uid b/Zennysoft.Game.Ma/src/enemy/state/states/EnemyLogic.State.Flee.cs.uid index 52e8cad6..106b99c4 100644 --- a/Zennysoft.Game.Ma/src/enemy/state/states/EnemyLogic.State.Flee.cs.uid +++ b/Zennysoft.Game.Ma/src/enemy/state/states/EnemyLogic.State.Flee.cs.uid @@ -1 +1 @@ -uid://btpqk2uvk834y +uid://t0j1h13hgo0t diff --git a/Zennysoft.Game.Ma/src/game/Game.cs b/Zennysoft.Game.Ma/src/game/Game.cs index 59a0ada1..778b12ec 100644 --- a/Zennysoft.Game.Ma/src/game/Game.cs +++ b/Zennysoft.Game.Ma/src/game/Game.cs @@ -267,7 +267,7 @@ public partial class Game : Node3D, IGame }) .Handle((in GameState.Output.OpenInventoryMenu _) => { - InGameUI.InventoryMenu.RefreshInventoryScreen(); + //InGameUI.InventoryMenu.RefreshInventoryScreen(); InGameUI.InventoryMenu.Show(); InGameUI.InventoryMenu.SetProcessInput(true); }) @@ -460,7 +460,7 @@ public partial class Game : Node3D, IGame if (item is IStackable stackableItem && stackableItem.Count > 1) stackableItem.SetCount(stackableItem.Count - 1); else - GameRepo.RemoveItemFromInventory(item); + _player.Inventory.Remove(item); } private void MovePlayer(Transform3D spawnPoint) => _player.TeleportPlayer(spawnPoint); diff --git a/Zennysoft.Game.Ma/src/items/Inventory.cs b/Zennysoft.Game.Ma/src/items/Inventory.cs index 9e8ee4ff..9e9cf032 100644 --- a/Zennysoft.Game.Ma/src/items/Inventory.cs +++ b/Zennysoft.Game.Ma/src/items/Inventory.cs @@ -19,6 +19,7 @@ public partial class Inventory : Node, IInventory private const int _maxInventorySize = 20; public event Action BroadcastMessage; + public event Action InventoryChanged; public Inventory() { @@ -32,10 +33,7 @@ public partial class Inventory : Node, IInventory { var isAdded = TryAdd(item); if (isAdded) - { BroadcastMessage?.Invoke($"{item.ItemName} picked up."); - item.QueueFree(); - } else BroadcastMessage?.Invoke($"Could not pick up {item.ItemName}."); @@ -48,6 +46,7 @@ public partial class Inventory : Node, IInventory return false; Items.Add(inventoryItem); + InventoryChanged?.Invoke(); return true; } @@ -57,11 +56,15 @@ public partial class Inventory : Node, IInventory return false; Items.Insert(index, inventoryItem); + InventoryChanged?.Invoke(); return true; } - public void Remove(InventoryItem inventoryItem) => Items.Remove(inventoryItem); - + public void Remove(InventoryItem inventoryItem) + { + Items.Remove(inventoryItem); + InventoryChanged?.Invoke(); + } public void Sort(EquipableItem currentWeapon, EquipableItem currentArmor, EquipableItem currentAccessory) { diff --git a/Zennysoft.Game.Ma/src/items/InventoryItemStats.cs b/Zennysoft.Game.Ma/src/items/InventoryItemStats.cs index 1aae90aa..91f6e87e 100644 --- a/Zennysoft.Game.Ma/src/items/InventoryItemStats.cs +++ b/Zennysoft.Game.Ma/src/items/InventoryItemStats.cs @@ -9,9 +9,11 @@ namespace Zennysoft.Game.Ma; public abstract partial class InventoryItemStats : Resource { [Export] + [Save("equipment_name")] public string Name { get; set; } [Export(PropertyHint.MultilineText)] + [Save("equipment_description")] public string Description { get; set; } [Export(PropertyHint.Range, "0, 1, 0.01")] @@ -30,33 +32,43 @@ public abstract partial class InventoryItemStats : Resource public double BonusLuck { get; set; } = 0.05; [Export] + [Save("equipment_bonus_hp")] + public int BonusHP { get; set; } = 0; + + [Export] + [Save("equipment_bonus_vt")] + public int BonusVT { get; set; } = 0; + + [Export] + [Save("equipment_aeolic_resist")] public int AeolicResistance { get; set; } = 0; [Export] + [Save("equipment_telluric_resist")] public int TelluricResistance { get; set; } = 0; [Export] + [Save("equipment_hydric_resist")] public int HydricResistance { get; set; } = 0; [Export] + [Save("equipment_igneous_resist")] public int IgneousResistance { get; set; } = 0; [Export] + [Save("equipment_ferrum_resist")] public int FerrumResistance { get; set; } = 0; [Export(PropertyHint.Range, "0, 25, 0.1")] + [Save("equipment_throw_speed")] public float ThrowSpeed { get; set; } = 12.0f; [Export(PropertyHint.Range, "0, 999, 1")] - public int IncreaseMaxHPAmount { get; set; } - - [Export(PropertyHint.Range, "0, 999, 1")] - public int IncreaseMaxVTAmount { get; set; } - - [Export(PropertyHint.Range, "0, 999, 1")] + [Save("equipment_throw_damage")] public int ThrowDamage { get; set; } = 5; [Export] + [Save("equipment_item_tag")] public ItemTag ItemTag { get; set; } = ItemTag.None; [Export] diff --git a/Zennysoft.Game.Ma/src/items/ItemDatabase.cs b/Zennysoft.Game.Ma/src/items/ItemDatabase.cs index 3ef1da75..552d857e 100644 --- a/Zennysoft.Game.Ma/src/items/ItemDatabase.cs +++ b/Zennysoft.Game.Ma/src/items/ItemDatabase.cs @@ -62,7 +62,6 @@ public class ItemDatabase weaponScene.Stats = weaponInfo; if (!database.Contains(weaponScene)) database.Add(weaponScene); - database.Add(weaponScene); } foreach (var accessory in accessoryResources) @@ -72,7 +71,6 @@ public class ItemDatabase accessoryScene.Stats = accessoryInfo; if (!database.Contains(accessoryScene)) database.Add(accessoryScene); - database.Add(accessoryScene); } foreach (var throwable in throwableResources) @@ -82,7 +80,6 @@ public class ItemDatabase throwableItemScene.Stats = throwableItemInfo; if (!database.Contains(throwableItemScene)) database.Add(throwableItemScene); - database.Add(throwableItemScene); } foreach (var consumable in consumableResources) @@ -92,7 +89,6 @@ public class ItemDatabase consumableItemScene.Stats = consumableItemInfo; if (!database.Contains(consumableItemScene)) database.Add(consumableItemScene); - database.Add(consumableItemScene); } foreach (var effectItem in effectResources) @@ -102,7 +98,6 @@ public class ItemDatabase effectItemScene.Stats = effectItemInfo; if (!database.Contains(effectItemScene)) database.Add(effectItemScene); - database.Add(effectItemScene); } Items = [.. database]; diff --git a/Zennysoft.Game.Ma/src/items/accessory/Accessory.cs b/Zennysoft.Game.Ma/src/items/accessory/Accessory.cs index 38a52d4c..b1964dc9 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/Accessory.cs +++ b/Zennysoft.Game.Ma/src/items/accessory/Accessory.cs @@ -28,6 +28,14 @@ public partial class Accessory : EquipableItem public override float ThrowSpeed => Stats.ThrowSpeed; + public override int BonusAttack => Stats.BonusAttack; + + public override int BonusDefense => Stats.BonusDefense; + + public override int BonusHP => Stats.BonusHP; + + public override int BonusVT => Stats.BonusVT; + public override ElementalResistanceSet ElementalResistance => new ElementalResistanceSet(Stats.AeolicResistance, Stats.HydricResistance, Stats.IgneousResistance, Stats.FerrumResistance, Stats.TelluricResistance); [Save("accessory_tag")] diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskAvarice.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskAvarice.tres index 09247e0e..5d69270a 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskAvarice.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskAvarice.tres @@ -5,18 +5,21 @@ [resource] script = ExtResource("1_xqaot") -ATKUp = 0 -DEFUp = 0 -LuckUp = 0.1 -MaxHPUp = 0 -MaxVTUp = 0 AccessoryTag = 0 Name = "Mask of the Goddess of Avarice" Description = "Raises Luck" -Texture = ExtResource("1_q42cv") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.3 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 12.0 -HealHPAmount = 0 -HealVTAmount = 0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 ThrowDamage = 5 ItemTag = 0 +Texture = ExtResource("1_q42cv") diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskDestruction.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskDestruction.tres index 57b05d79..35927852 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskDestruction.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskDestruction.tres @@ -1,17 +1,25 @@ [gd_resource type="Resource" script_class="AccessoryStats" load_steps=3 format=3 uid="uid://d4bcem2nup7ef"] [ext_resource type="Texture2D" uid="uid://db7i7iy5gagae" path="res://src/items/accessory/textures/MASK 02.PNG" id="1_0p1ot"] -[ext_resource type="Script" path="res://src/items/accessory/AccessoryStats.cs" id="1_vef66"] +[ext_resource type="Script" uid="uid://b8arlmivk68b" path="res://src/items/accessory/AccessoryStats.cs" id="1_vef66"] [resource] script = ExtResource("1_vef66") -ATKUp = 3 -DEFUp = 0 -LUCKUp = 0.0 -MaxHPUp = 0 -MaxVTUp = 0 -AccessoryTags = [0] +AccessoryTag = 0 Name = "Mask of the Goddess of Destruction" Description = "Raises ATK." -Texture = ExtResource("1_0p1ot") SpawnRate = 0.1 +BonusAttack = 3 +BonusDefense = 0 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_0p1ot") diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskGuilt.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskGuilt.tres index 576ecd81..0ec604db 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskGuilt.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskGuilt.tres @@ -1,17 +1,25 @@ [gd_resource type="Resource" script_class="AccessoryStats" load_steps=3 format=3 uid="uid://bejy3lpudgawg"] [ext_resource type="Texture2D" uid="uid://db7i7iy5gagae" path="res://src/items/accessory/textures/MASK 02.PNG" id="1_0k42r"] -[ext_resource type="Script" path="res://src/items/accessory/AccessoryStats.cs" id="1_cgxkh"] +[ext_resource type="Script" uid="uid://b8arlmivk68b" path="res://src/items/accessory/AccessoryStats.cs" id="1_cgxkh"] [resource] script = ExtResource("1_cgxkh") -ATKUp = 1 -DEFUp = 1 -LUCKUp = 0.0 -MaxHPUp = 30 -MaxVTUp = 30 -AccessoryTags = [] +AccessoryTag = 0 Name = "Mask of the Goddess of Guilt" Description = "Raises MAX HP, MAX VT, ATK, DEF" -Texture = ExtResource("1_0k42r") SpawnRate = 0.1 +BonusAttack = 2 +BonusDefense = 2 +BonusLuck = 0.05 +BonusHP = 25 +BonusVT = 25 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_0k42r") diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskObstinance.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskObstinance.tres index 541ee6bc..5260c51e 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskObstinance.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskObstinance.tres @@ -1,17 +1,25 @@ [gd_resource type="Resource" script_class="AccessoryStats" load_steps=3 format=3 uid="uid://ddwyaxxqvk52h"] [ext_resource type="Texture2D" uid="uid://db7i7iy5gagae" path="res://src/items/accessory/textures/MASK 02.PNG" id="1_1uw37"] -[ext_resource type="Script" path="res://src/items/accessory/AccessoryStats.cs" id="1_kuyyj"] +[ext_resource type="Script" uid="uid://b8arlmivk68b" path="res://src/items/accessory/AccessoryStats.cs" id="1_kuyyj"] [resource] script = ExtResource("1_kuyyj") -ATKUp = 0 -DEFUp = 3 -LUCKUp = 0.0 -MaxHPUp = 0 -MaxVTUp = 0 -AccessoryTags = [] +AccessoryTag = 0 Name = "Mask of the Goddess of Obstinance" Description = "Raises DEF." -Texture = ExtResource("1_1uw37") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 3 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_1uw37") diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskSuffering.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskSuffering.tres index fc0b28b3..0abd3170 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskSuffering.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskSuffering.tres @@ -1,17 +1,25 @@ [gd_resource type="Resource" script_class="AccessoryStats" load_steps=3 format=3 uid="uid://d02kuxaus43mk"] -[ext_resource type="Script" path="res://src/items/accessory/AccessoryStats.cs" id="1_3iw2y"] +[ext_resource type="Script" uid="uid://b8arlmivk68b" path="res://src/items/accessory/AccessoryStats.cs" id="1_3iw2y"] [ext_resource type="Texture2D" uid="uid://db7i7iy5gagae" path="res://src/items/accessory/textures/MASK 02.PNG" id="1_vc77e"] [resource] script = ExtResource("1_3iw2y") -ATKUp = 0 -DEFUp = 0 -LUCKUp = 0.0 -MaxHPUp = 0 -MaxVTUp = 50 -AccessoryTags = [] +AccessoryTag = 0 Name = "Mask of the Goddess of Suffering" Description = "Raises MAX VT" -Texture = ExtResource("1_vc77e") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 50 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_vc77e") diff --git a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskZeal.tres b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskZeal.tres index 2d4792f8..911a4420 100644 --- a/Zennysoft.Game.Ma/src/items/accessory/resources/MaskZeal.tres +++ b/Zennysoft.Game.Ma/src/items/accessory/resources/MaskZeal.tres @@ -1,17 +1,25 @@ [gd_resource type="Resource" script_class="AccessoryStats" load_steps=3 format=3 uid="uid://b0bxwp55mcyyp"] -[ext_resource type="Script" path="res://src/items/accessory/AccessoryStats.cs" id="1_0u4rq"] +[ext_resource type="Script" uid="uid://b8arlmivk68b" path="res://src/items/accessory/AccessoryStats.cs" id="1_0u4rq"] [ext_resource type="Texture2D" uid="uid://db7i7iy5gagae" path="res://src/items/accessory/textures/MASK 02.PNG" id="1_ggv41"] [resource] script = ExtResource("1_0u4rq") -ATKUp = 0 -DEFUp = 0 -LUCKUp = 0.0 -MaxHPUp = 50 -MaxVTUp = 0 -AccessoryTags = [] +AccessoryTag = 0 Name = "Mask of the Goddess of Zeal" Description = "Raises MAX HP" -Texture = ExtResource("1_ggv41") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 50 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_ggv41") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/Acceptance.tres b/Zennysoft.Game.Ma/src/items/armor/resources/Acceptance.tres index 59ba7b8d..94a4491b 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/Acceptance.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/Acceptance.tres @@ -5,9 +5,6 @@ [resource] script = ExtResource("1_si4wu") -Name = "Acceptance" -Description = "+9 DEF" -Defense = 9 _telluricResistance = 0.0 _aeolicResistance = 0.0 _hydricResistance = 0.0 @@ -16,9 +13,17 @@ _ferrumResistance = 0.0 Name = "Acceptance" Description = "+9 DEF" SpawnRate = 0.01 +BonusAttack = 0 +BonusDefense = 9 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 12.0 -HealHPAmount = 0 -HealVTAmount = 0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 ThrowDamage = 5 ItemTag = 0 Texture = ExtResource("1_p85jd") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/AtonersAdornments.tres b/Zennysoft.Game.Ma/src/items/armor/resources/AtonersAdornments.tres index 6a7f0674..1503b882 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/AtonersAdornments.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/AtonersAdornments.tres @@ -5,18 +5,25 @@ [resource] script = ExtResource("1_6r2bl") -Defense = 1 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Atoner's Adornments" Description = "+1 DEF" -Texture = ExtResource("1_588l8") SpawnRate = 0.25 +BonusAttack = 0 +BonusDefense = 1 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 12.0 -HealHPAmount = 0 -HealVTAmount = 0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 ThrowDamage = 5 ItemTag = 0 +Texture = ExtResource("1_588l8") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/CeremonialVestments.tres b/Zennysoft.Game.Ma/src/items/armor/resources/CeremonialVestments.tres index f782304a..877393fc 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/CeremonialVestments.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/CeremonialVestments.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://dnu241lh47oqd"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_0qtvf"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_0qtvf"] [ext_resource type="Texture2D" uid="uid://vvhbibkslh57" path="res://src/items/armor/textures/CEREMONIAL.PNG" id="1_s4gpg"] [resource] script = ExtResource("1_0qtvf") -Defense = 2 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Ceremonial Vestments" Description = "+2 DEF" -Texture = ExtResource("1_s4gpg") SpawnRate = 0.2 +BonusAttack = 0 +BonusDefense = 2 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_s4gpg") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/DevicLayer.tres b/Zennysoft.Game.Ma/src/items/armor/resources/DevicLayer.tres index fe227fa4..14f8910d 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/DevicLayer.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/DevicLayer.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://4s7wjsb7eb6e"] [ext_resource type="Texture2D" uid="uid://381ddynsa3gc" path="res://src/items/armor/textures/DEVIC.PNG" id="1_5ik54"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_w3lql"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_w3lql"] [resource] script = ExtResource("1_w3lql") -Defense = 7 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Devic Layers" Description = "+7 DEF" -Texture = ExtResource("1_5ik54") SpawnRate = 0.05 +BonusAttack = 0 +BonusDefense = 7 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_5ik54") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/GoddessRobe.tres b/Zennysoft.Game.Ma/src/items/armor/resources/GoddessRobe.tres index 3acdd119..faa3c0ba 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/GoddessRobe.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/GoddessRobe.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://dc0qjer88chme"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_3mc7x"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_3mc7x"] [ext_resource type="Texture2D" uid="uid://c57kuugsc2lti" path="res://src/items/armor/textures/GODDESS.PNG" id="1_5vleh"] [resource] script = ExtResource("1_3mc7x") -Defense = 8 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Goddess' Robe" Description = "+8 DEF" -Texture = ExtResource("1_5vleh") SpawnRate = 0.03 +BonusAttack = 0 +BonusDefense = 8 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_5vleh") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/IronCage.tres b/Zennysoft.Game.Ma/src/items/armor/resources/IronCage.tres index 6b2c1ca9..63715cc8 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/IronCage.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/IronCage.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://ceqnyutl7y7t4"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_iqj2w"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_iqj2w"] [ext_resource type="Texture2D" uid="uid://cj5m8qkpqrcx4" path="res://src/items/armor/textures/IRON.PNG" id="1_jyoar"] [resource] script = ExtResource("1_iqj2w") -Defense = 4 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Iron Cage" Description = "+4 DEF" -Texture = ExtResource("1_jyoar") SpawnRate = 0.15 +BonusAttack = 0 +BonusDefense = 4 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_jyoar") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/LogisticiansGarb.tres b/Zennysoft.Game.Ma/src/items/armor/resources/LogisticiansGarb.tres index 4d89df4f..bf311795 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/LogisticiansGarb.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/LogisticiansGarb.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://chhxktntl4k8r"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_frqfh"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_frqfh"] [ext_resource type="Texture2D" uid="uid://2qvbtq2obsac" path="res://src/items/armor/textures/LOGISTIAN.PNG" id="1_kh3n2"] [resource] script = ExtResource("1_frqfh") -Defense = 5 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Logistician's Garb" Description = "+5 DEF" -Texture = ExtResource("1_kh3n2") SpawnRate = 0.08 +BonusAttack = 0 +BonusDefense = 5 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_kh3n2") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/Stoic.tres b/Zennysoft.Game.Ma/src/items/armor/resources/Stoic.tres index 99e461b4..7904418b 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/Stoic.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/Stoic.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://d3l8aa87tevgt"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_dh6tr"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_dh6tr"] [ext_resource type="Texture2D" uid="uid://ddtscpfj6nf6i" path="res://src/items/armor/textures/STOIC.PNG" id="1_xpphu"] [resource] script = ExtResource("1_dh6tr") -Defense = 6 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Stoic" Description = "+6 DEF" -Texture = ExtResource("1_xpphu") SpawnRate = 0.05 +BonusAttack = 0 +BonusDefense = 6 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_xpphu") diff --git a/Zennysoft.Game.Ma/src/items/armor/resources/WoodenArmament.tres b/Zennysoft.Game.Ma/src/items/armor/resources/WoodenArmament.tres index cef85d99..fb1fc29c 100644 --- a/Zennysoft.Game.Ma/src/items/armor/resources/WoodenArmament.tres +++ b/Zennysoft.Game.Ma/src/items/armor/resources/WoodenArmament.tres @@ -1,17 +1,29 @@ [gd_resource type="Resource" script_class="ArmorStats" load_steps=3 format=3 uid="uid://dq4c6an78qa4q"] -[ext_resource type="Script" path="res://src/items/armor/ArmorStats.cs" id="1_bkpin"] +[ext_resource type="Script" uid="uid://dqtp6ewvttoyu" path="res://src/items/armor/ArmorStats.cs" id="1_bkpin"] [ext_resource type="Texture2D" uid="uid://dghvd33w32q63" path="res://src/items/armor/textures/WOODEN.PNG" id="1_vs6ua"] [resource] script = ExtResource("1_bkpin") -Defense = 3 -TelluricResistance = 0.0 -AeolicResistance = 0.0 -HydricResistance = 0.0 -IgneousResistance = 0.0 -FerrumResistance = 0.0 +_telluricResistance = 0.0 +_aeolicResistance = 0.0 +_hydricResistance = 0.0 +_igneousResistance = 0.0 +_ferrumResistance = 0.0 Name = "Wooden Armament" Description = "+3 DEF" -Texture = ExtResource("1_vs6ua") SpawnRate = 0.3 +BonusAttack = 0 +BonusDefense = 3 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_vs6ua") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/AmritShard.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/AmritShard.tres index fd5a3ef2..4ac5745e 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/AmritShard.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/AmritShard.tres @@ -1,16 +1,29 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://d0cxrf0nldona"] [ext_resource type="Texture2D" uid="uid://ttmu3vttq8yo" path="res://src/items/consumable/textures/amrit shard.PNG" id="1_f1n30"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_riwik"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_riwik"] [resource] script = ExtResource("2_riwik") HealHPAmount = 60 -RaiseHPAmount = 16 HealVTAmount = 0 -RaiseVTAmount = 0 +PermanentRaiseHPAmount = 16 +PermanentRaiseVTAmount = 0 Name = "Amrit Shard" Description = "A droplet of the heavenly elixir, frozen in time. Restores 60 HP. If HP full, raises MAX HP by 16." -Texture = ExtResource("1_f1n30") SpawnRate = 0.05 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_f1n30") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfRemnant.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfRemnant.tres index 9e1895b5..19934a67 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfRemnant.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfRemnant.tres @@ -1,15 +1,28 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://dns281deffo6q"] [ext_resource type="Texture2D" uid="uid://bg47n2tmintm0" path="res://src/items/consumable/textures/past self remnant.PNG" id="1_rc8t1"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_e61q8"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_e61q8"] [resource] script = ExtResource("2_e61q8") -HealHPAmount = 1000 -RaiseHPAmount = 25 +HealHPAmount = 999 HealVTAmount = 0 -RaiseVTAmount = 0 +PermanentRaiseHPAmount = 25 +PermanentRaiseVTAmount = 0 Name = "Past Self's Fragment" Description = "Restores all HP. If HP full, raises MAX HP by 25." -Texture = ExtResource("1_rc8t1") SpawnRate = 0.05 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_rc8t1") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfSpirit.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfSpirit.tres index 86591377..14e78af5 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfSpirit.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/PastSelfSpirit.tres @@ -1,15 +1,28 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://bnec53frgyue8"] [ext_resource type="Texture2D" uid="uid://cj0x1u7rknrvy" path="res://src/items/consumable/textures/past self spirit.PNG" id="1_jx43p"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_wmtl1"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_wmtl1"] [resource] script = ExtResource("2_wmtl1") HealHPAmount = 0 -RaiseHPAmount = 0 HealVTAmount = 1000 -RaiseVTAmount = 20 +PermanentRaiseHPAmount = 0 +PermanentRaiseVTAmount = 20 Name = "Past Self's Spirit" Description = "Restores all VT. If VT full, raises MAX VT by 20." -Texture = ExtResource("1_jx43p") SpawnRate = 0.05 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_jx43p") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/SteloFragment.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/SteloFragment.tres index cc461392..9e426c19 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/SteloFragment.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/SteloFragment.tres @@ -1,16 +1,29 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://75fpkwfp0t0k"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="1_f8ogj"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="1_f8ogj"] [ext_resource type="Texture2D" uid="uid://dbl5v5i1s3m2u" path="res://src/items/consumable/textures/stelo fragment.PNG" id="1_ic5xm"] [resource] script = ExtResource("1_f8ogj") HealHPAmount = 0 -RaiseHPAmount = 0 HealVTAmount = 30 -RaiseVTAmount = 10 +PermanentRaiseHPAmount = 0 +PermanentRaiseVTAmount = 10 Name = "Stelo Fragment" Description = "A small gathered piece of the former heavens. Restores 30 VT. If VT full, raises MAX VT by 10." -Texture = ExtResource("1_ic5xm") SpawnRate = 0.5 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_ic5xm") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/SunaFragment.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/SunaFragment.tres index e3203b65..48523dfa 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/SunaFragment.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/SunaFragment.tres @@ -1,16 +1,29 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://ypw2yg10430p"] [ext_resource type="Texture2D" uid="uid://bqyjjdgub6iem" path="res://src/items/consumable/textures/suna fragment.PNG" id="1_ldd10"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_41hue"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_41hue"] [resource] script = ExtResource("2_41hue") HealHPAmount = 0 -RaiseHPAmount = 0 HealVTAmount = 60 -RaiseVTAmount = 20 +PermanentRaiseHPAmount = 0 +PermanentRaiseVTAmount = 20 Name = "Suna Fragment" Description = "A large gathered piece of the former heavens. Restores 60 VT. If VT full, raises MAX VT by 20." -Texture = ExtResource("1_ldd10") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_ldd10") diff --git a/Zennysoft.Game.Ma/src/items/consumable/resources/YdunicShard.tres b/Zennysoft.Game.Ma/src/items/consumable/resources/YdunicShard.tres index 5af69b69..94931c7b 100644 --- a/Zennysoft.Game.Ma/src/items/consumable/resources/YdunicShard.tres +++ b/Zennysoft.Game.Ma/src/items/consumable/resources/YdunicShard.tres @@ -1,16 +1,29 @@ [gd_resource type="Resource" script_class="ConsumableItemStats" load_steps=3 format=3 uid="uid://lu0ddu3538p6"] [ext_resource type="Texture2D" uid="uid://dw06kkltgk3sv" path="res://src/items/consumable/textures/ydunic fragment.PNG" id="1_4llax"] -[ext_resource type="Script" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_q4pyq"] +[ext_resource type="Script" uid="uid://cymeea1n4f04i" path="res://src/items/consumable/ConsumableItemStats.cs" id="2_q4pyq"] [resource] script = ExtResource("2_q4pyq") HealHPAmount = 30 -RaiseHPAmount = 8 -HealVTAmount = 0 -RaiseVTAmount = 0 +HealVTAmount = 8 +PermanentRaiseHPAmount = 0 +PermanentRaiseVTAmount = 0 Name = "Ydunic Shard" Description = "A fragment of the divine fruit, frozen in time. Restores 30 HP. If HP full, raises MAX HP by 8." -Texture = ExtResource("1_4llax") SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 +ThrowSpeed = 12.0 +ThrowDamage = 5 +ItemTag = 0 +Texture = ExtResource("1_4llax") diff --git a/Zennysoft.Game.Ma/src/items/throwable/resources/GeomanticDice.tres b/Zennysoft.Game.Ma/src/items/throwable/resources/GeomanticDice.tres index b03539ec..9d27fd1d 100644 --- a/Zennysoft.Game.Ma/src/items/throwable/resources/GeomanticDice.tres +++ b/Zennysoft.Game.Ma/src/items/throwable/resources/GeomanticDice.tres @@ -5,9 +5,8 @@ [resource] script = ExtResource("1_ewck5") -Name = "Geomantic Dice" -Description = "Inflicts base damage when thrown. -Use item to change Affinity." +HealHPAmount = 10 +HealVTAmount = 5 ThrowableItemTag = 3 ElementType = 0 UsableItemTag = 0 @@ -17,9 +16,17 @@ Name = "Geomantic Dice" Description = "Inflicts base damage when thrown. Use item to change Affinity." SpawnRate = 1.0 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 20.0 -HealHPAmount = 0 -HealVTAmount = 0 ThrowDamage = 20 ItemTag = 0 Texture = ExtResource("1_jhits") diff --git a/Zennysoft.Game.Ma/src/items/throwable/resources/Gospel of Dimension.tres b/Zennysoft.Game.Ma/src/items/throwable/resources/Gospel of Dimension.tres index 5f316d6e..cf5bfbf9 100644 --- a/Zennysoft.Game.Ma/src/items/throwable/resources/Gospel of Dimension.tres +++ b/Zennysoft.Game.Ma/src/items/throwable/resources/Gospel of Dimension.tres @@ -5,9 +5,9 @@ [resource] script = ExtResource("2_m680r") -Name = "Gospel of Dimension" -Description = "Teleports target to a random location." -ThrowableItemTag = 0 +HealHPAmount = 0 +HealVTAmount = 0 +ThrowableItemTag = 4 ElementType = 0 UsableItemTag = 0 MinimumCount = 1 @@ -15,9 +15,17 @@ MaximumCount = 8 Name = "Gospel of Dimension" Description = "Teleports target to a random location." SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 20.0 -HealHPAmount = 0 -HealVTAmount = 0 +IncreaseMaxHPAmount = 0 +IncreaseMaxVTAmount = 0 ThrowDamage = 20 ItemTag = 0 Texture = ExtResource("1_xt2mp") diff --git a/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres b/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres index db516041..353295eb 100644 --- a/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres +++ b/Zennysoft.Game.Ma/src/items/throwable/resources/SpellSignKnowledge.tres @@ -5,17 +5,27 @@ [resource] script = ExtResource("1_s3pq7") -Name = "Spell Sign: Knowledge" -Description = "Doubles experience points earned. Effect is temporary." +HealHPAmount = 8 +HealVTAmount = 3 ThrowableItemTag = 1 ElementType = 0 UsableItemTag = 0 +MinimumCount = 1 +MaximumCount = 8 Name = "Spell Sign: Knowledge" Description = "Doubles experience points earned. Effect is temporary." SpawnRate = 0.1 +BonusAttack = 0 +BonusDefense = 0 +BonusLuck = 0.05 +BonusHP = 0 +BonusVT = 0 +AeolicResistance = 0 +TelluricResistance = 0 +HydricResistance = 0 +IgneousResistance = 0 +FerrumResistance = 0 ThrowSpeed = 12.0 -HealHPAmount = 0 -HealVTAmount = 0 ThrowDamage = 5 ItemTag = 0 Texture = ExtResource("1_3605p") diff --git a/Zennysoft.Game.Ma/src/map/Map.tscn b/Zennysoft.Game.Ma/src/map/Map.tscn index 720a3498..b29c35eb 100644 --- a/Zennysoft.Game.Ma/src/map/Map.tscn +++ b/Zennysoft.Game.Ma/src/map/Map.tscn @@ -80,7 +80,7 @@ unique_name_in_owner = true script = ExtResource("2_00xd7") FolderName = "SetAFloors" FloorOdds = Array[float]([0.0, 1.0]) -GoldSproingy = 1.0 +Sproingy = 1.0 [node name="Overworld" type="Node" parent="MapOrder"] script = ExtResource("3_v14r0") diff --git a/Zennysoft.Game.Ma/src/menu/MainMenu.cs b/Zennysoft.Game.Ma/src/menu/MainMenu.cs index 820eec82..493d5fb1 100644 --- a/Zennysoft.Game.Ma/src/menu/MainMenu.cs +++ b/Zennysoft.Game.Ma/src/menu/MainMenu.cs @@ -26,6 +26,8 @@ public partial class MainMenu : Control, IMainMenu [Node] public IButton GalleryButton { get; set; } = default!; + [Node] public IButton OptionsButton { get; set; } = default!; + [Node] public IButton QuitButton { get; set; } = default!; [Signal] @@ -35,6 +37,8 @@ public partial class MainMenu : Control, IMainMenu [Signal] public delegate void GalleryEventHandler(); [Signal] + public delegate void OptionsEventHandler(); + [Signal] public delegate void QuitEventHandler(); public void OnReady() @@ -42,6 +46,7 @@ public partial class MainMenu : Control, IMainMenu StartGameButton.Pressed += OnStartGamePressed; EnemyViewerButton.Pressed += EnemyViewerButton_Pressed; GalleryButton.Pressed += GalleryButton_Pressed; + OptionsButton.Pressed += OptionsButton_Pressed; QuitButton.Pressed += OnQuitPressed; StartGameButton.GrabFocus(); } @@ -57,6 +62,7 @@ public partial class MainMenu : Control, IMainMenu StartGameButton.Pressed -= OnStartGamePressed; EnemyViewerButton.Pressed -= EnemyViewerButton_Pressed; GalleryButton.Pressed -= GalleryButton_Pressed; + OptionsButton.Pressed -= OptionsButton_Pressed; QuitButton.Pressed -= OnQuitPressed; } @@ -66,5 +72,7 @@ public partial class MainMenu : Control, IMainMenu private void EnemyViewerButton_Pressed() => EmitSignal(SignalName.EnemyViewer); + private void OptionsButton_Pressed() => EmitSignal(SignalName.Options); + public void OnQuitPressed() => EmitSignal(SignalName.Quit); } diff --git a/Zennysoft.Game.Ma/src/menu/MainMenu.tscn b/Zennysoft.Game.Ma/src/menu/MainMenu.tscn index 6596d45c..aba6810f 100644 --- a/Zennysoft.Game.Ma/src/menu/MainMenu.tscn +++ b/Zennysoft.Game.Ma/src/menu/MainMenu.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3 uid="uid://rfvnddfqufho"] +[gd_scene load_steps=3 format=3 uid="uid://rfvnddfqufho"] [ext_resource type="Script" uid="uid://14b7o2c6cgry" path="res://src/menu/MainMenu.cs" id="1_y6722"] +[ext_resource type="Shortcut" uid="uid://dumkrjur22k2a" path="res://src/ui/ButtonShortcut.tres" id="2_7fwjx"] [node name="MainMenu" type="Control"] layout_mode = 3 @@ -27,7 +28,6 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -mouse_filter = 0 theme_override_constants/margin_left = 100 theme_override_constants/margin_top = 100 theme_override_constants/margin_right = 100 @@ -37,33 +37,56 @@ theme_override_constants/margin_bottom = 100 layout_mode = 2 size_flags_horizontal = 0 size_flags_vertical = 0 -mouse_filter = 0 [node name="StartGameButton" type="Button" parent="MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 +focus_neighbor_bottom = NodePath("../EnemyViewerButton") +focus_next = NodePath("../EnemyViewerButton") theme_override_colors/font_focus_color = Color(0.976471, 0.827451, 0, 1) +shortcut = ExtResource("2_7fwjx") text = "Start Game" [node name="EnemyViewerButton" type="Button" parent="MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 focus_neighbor_bottom = NodePath("../GalleryButton") +focus_next = NodePath("../GalleryButton") +focus_previous = NodePath("../StartGameButton") theme_override_colors/font_focus_color = Color(0.976471, 0.827451, 0, 1) +shortcut = ExtResource("2_7fwjx") text = "Enemy Viewer" [node name="GalleryButton" type="Button" parent="MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 focus_neighbor_top = NodePath("../EnemyViewerButton") -focus_neighbor_bottom = NodePath("../QuitButton") +focus_neighbor_bottom = NodePath("../OptionsButton") +focus_next = NodePath("../OptionsButton") +focus_previous = NodePath("../EnemyViewerButton") theme_override_colors/font_focus_color = Color(0.976471, 0.827451, 0, 1) +shortcut = ExtResource("2_7fwjx") text = "Gallery" +[node name="OptionsButton" type="Button" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +focus_neighbor_top = NodePath("../GalleryButton") +focus_neighbor_bottom = NodePath("../QuitButton") +focus_next = NodePath("../QuitButton") +focus_previous = NodePath("../GalleryButton") +theme_override_colors/font_focus_color = Color(0.976471, 0.827451, 0, 1) +shortcut = ExtResource("2_7fwjx") +text = "Options" + [node name="QuitButton" type="Button" parent="MarginContainer/VBoxContainer"] unique_name_in_owner = true layout_mode = 2 -focus_neighbor_top = NodePath("../GalleryButton") +focus_neighbor_top = NodePath("../OptionsButton") +focus_neighbor_bottom = NodePath(".") +focus_next = NodePath(".") +focus_previous = NodePath("../OptionsButton") theme_override_colors/font_focus_color = Color(0.976471, 0.827451, 0, 1) +shortcut = ExtResource("2_7fwjx") text = "Quit " diff --git a/Zennysoft.Game.Ma/src/options/InputMapButton.cs b/Zennysoft.Game.Ma/src/options/InputMapButton.cs new file mode 100644 index 00000000..0590d3f4 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputMapButton.cs @@ -0,0 +1,19 @@ +using Godot; + +namespace Zennysoft.Game.Ma; + +public abstract partial class InputMapButton : Button +{ + [Signal] public delegate void RemapEventHandler(InputMapButton buttonBeingRemapped); + + public string Action { get; set; } + + public InputEvent InputEvent { get; set; } + + public InputMapButton() + { + Pressed += RemapButton_Pressed; + } + + private void RemapButton_Pressed() => EmitSignal(SignalName.Remap, this); +} diff --git a/Zennysoft.Game.Ma/src/options/InputMapButton.cs.uid b/Zennysoft.Game.Ma/src/options/InputMapButton.cs.uid new file mode 100644 index 00000000..72829c1e --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputMapButton.cs.uid @@ -0,0 +1 @@ +uid://bhfrdlnv7xyf6 diff --git a/Zennysoft.Game.Ma/src/options/InputMapper.cs b/Zennysoft.Game.Ma/src/options/InputMapper.cs new file mode 100644 index 00000000..88d52f88 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputMapper.cs @@ -0,0 +1,191 @@ +using Chickensoft.AutoInject; +using Chickensoft.Introspection; +using Godot; +using NathanHoad; +using SimpleInjector.Lifestyles; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; +using Zennysoft.Game.Abstractions; +using Zennysoft.Game.Implementation; + +namespace Zennysoft.Game.Ma; + +[Meta(typeof(IAutoNode))] +public partial class InputMapper : PanelContainer +{ + public override void _Notification(int what) => this.Notify(what); + + [Node] public VBoxContainer ActionList { get; set; } + + [Node] public KeyboardRemapButton MoveForwardKeyboard { get; set; } + [Node] public JoypadRemapButton MoveForwardController { get; set; } + [Node] public KeyboardRemapButton MoveLeftKeyboard { get; set; } + [Node] public JoypadRemapButton MoveLeftController { get; set; } + [Node] public KeyboardRemapButton MoveRightKeyboard { get; set; } + [Node] public JoypadRemapButton MoveRightController { get; set; } + [Node] public KeyboardRemapButton MoveBackwardKeyboard { get; set; } + [Node] public JoypadRemapButton MoveBackwardController { get; set; } + [Node] public KeyboardRemapButton StrafeLeftKeyboard { get; set; } + [Node] public JoypadRemapButton StrafeLeftController { get; set; } + [Node] public KeyboardRemapButton StrafeRightKeyboard { get; set; } + [Node] public JoypadRemapButton StrafeRightController { get; set; } + [Node] public KeyboardRemapButton AttackKeyboard { get; set; } + [Node] public JoypadRemapButton AttackController { get; set; } + [Node] public KeyboardRemapButton InteractKeyboard { get; set; } + [Node] public JoypadRemapButton InteractController { get; set; } + [Node] public KeyboardRemapButton InventoryKeyboard { get; set; } + [Node] public JoypadRemapButton InventoryController { get; set; } + [Node] public KeyboardRemapButton SortKeyboard { get; set; } + [Node] public JoypadRemapButton SortController { get; set; } + + private Button _remappingButton = null; + private InputEvent _remappingAction = null; + + private List _actionJoyMap = []; + private List _actionKeyMap = []; + + private SimpleInjector.Container _container; + private ISaveFileManager _saveFileManager; + + [Signal] public delegate void SaveControllerInputEventHandler(); + + public void OnReady() + { + _container = new SimpleInjector.Container(); + _container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle(); + _container.RegisterSingleton(); + _container.RegisterSingleton(); + _saveFileManager = _container.GetInstance(); + + MoveForwardController.Action = GameInputs.MoveUp; + MoveForwardKeyboard.Action = GameInputs.MoveUp; + MoveLeftController.Action = GameInputs.MoveLeft; + MoveLeftKeyboard.Action = GameInputs.MoveLeft; + MoveRightController.Action = GameInputs.MoveRight; + MoveRightKeyboard.Action = GameInputs.MoveRight; + MoveBackwardController.Action = GameInputs.MoveDown; + MoveBackwardKeyboard.Action = GameInputs.MoveDown; + StrafeLeftController.Action = GameInputs.StrafeLeft; + StrafeLeftKeyboard.Action = GameInputs.StrafeLeft; + StrafeRightController.Action = GameInputs.StrafeRight; + StrafeRightKeyboard.Action = GameInputs.StrafeRight; + AttackController.Action = GameInputs.Attack; + AttackKeyboard.Action = GameInputs.Attack; + InteractController.Action = GameInputs.Interact; + InteractKeyboard.Action = GameInputs.Interact; + InventoryController.Action = GameInputs.Inventory; + InventoryKeyboard.Action = GameInputs.Inventory; + SortController.Action = GameInputs.InventorySort; + SortKeyboard.Action = GameInputs.InventorySort; + + _actionJoyMap.Add(MoveForwardController); + _actionJoyMap.Add(MoveLeftController); + _actionJoyMap.Add(MoveRightController); + _actionJoyMap.Add(MoveBackwardController); + _actionJoyMap.Add(StrafeLeftController); + _actionJoyMap.Add(StrafeRightController); + _actionJoyMap.Add(AttackController); + _actionJoyMap.Add(InteractController); + _actionJoyMap.Add(InventoryController); + _actionJoyMap.Add(SortController); + + _actionKeyMap.Add(MoveForwardKeyboard); + _actionKeyMap.Add(MoveLeftKeyboard); + _actionKeyMap.Add(MoveRightKeyboard); + _actionKeyMap.Add(MoveBackwardKeyboard); + _actionKeyMap.Add(StrafeLeftKeyboard); + _actionKeyMap.Add(StrafeRightKeyboard); + _actionKeyMap.Add(AttackKeyboard); + _actionKeyMap.Add(InteractKeyboard); + _actionKeyMap.Add(InventoryKeyboard); + _actionKeyMap.Add(SortKeyboard); + + MoveForwardKeyboard.Remap += OnRemap; + MoveForwardController.Remap += OnRemap; + MoveLeftKeyboard.Remap += OnRemap; + MoveLeftController.Remap += OnRemap; + MoveRightKeyboard.Remap += OnRemap; + MoveRightController.Remap += OnRemap; + MoveBackwardKeyboard.Remap += OnRemap; + MoveBackwardController.Remap += OnRemap; + StrafeLeftKeyboard.Remap += OnRemap; + StrafeLeftController.Remap += OnRemap; + StrafeRightKeyboard.Remap += OnRemap; + StrafeRightController.Remap += OnRemap; + AttackKeyboard.Remap += OnRemap; + AttackController.Remap += OnRemap; + InteractKeyboard.Remap += OnRemap; + InteractController.Remap += OnRemap; + InventoryKeyboard.Remap += OnRemap; + InventoryController.Remap += OnRemap; + SortKeyboard.Remap += OnRemap; + SortController.Remap += OnRemap; + + InputHelper.JoypadInputChanged += (string action, InputEvent input) => + { + var buttonChanged = _actionJoyMap.SingleOrDefault(x => x.Action == action); + if (buttonChanged != null) + { + buttonChanged.SetProcessInput(false); + buttonChanged.Text = InputHelper.GetLabelForInput(input); + } + var allButtons = _actionKeyMap.Concat(_actionJoyMap); + foreach (var button in allButtons) + button.Disabled = false; + }; + + InputHelper.KeyboardInputChanged += (string action, InputEvent input) => + { + var buttonChanged = _actionKeyMap.SingleOrDefault(x => x.Action == action); + if (buttonChanged != null) + { + buttonChanged.SetProcessInput(false); + buttonChanged.Text = InputHelper.GetLabelForInput(input); + } + var allButtons = _actionKeyMap.Concat(_actionJoyMap); + foreach (var button in allButtons) + button.Disabled = false; + }; + } + + public void LoadControllerInput(string jsonData) + { + InputHelper.DeserializeInputsForActions(jsonData); + InitializeButtonText(); + } + + public void InitializeButtonText() + { + MoveForwardKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.MoveUp)); + MoveForwardController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.MoveUp)); + MoveLeftKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.MoveLeft)); + MoveLeftController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.MoveLeft)); + MoveRightKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.MoveRight)); + MoveRightController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.MoveRight)); + MoveBackwardKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.MoveDown)); + MoveBackwardController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.MoveDown)); + StrafeLeftKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.StrafeLeft)); + StrafeLeftController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.StrafeLeft)); + StrafeRightKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.StrafeRight)); + StrafeRightController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.StrafeRight)); + AttackKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.Attack)); + AttackController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.Attack)); + InteractKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.Interact)); + InteractController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.Interact)); + InventoryKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.Inventory)); + InventoryController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.Inventory)); + SortKeyboard.Text = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.InventorySort)); + SortController.Text = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.InventorySort)); + } + + private void OnRemap(InputMapButton inputButton) + { + inputButton.Text = "..."; + inputButton.SetProcessInput(true); + var allButtons = _actionKeyMap.Concat(_actionJoyMap); + foreach (var button in allButtons) + button.Disabled = true; + ReleaseFocus(); + } +} diff --git a/Zennysoft.Game.Ma/src/options/InputMapper.cs.uid b/Zennysoft.Game.Ma/src/options/InputMapper.cs.uid new file mode 100644 index 00000000..2c0fb92d --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputMapper.cs.uid @@ -0,0 +1 @@ +uid://c6lw5yp8p0wb5 diff --git a/Zennysoft.Game.Ma/src/options/InputMapper.tscn b/Zennysoft.Game.Ma/src/options/InputMapper.tscn new file mode 100644 index 00000000..f1d02dc2 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputMapper.tscn @@ -0,0 +1,361 @@ +[gd_scene load_steps=4 format=3 uid="uid://dk5esf6mm6kte"] + +[ext_resource type="Script" uid="uid://c6lw5yp8p0wb5" path="res://src/options/InputMapper.cs" id="1_rwvs3"] +[ext_resource type="Script" uid="uid://b70br20xue678" path="res://src/options/KeyboardRemapButton.cs" id="2_fmxfy"] +[ext_resource type="Script" uid="uid://bo7vk56h1lr07" path="res://src/options/JoypadRemapButton.cs" id="3_yis0i"] + +[node name="InputMapper" type="PanelContainer"] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -451.0 +offset_top = -451.0 +offset_right = 449.0 +offset_bottom = 449.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_rwvs3") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +mouse_filter = 0 +theme_override_constants/margin_left = 25 +theme_override_constants/margin_top = 25 +theme_override_constants/margin_right = 25 +theme_override_constants/margin_bottom = 25 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ActionList" type="VBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/separation = 10 + +[node name="Header" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Header"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Actions" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Header"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Header"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Keyboard" +horizontal_alignment = 1 + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Header"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label3" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Header"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Controller" +horizontal_alignment = 1 + +[node name="Move Forward" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Forward"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Move Forward" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Forward"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveForwardKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Forward"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Forward"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveForwardController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Forward"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Move Left" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Left"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Move Left" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Left"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveLeftKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Left"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Left"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveLeftController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Left"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Move Right" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Right"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Move Right" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Right"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveRightKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Right"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Right"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveRightController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Right"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Move Backward" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Backward"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Move Backward" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Backward"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveBackwardKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Backward"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Backward"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MoveBackwardController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Move Backward"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Strafe Left" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Left"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Strafe Left" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Left"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StrafeLeftKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Left"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Left"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StrafeLeftController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Left"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Strafe Right" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Right"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Strafe Right" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Right"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StrafeRightKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Right"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Right"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="StrafeRightController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Strafe Right"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Attack" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Attack"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Attack" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Attack"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="AttackKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Attack"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Attack"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="AttackController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Attack"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Interact" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Interact"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Interact" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Interact"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="InteractKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Interact"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Interact"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="InteractController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Interact"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Open Inventory" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Open Inventory"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Open Inventory" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Open Inventory"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="InventoryKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Open Inventory"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Open Inventory"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="InventoryController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Open Inventory"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") + +[node name="Sort Inventory" type="HBoxContainer" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Sort Inventory"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +text = "Sort Inventory" +horizontal_alignment = 2 + +[node name="Control" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Sort Inventory"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="SortKeyboard" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Sort Inventory"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 35) +layout_mode = 2 +script = ExtResource("2_fmxfy") + +[node name="Control2" type="Control" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Sort Inventory"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="SortController" type="Button" parent="MarginContainer/VBoxContainer/ScrollContainer/ActionList/Sort Inventory"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +script = ExtResource("3_yis0i") diff --git a/Zennysoft.Game.Ma/src/options/InputToString.cs.uid b/Zennysoft.Game.Ma/src/options/InputToString.cs.uid new file mode 100644 index 00000000..726dcf37 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/InputToString.cs.uid @@ -0,0 +1 @@ +uid://bwd066xv65pbi diff --git a/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs b/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs new file mode 100644 index 00000000..fb90adb4 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs @@ -0,0 +1,41 @@ +using Godot; +using NathanHoad; + +namespace Zennysoft.Game.Ma; + +public partial class JoypadRemapButton : InputMapButton +{ + public override void _Ready() + { + SetProcessInput(false); + } + + public override void _Input(InputEvent @event) + { + if (@event.IsActionType() && InputHelper.GetDeviceFromEvent(@event) != "keyboard") + { + if (InputHelper.GetJoypadInputForAction(GameInputs.Pause).IsMatch(@event)) + { + InputHelper.SetJoypadInputForAction(Action, InputEvent); + } + else + { + InputHelper.ReplaceJoypadInputForAction(Action, InputEvent, @event, true); + if (Action == GameInputs.MoveUp) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiUp, InputEvent, @event, true); + else if (Action == GameInputs.MoveLeft) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiLeft, InputEvent, @event, true); + else if (Action == GameInputs.MoveRight) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiRight, InputEvent, @event, true); + else if (Action == GameInputs.MoveDown) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiDown, InputEvent, @event, true); + else if (Action == GameInputs.Attack) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiAccept, InputEvent, @event, true); + else if (Action == GameInputs.Interact) + InputHelper.ReplaceJoypadInputForAction(GameInputs.UiCancel, InputEvent, @event, true); + InputEvent = @event; + } + } + AcceptEvent(); + } +} \ No newline at end of file diff --git a/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs.uid b/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs.uid new file mode 100644 index 00000000..e65146eb --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/JoypadRemapButton.cs.uid @@ -0,0 +1 @@ +uid://bo7vk56h1lr07 diff --git a/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs b/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs new file mode 100644 index 00000000..5da3297d --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs @@ -0,0 +1,42 @@ +using Godot; +using NathanHoad; + +namespace Zennysoft.Game.Ma; + +public partial class KeyboardRemapButton : InputMapButton +{ + public override void _Ready() + { + SetProcessInput(false); + } + + public override void _Input(InputEvent @event) + { + AcceptEvent(); + if (@event.IsActionType() && InputHelper.GetDeviceFromEvent(@event) == "keyboard") + { + if (InputHelper.GetKeyboardInputForAction(GameInputs.Pause).IsMatch(@event)) + { + InputHelper.SetKeyboardInputForAction(Action, InputEvent); + } + else + { + InputHelper.ReplaceKeyboardInputForAction(Action, InputEvent, @event, true); + if (Action == GameInputs.MoveUp) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiUp, InputEvent, @event, true); + else if (Action == GameInputs.MoveLeft) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiLeft, InputEvent, @event, true); + else if (Action == GameInputs.MoveRight) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiRight, InputEvent, @event, true); + else if (Action == GameInputs.MoveDown) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiDown, InputEvent, @event, true); + else if (Action == GameInputs.Attack) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiAccept, InputEvent, @event, true); + else if (Action == GameInputs.Interact) + InputHelper.ReplaceKeyboardInputForAction(GameInputs.UiCancel, InputEvent, @event, true); + InputEvent = @event; + } + } + } +} + diff --git a/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs.uid b/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs.uid new file mode 100644 index 00000000..e6c4e9c6 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/KeyboardRemapButton.cs.uid @@ -0,0 +1 @@ +uid://b70br20xue678 diff --git a/Zennysoft.Game.Ma/src/options/OptionsData.cs b/Zennysoft.Game.Ma/src/options/OptionsData.cs new file mode 100644 index 00000000..41d5f2fe --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/OptionsData.cs @@ -0,0 +1,21 @@ +using Chickensoft.Introspection; +using Chickensoft.Serialization; +using Godot; + +namespace Zennysoft.Game.Ma; + +[Meta, Id("options_data")] +public partial class OptionsData : Node +{ + [Save("MasterVolume")] + public required double MasterVolumeLevel { get; set; } + + [Save("MusicVolume")] + public required double MusicVolumeLevel { get; set; } + + [Save("SFXVolume")] + public required double SFXVolumeLevel { get; set; } + + [Save("ScreenResolution")] + public required int ScreenResolution { get; set; } +} \ No newline at end of file diff --git a/Zennysoft.Game.Ma/src/options/OptionsData.cs.uid b/Zennysoft.Game.Ma/src/options/OptionsData.cs.uid new file mode 100644 index 00000000..676d24d4 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/OptionsData.cs.uid @@ -0,0 +1 @@ +uid://jrl2hyudv6jn diff --git a/Zennysoft.Game.Ma/src/options/OptionsMenu.cs b/Zennysoft.Game.Ma/src/options/OptionsMenu.cs new file mode 100644 index 00000000..faa2d145 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/OptionsMenu.cs @@ -0,0 +1,159 @@ +using Chickensoft.AutoInject; +using Chickensoft.Introspection; +using Godot; +using NathanHoad; +using System.Linq; + +namespace Zennysoft.Game.Ma; + +[Meta(typeof(IAutoNode))] +public partial class OptionsMenu : Control +{ + public override void _Notification(int what) => this.Notify(what); + + [Node] public OptionButton ResolutionOptions { get; set; } + + [Node] public HSlider MasterVolumeSlider { get; set; } + [Node] public HSlider MusicVolumeSlider { get; set; } + [Node] public HSlider SFXVolumeSlider { get; set; } + + [Node] public InputMapper Controller { get; set; } + [Node] public TabContainer TabContainer { get; set; } + [Node] public Label PressToGoBackLabel { get; set; } + + [Node] public CanvasLayer CanvasLayer { get; set; } + + public OptionsData OptionsData; + + private int _masterBusIndex; + private int _musicBusIndex; + private int _sfxBusIndex; + + private readonly DisplayServer.WindowMode[] _windowModes = [DisplayServer.WindowMode.Windowed, DisplayServer.WindowMode.Maximized, DisplayServer.WindowMode.Fullscreen, DisplayServer.WindowMode.ExclusiveFullscreen]; + + [Signal] public delegate void OptionsMenuExitedEventHandler(); + + public void OnReady() + { + ResolutionOptions.AddItem("Windowed"); + ResolutionOptions.AddItem("Maximized"); + ResolutionOptions.AddItem("Fullscreen"); + ResolutionOptions.AddItem("Exclusive Fullscreen"); + ResolutionOptions.Select(0); + + OptionsData = new OptionsData() + { + MasterVolumeLevel = MasterVolumeSlider.Value, + MusicVolumeLevel = MusicVolumeSlider.Value, + SFXVolumeLevel = SFXVolumeSlider.Value, + ScreenResolution = ResolutionOptions.GetSelectedId() + }; + MasterVolumeSlider.ValueChanged += MasterVolumeSlider_Changed; + MusicVolumeSlider.ValueChanged += MusicVolumeSlider_Changed; + SFXVolumeSlider.ValueChanged += SFXVolumeSlider_Changed; + + ResolutionOptions.ItemSelected += ResolutionOptions_ItemSelected; + + TabContainer.TabChanged += TabContainer_TabChanged; + + _masterBusIndex = AudioServer.GetBusIndex("Master"); + _musicBusIndex = AudioServer.GetBusIndex("MUSIC"); + _sfxBusIndex = AudioServer.GetBusIndex("SFX"); + + VisibilityChanged += OptionsMenu_VisibilityChanged; + + InputHelper.JoypadInputChanged += (string action, InputEvent input) => + { + if (GameInputs.Interact == action) + { + var interactInputs = InputHelper.GetLabelForInput(InputHelper.GetJoypadInputForAction(GameInputs.Interact)); + PressToGoBackLabel.Text = $"Press {interactInputs} to save and exit."; + } + }; + + InputHelper.KeyboardInputChanged += (string action, InputEvent input) => + { + if (GameInputs.Interact == action) + { + var interactInputs = InputHelper.GetLabelForInput(InputHelper.GetKeyboardInputForAction(GameInputs.Interact)); + PressToGoBackLabel.Text = $"Press {interactInputs} to save and exit."; + } + }; + } + + private void TabContainer_TabChanged(long tab) + { + if (tab == 0) + MasterVolumeSlider.GrabFocus(); + if (tab == 1) + Controller.MoveForwardKeyboard.GrabFocus(); + } + + private void OptionsMenu_VisibilityChanged() + { + CanvasLayer.Visible = !CanvasLayer.Visible; + } + + public override void _Input(InputEvent @event) + { + if (!Visible) + return; + + var interactInputs = InputHelper.GetKeyboardOrJoypadInputsForAction(GameInputs.Interact); + if (interactInputs.Any(x => x.IsMatch(@event))) + { + AcceptEvent(); + SaveAndExitMenu(); + } + + var leftTab = InputHelper.GetKeyboardOrJoypadInputsForAction(GameInputs.StrafeLeft); + if (leftTab.Any(x => x.IsMatch(@event))) + { + AcceptEvent(); + TabContainer.CurrentTab = Mathf.Max(0, TabContainer.CurrentTab - 1); + } + + var rightTab = InputHelper.GetKeyboardOrJoypadInputsForAction(GameInputs.StrafeRight); + if (rightTab.Any(x => x.IsMatch(@event))) + { + AcceptEvent(); + TabContainer.CurrentTab = Mathf.Min(TabContainer.GetTabCount() - 1, TabContainer.CurrentTab + 1); + } + } + + private void ResolutionOptions_ItemSelected(long index) + { + var resolutionIndex = ResolutionOptions.GetSelectedId(); + OptionsData.ScreenResolution = resolutionIndex; + DisplayServer.WindowSetMode(_windowModes[resolutionIndex]); + } + + public void Load(OptionsData optionsData) + { + MasterVolumeSlider.Value = optionsData.MasterVolumeLevel; + MusicVolumeSlider.Value = optionsData.MusicVolumeLevel; + SFXVolumeSlider.Value = optionsData.SFXVolumeLevel; + ResolutionOptions.Select(optionsData.ScreenResolution); + DisplayServer.WindowSetMode(_windowModes[optionsData.ScreenResolution]); + } + + private void SaveAndExitMenu() => EmitSignal(SignalName.OptionsMenuExited); + + private void MasterVolumeSlider_Changed(double valueChanged) + { + OptionsData.MasterVolumeLevel = valueChanged; + AudioServer.SetBusVolumeDb(_masterBusIndex, Mathf.LinearToDb((float)valueChanged)); + } + + private void MusicVolumeSlider_Changed(double valueChanged) + { + OptionsData.MusicVolumeLevel = valueChanged; + AudioServer.SetBusVolumeDb(_musicBusIndex, Mathf.LinearToDb((float)valueChanged)); + } + + private void SFXVolumeSlider_Changed(double valueChanged) + { + OptionsData.SFXVolumeLevel = valueChanged; + AudioServer.SetBusVolumeDb(_sfxBusIndex, Mathf.LinearToDb((float)valueChanged)); + } +} \ No newline at end of file diff --git a/Zennysoft.Game.Ma/src/options/OptionsMenu.cs.uid b/Zennysoft.Game.Ma/src/options/OptionsMenu.cs.uid new file mode 100644 index 00000000..0f7219d1 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/OptionsMenu.cs.uid @@ -0,0 +1 @@ +uid://cjxmdvhixcj6e diff --git a/Zennysoft.Game.Ma/src/options/OptionsMenu.tscn b/Zennysoft.Game.Ma/src/options/OptionsMenu.tscn new file mode 100644 index 00000000..1403a031 --- /dev/null +++ b/Zennysoft.Game.Ma/src/options/OptionsMenu.tscn @@ -0,0 +1,149 @@ +[gd_scene load_steps=7 format=3 uid="uid://drkl3btdy6uxj"] + +[ext_resource type="Script" uid="uid://cjxmdvhixcj6e" path="res://src/options/OptionsMenu.cs" id="1_jli36"] +[ext_resource type="Shortcut" uid="uid://dumkrjur22k2a" path="res://src/ui/ButtonShortcut.tres" id="2_1egkf"] +[ext_resource type="PackedScene" uid="uid://dk5esf6mm6kte" path="res://src/options/InputMapper.tscn" id="2_utd4g"] + +[sub_resource type="StyleBoxLine" id="StyleBoxLine_jli36"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_utd4g"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1egkf"] +bg_color = Color(2.5028e-06, 0.712708, 0.445629, 1) + +[node name="OptionsMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_jli36") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] +unique_name_in_owner = true +visible = false + +[node name="CenterContainer" type="AspectRatioContainer" parent="CanvasLayer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/CenterContainer"] +layout_mode = 2 + +[node name="TabContainer" type="TabContainer" parent="CanvasLayer/CenterContainer/VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(1280, 960) +layout_mode = 2 +current_tab = 0 + +[node name="Audio" type="MarginContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 100 +theme_override_constants/margin_top = 100 +theme_override_constants/margin_right = 100 +theme_override_constants/margin_bottom = 100 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio"] +layout_mode = 2 +size_flags_horizontal = 0 +mouse_filter = 0 + +[node name="MasterVolume" type="HBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer"] +layout_mode = 2 + +[node name="VolumeLabel" type="Label" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/MasterVolume"] +custom_minimum_size = Vector2(125, 0) +layout_mode = 2 +text = "Master Volume" +horizontal_alignment = 2 + +[node name="MasterVolumeSlider" type="HSlider" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/MasterVolume"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_vertical = 1 +theme_override_styles/slider = SubResource("StyleBoxLine_jli36") +theme_override_styles/grabber_area = SubResource("StyleBoxFlat_utd4g") +theme_override_styles/grabber_area_highlight = SubResource("StyleBoxFlat_1egkf") +max_value = 1.0 +step = 0.001 +value = 1.0 + +[node name="MusicVolume" type="HBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer"] +layout_mode = 2 + +[node name="VolumeLabel" type="Label" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/MusicVolume"] +custom_minimum_size = Vector2(125, 0) +layout_mode = 2 +text = "Music Volume" +horizontal_alignment = 2 + +[node name="MusicVolumeSlider" type="HSlider" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/MusicVolume"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_vertical = 1 +theme_override_styles/slider = SubResource("StyleBoxLine_jli36") +theme_override_styles/grabber_area = SubResource("StyleBoxFlat_utd4g") +theme_override_styles/grabber_area_highlight = SubResource("StyleBoxFlat_1egkf") +max_value = 1.0 +step = 0.001 +value = 1.0 + +[node name="SFXVolume" type="HBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer"] +layout_mode = 2 + +[node name="VolumeLabel" type="Label" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/SFXVolume"] +custom_minimum_size = Vector2(125, 0) +layout_mode = 2 +text = "SFX Volume" +horizontal_alignment = 2 + +[node name="SFXVolumeSlider" type="HSlider" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/SFXVolume"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_vertical = 1 +theme_override_styles/slider = SubResource("StyleBoxLine_jli36") +theme_override_styles/grabber_area = SubResource("StyleBoxFlat_utd4g") +theme_override_styles/grabber_area_highlight = SubResource("StyleBoxFlat_1egkf") +max_value = 1.0 +step = 0.001 +value = 1.0 + +[node name="Resolution" type="HBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer"] +layout_mode = 2 + +[node name="ResolutionLabel" type="Label" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/Resolution"] +custom_minimum_size = Vector2(125, 0) +layout_mode = 2 +text = "Resolution: " +horizontal_alignment = 2 + +[node name="ResolutionOptions" type="OptionButton" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer/Resolution"] +unique_name_in_owner = true +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 +shortcut = ExtResource("2_1egkf") +flat = true + +[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer/Audio/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 10 + +[node name="Controller" parent="CanvasLayer/CenterContainer/VBoxContainer/TabContainer" instance=ExtResource("2_utd4g")] +unique_name_in_owner = true +visible = false +layout_mode = 2 +metadata/_tab_index = 1 + +[node name="PressToGoBackLabel" type="Label" parent="CanvasLayer/CenterContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Press " diff --git a/Zennysoft.Game.Ma/src/player/Player.cs b/Zennysoft.Game.Ma/src/player/Player.cs index 85d21767..a0eb9862 100644 --- a/Zennysoft.Game.Ma/src/player/Player.cs +++ b/Zennysoft.Game.Ma/src/player/Player.cs @@ -99,173 +99,179 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide public void Initialize() { - var container = new SimpleInjector.Container(); - container.Register(Lifestyle.Singleton); + var container = new SimpleInjector.Container(); + container.Register(Lifestyle.Singleton); - PlayerLogic = container.GetInstance(); - PlayerLogic.Set(this as IPlayer); - PlayerLogic.Set(Settings); + PlayerLogic = container.GetInstance(); + PlayerLogic.Set(this as IPlayer); + PlayerLogic.Set(Settings); - Inventory = new Inventory(); - HealthComponent = new HealthComponent(InitialHP); - VTComponent = new VTComponent(InitialVT); - AttackComponent = new AttackComponent(InitialAttack); - DefenseComponent = new DefenseComponent(InitialDefense); - ExperiencePointsComponent = new ExperiencePointsComponent(); - LuckComponent = new LuckComponent(InitialLuck); - EquipmentComponent = new EquipmentComponent(); + Inventory = new Inventory(); + HealthComponent = new HealthComponent(InitialHP); + VTComponent = new VTComponent(InitialVT); + AttackComponent = new AttackComponent(InitialAttack); + DefenseComponent = new DefenseComponent(InitialDefense); + ExperiencePointsComponent = new ExperiencePointsComponent(); + LuckComponent = new LuckComponent(InitialLuck); + EquipmentComponent = new EquipmentComponent(); - _itemReroller = new ItemReroller(ItemDatabase.Instance); + _itemReroller = new ItemReroller(ItemDatabase.Instance); - Settings = new PlayerLogic.Settings() { RotationSpeed = RotationSpeed, MoveSpeed = MoveSpeed, Acceleration = Acceleration }; + Settings = new PlayerLogic.Settings() { RotationSpeed = RotationSpeed, MoveSpeed = MoveSpeed, Acceleration = Acceleration }; - PlayerBinding = PlayerLogic.Bind(); + PlayerBinding = PlayerLogic.Bind(); - PlayerBinding - .Handle((in PlayerLogic.Output.ThrowItem output) => - { - }) - .Handle((in PlayerLogic.Output.Move output) => - { - Move(output.delta); - }); + PlayerBinding + .Handle((in PlayerLogic.Output.ThrowItem output) => + { + }) + .Handle((in PlayerLogic.Output.Move output) => + { + Move(output.delta); + }); - PlayerLogic.Start(); - this.Provide(); + PlayerLogic.Start(); + this.Provide(); } public void ResetPlayerData() { - foreach (var item in Inventory.Items) - Inventory.Remove(item); + foreach (var item in Inventory.Items) + Inventory.Remove(item); - HealthComponent.Reset(); - VTComponent.Reset(); - AttackComponent.Reset(); - DefenseComponent.Reset(); - ExperiencePointsComponent.Reset(); - LuckComponent.Reset(); - EquipmentComponent.Reset(); + HealthComponent.Reset(); + VTComponent.Reset(); + AttackComponent.Reset(); + DefenseComponent.Reset(); + ExperiencePointsComponent.Reset(); + LuckComponent.Reset(); + EquipmentComponent.Reset(); - HealthTimer.Timeout += OnHealthTimerTimeout; + HealthTimer.Timeout += OnHealthTimerTimeout; } #region Initialization public void OnReady() { - Hitbox.AreaEntered += Hitbox_AreaEntered; - CollisionDetector.AreaEntered += CollisionDetector_AreaEntered; - SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2; - HealthComponent.HealthReachedZero += Die; - HealthTimer.WaitTime = _healthTimerWaitTime; - SetProcessInput(false); - SetPhysicsProcess(false); + Hitbox.AreaEntered += Hitbox_AreaEntered; + CollisionDetector.AreaEntered += CollisionDetector_AreaEntered; + SwordSlashAnimation.Position = GetViewport().GetVisibleRect().Size / 2; + HealthComponent.HealthReachedZero += Die; + HealthTimer.WaitTime = _healthTimerWaitTime; + SetProcessInput(false); + SetPhysicsProcess(false); } #endregion public void Activate() { - SetProcessInput(true); - SetPhysicsProcess(true); - SetHealthTimerStatus(HealthTimerIsActive); + SetProcessInput(true); + SetPhysicsProcess(true); + SetHealthTimerStatus(HealthTimerIsActive); } public void Deactivate() { - SetProcessInput(false); - SetPhysicsProcess(false); - SetHealthTimerStatus(false); + SetProcessInput(false); + SetPhysicsProcess(false); + SetHealthTimerStatus(false); } private void SetHealthTimerStatus(bool isActive) { - if (isActive) - HealthTimer.Start(); - else - HealthTimer.Stop(); + if (isActive) + HealthTimer.Start(); + else + HealthTimer.Stop(); } public void TeleportPlayer(Transform3D newTransform) { - Transform = newTransform; + Transform = newTransform; } public void TakeDamage(AttackData damage) { - var damageReceived = DamageCalculator.CalculateDamage(damage, DefenseComponent.CurrentDefense.Value + EquipmentComponent.BonusDefense, EquipmentComponent.ElementalResistance); - HealthComponent.Damage(damageReceived); + var damageReceived = DamageCalculator.CalculateDamage(damage, DefenseComponent.CurrentDefense.Value + EquipmentComponent.BonusDefense, EquipmentComponent.ElementalResistance); + HealthComponent.Damage(damageReceived); } public void Knockback(float impulse) { - _knockbackStrength = impulse; - _knockbackDirection = GlobalBasis.Z.Normalized(); + _knockbackStrength = impulse; + _knockbackDirection = GlobalBasis.Z.Normalized(); } public void LevelUp() { - var rng = new RandomNumberGenerator(); - rng.Randomize(); - var hpIncrease = rng.RandiRange(3, 6); - HealthComponent.RaiseMaximumHP(hpIncrease); - ExperiencePointsComponent.LevelUp(); + var rng = new RandomNumberGenerator(); + rng.Randomize(); + var hpIncrease = rng.RandiRange(3, 6); + HealthComponent.RaiseMaximumHP(hpIncrease); + ExperiencePointsComponent.LevelUp(); } public void Die() { - HealthTimer.WaitTime = _healthTimerWaitTime; - HealthTimer.Timeout -= OnHealthTimerTimeout; - SwordSlashAnimation.Stop(); - SetProcessInput(false); - SetPhysicsProcess(false); - PlayerDied?.Invoke(); + HealthTimer.WaitTime = _healthTimerWaitTime; + HealthTimer.Timeout -= OnHealthTimerTimeout; + SwordSlashAnimation.Stop(); + SetProcessInput(false); + SetPhysicsProcess(false); + PlayerDied?.Invoke(); } public override void _Input(InputEvent @event) { - if (@event.IsActionPressed(GameInputs.Attack)) - Attack(); - if (@event.IsActionPressed(GameInputs.Sprint)) - Settings.MoveSpeed *= 2; - if (@event.IsActionReleased(GameInputs.Sprint)) - Settings.MoveSpeed /= 2; + if (@event.IsActionPressed(GameInputs.Attack)) + Attack(); + if (@event.IsActionPressed(GameInputs.Sprint)) + Settings.MoveSpeed *= 2; + if (@event.IsActionReleased(GameInputs.Sprint)) + Settings.MoveSpeed /= 2; } public void OnPhysicsProcess(double delta) { - PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta)); - PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition, GlobalTransform)); + PlayerLogic.Input(new PlayerLogic.Input.PhysicsTick(delta)); + PlayerLogic.Input(new PlayerLogic.Input.Moved(GlobalPosition, GlobalTransform)); } public void Equip(EquipableItem equipable) { - if (equipable.ItemTag == ItemTag.MysteryItem) - { - var rerolledItem = _itemReroller.RerollItem(equipable, Inventory); - Equip(rerolledItem); - return; - } + if (equipable.ItemTag == ItemTag.MysteryItem) + { + var rerolledItem = _itemReroller.RerollItem(equipable, Inventory); + Equip(rerolledItem); + return; + } - EquipmentComponent.Equip(equipable); + HealthComponent.RaiseMaximumHP(equipable.BonusHP, false); + VTComponent.RaiseMaximumVT(equipable.BonusVT, false); + + EquipmentComponent.Equip(equipable); } public void Unequip(EquipableItem equipable) { - EquipmentComponent.Unequip(equipable); + HealthComponent.SetMaximumHealth(HealthComponent.MaximumHP.Value - equipable.BonusHP); + VTComponent.SetMaximumVT(VTComponent.MaximumVT.Value - equipable.BonusVT); + + EquipmentComponent.Unequip(equipable); } private static Vector3 GlobalInputVector { - get - { - var rawInput = Input.GetVector(GameInputs.MoveLeft, GameInputs.MoveRight, GameInputs.MoveUp, GameInputs.MoveDown); - var input = new Vector3 - { - X = rawInput.X, - Z = rawInput.Y - }; - return input with { Y = 0f }; - } + get + { + var rawInput = Input.GetVector(GameInputs.MoveLeft, GameInputs.MoveRight, GameInputs.MoveUp, GameInputs.MoveDown); + var input = new Vector3 + { + X = rawInput.X, + Z = rawInput.Y + }; + return input with { Y = 0f }; + } } private static float LeftStrafeInputVector => Input.GetActionStrength(GameInputs.StrafeLeft); @@ -274,140 +280,140 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide private void Attack() { - if (PlayerIsHittingGeometry()) - { - AnimationPlayer.Play("hit_wall"); - } - else - { - PlayAttackAnimation(); - } + if (PlayerIsHittingGeometry()) + { + AnimationPlayer.Play("hit_wall"); + } + else + { + PlayAttackAnimation(); + } } private void ThrowItem() { - var itemScene = GD.Load("res://src/items/throwable/ThrowableItem.tscn"); - var throwItem = itemScene.Instantiate(); - GetTree().Root.AddChildEx(throwItem); - throwItem.GlobalPosition = CurrentPosition + new Vector3(0, 3.5f, 0); - throwItem.GlobalRotation = GlobalRotation; + var itemScene = GD.Load("res://src/items/throwable/ThrowableItem.tscn"); + var throwItem = itemScene.Instantiate(); + GetTree().Root.AddChildEx(throwItem); + throwItem.GlobalPosition = CurrentPosition + new Vector3(0, 3.5f, 0); + throwItem.GlobalRotation = GlobalRotation; } private void PlayAttackAnimation() { - var attackSpeed = ((Weapon)EquipmentComponent.EquippedWeapon.Value).AttackSpeed; - AnimationPlayer.SetSpeedScale((float)attackSpeed); - AnimationPlayer.Play("attack"); + var attackSpeed = ((Weapon)EquipmentComponent.EquippedWeapon.Value).AttackSpeed; + AnimationPlayer.SetSpeedScale((float)attackSpeed); + AnimationPlayer.Play("attack"); } private void OnExitTree() { - PlayerLogic.Stop(); - PlayerBinding.Dispose(); - Hitbox.AreaEntered -= Hitbox_AreaEntered; - CollisionDetector.AreaEntered -= CollisionDetector_AreaEntered; - HealthComponent.HealthReachedZero -= Die; - HealthTimer.Timeout -= OnHealthTimerTimeout; + PlayerLogic.Stop(); + PlayerBinding.Dispose(); + Hitbox.AreaEntered -= Hitbox_AreaEntered; + CollisionDetector.AreaEntered -= CollisionDetector_AreaEntered; + HealthComponent.HealthReachedZero -= Die; + HealthTimer.Timeout -= OnHealthTimerTimeout; } private void Move(float delta) { - var rawInput = GlobalInputVector; - var strafeLeftInput = LeftStrafeInputVector; - var strafeRightInput = RightStrafeInputVector; + var rawInput = GlobalInputVector; + var strafeLeftInput = LeftStrafeInputVector; + var strafeRightInput = RightStrafeInputVector; - var transform = Transform; - transform.Basis = new Basis(Vector3.Up, Settings.RotationSpeed * -rawInput.X * delta) * transform.Basis; - var moveDirection = new Vector3(strafeRightInput - strafeLeftInput, 0, rawInput.Z).Normalized(); - var velocity = Basis * moveDirection * Settings.MoveSpeed * Settings.Acceleration; - _knockbackStrength *= 0.9f; - Transform = Transform with { Basis = transform.Basis }; - Velocity = velocity + (_knockbackDirection * _knockbackStrength); - MoveAndSlide(); + var transform = Transform; + transform.Basis = new Basis(Vector3.Up, Settings.RotationSpeed * -rawInput.X * delta) * transform.Basis; + var moveDirection = new Vector3(strafeRightInput - strafeLeftInput, 0, rawInput.Z).Normalized(); + var velocity = Basis * moveDirection * Settings.MoveSpeed * Settings.Acceleration; + _knockbackStrength *= 0.9f; + Transform = Transform with { Basis = transform.Basis }; + Velocity = velocity + (_knockbackDirection * _knockbackStrength); + MoveAndSlide(); } private void OnPlayerPositionUpdated(Vector3 globalPosition) => GlobalPosition = globalPosition; private void OnHealthTimerTimeout() { - if (VTComponent.CurrentVT.Value > 0) - { - if (((Accessory)EquipmentComponent.EquippedAccessory.Value).AccessoryTag == AccessoryTag.HalfVTConsumption) - reduceOnTick = !reduceOnTick; + if (VTComponent.CurrentVT.Value > 0) + { + if (((Accessory)EquipmentComponent.EquippedAccessory.Value).AccessoryTag == AccessoryTag.HalfVTConsumption) + reduceOnTick = !reduceOnTick; - HealthComponent.Heal(1); + HealthComponent.Heal(1); - if (reduceOnTick) - VTComponent.Reduce(1); - } - else - HealthComponent.Damage(1); + if (reduceOnTick) + VTComponent.Reduce(1); + } + else + HealthComponent.Damage(1); } private void Hitbox_AreaEntered(Area3D area) { - var target = area.GetOwner(); - if (target is IEnemy enemy) - HitEnemy(enemy); + var target = area.GetOwner(); + if (target is IEnemy enemy) + HitEnemy(enemy); } private void HitEnemy(IEnemy enemy) { - var ignoreElementalResistance = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponTag == WeaponTag.IgnoreAffinity; - var ignoreDefense = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponTag == WeaponTag.IgnoreDefense; - var isCriticalHit = BattleExtensions.IsCriticalHit(LuckComponent.Luck.Value + EquipmentComponent.BonusLuck); - var totalDamage = AttackComponent.CurrentAttack.Value + EquipmentComponent.BonusAttack; - var element = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponElement; + var ignoreElementalResistance = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponTag == WeaponTag.IgnoreAffinity; + var ignoreDefense = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponTag == WeaponTag.IgnoreDefense; + var isCriticalHit = BattleExtensions.IsCriticalHit(LuckComponent.Luck.Value + EquipmentComponent.BonusLuck); + var totalDamage = AttackComponent.CurrentAttack.Value + EquipmentComponent.BonusAttack; + var element = (EquipmentComponent.EquippedWeapon.Value as Weapon).WeaponElement; - if (isCriticalHit) - totalDamage += (int)(totalDamage * 0.5f); + if (isCriticalHit) + totalDamage += (int)(totalDamage * 0.5f); - var baseAttack = new AttackData(totalDamage, element, ignoreDefense, ignoreElementalResistance); - var damageDealt = DamageCalculator.CalculateDamage(baseAttack, enemy.DefenseComponent.CurrentDefense.Value, ElementalResistanceSet.None); - enemy.HealthComponent.Damage(damageDealt); + var baseAttack = new AttackData(totalDamage, element, ignoreDefense, ignoreElementalResistance); + var damageDealt = DamageCalculator.CalculateDamage(baseAttack, enemy.DefenseComponent.CurrentDefense.Value, ElementalResistanceSet.None); + enemy.HealthComponent.Damage(damageDealt); - if (((Weapon)EquipmentComponent.EquippedWeapon.Value).WeaponTag == WeaponTag.Knockback && enemy is IKnockbackable knockbackable) - knockbackable.Knockback(0.3f, -CurrentBasis.Z.Normalized()); - if (((Weapon)EquipmentComponent.EquippedWeapon.Value).WeaponTag == WeaponTag.SelfDamage) - HealthComponent.Damage(5); + if (((Weapon)EquipmentComponent.EquippedWeapon.Value).WeaponTag == WeaponTag.Knockback && enemy is IKnockbackable knockbackable) + knockbackable.Knockback(0.3f, -CurrentBasis.Z.Normalized()); + if (((Weapon)EquipmentComponent.EquippedWeapon.Value).WeaponTag == WeaponTag.SelfDamage) + HealthComponent.Damage(5); } private void CollisionDetector_AreaEntered(Area3D area) { - if (area.GetParent() is InventoryItem inventoryItem) - { - var isAdded = Inventory.PickUpItem(inventoryItem); - if (isAdded) - inventoryItem.QueueFree(); - } - if (area.GetParent() is DroppedItem droppedItem) - { - var isAdded = Inventory.PickUpItem(droppedItem.Item); - if (isAdded) - droppedItem.QueueFree(); - } - if (area.GetParent() is ThrownItem thrownItem) - { - var isAdded = Inventory.PickUpItem(thrownItem.ItemThatIsThrown); - if (isAdded) - thrownItem.QueueFree(); - } - if (area.GetParent() is Restorative restorative) - { - //_gameRepo.OnRestorativePickedUp(restorative); - restorative.QueueFree(); - } + if (area.GetParent() is InventoryItem inventoryItem) + { + var isAdded = Inventory.PickUpItem(inventoryItem); + if (isAdded) + inventoryItem.QueueFree(); + } + if (area.GetParent() is DroppedItem droppedItem) + { + var isAdded = Inventory.PickUpItem(droppedItem.Item); + if (isAdded) + droppedItem.QueueFree(); + } + if (area.GetParent() is ThrownItem thrownItem) + { + var isAdded = Inventory.PickUpItem(thrownItem.ItemThatIsThrown); + if (isAdded) + thrownItem.QueueFree(); + } + if (area.GetParent() is Restorative restorative) + { + //_gameRepo.OnRestorativePickedUp(restorative); + restorative.QueueFree(); + } } private bool PlayerIsHittingGeometry() { - var collisions = WallCheck.GetCollidingBodies(); - return collisions.Count > 0; + var collisions = WallCheck.GetCollidingBodies(); + return collisions.Count > 0; } private void WallCheck_BodyEntered(Node body) { - GD.Print("Hit wall"); - AnimationPlayer.Stop(); + GD.Print("Hit wall"); + AnimationPlayer.Stop(); } } diff --git a/Zennysoft.Game.Ma/src/ui/ButtonShortcut.tres b/Zennysoft.Game.Ma/src/ui/ButtonShortcut.tres new file mode 100644 index 00000000..99bc5811 --- /dev/null +++ b/Zennysoft.Game.Ma/src/ui/ButtonShortcut.tres @@ -0,0 +1,3 @@ +[gd_resource type="Shortcut" format=3 uid="uid://dumkrjur22k2a"] + +[resource] diff --git a/Zennysoft.Game.Ma/src/ui/dialogue/DialogueBalloon.cs b/Zennysoft.Game.Ma/src/ui/dialogue/DialogueBalloon.cs index 4935adb6..c69c06ce 100644 --- a/Zennysoft.Game.Ma/src/ui/dialogue/DialogueBalloon.cs +++ b/Zennysoft.Game.Ma/src/ui/dialogue/DialogueBalloon.cs @@ -5,8 +5,8 @@ using Godot.Collections; namespace Zennysoft.Game.Ma; public partial class DialogueBalloon : CanvasLayer { - [Export] public string NextAction = "ui_accept"; - [Export] public string SkipAction = "ui_cancel"; + [Export] public string NextAction = GameInputs.Interact; + [Export] public string SkipAction = GameInputs.Attack; Control balloon; diff --git a/Zennysoft.Game.Ma/src/ui/in_game_ui/InGameUI.cs b/Zennysoft.Game.Ma/src/ui/in_game_ui/InGameUI.cs index 1a3c629b..893e660d 100644 --- a/Zennysoft.Game.Ma/src/ui/in_game_ui/InGameUI.cs +++ b/Zennysoft.Game.Ma/src/ui/in_game_ui/InGameUI.cs @@ -44,9 +44,9 @@ public partial class InGameUI : Control, IInGameUI InGameUILogicBinding .Handle((in InGameUILogic.Output.AnnounceMessageOnMainScreen output) => { InventoryMessageUI.DisplayMessage(output.Message); }) - .Handle((in InGameUILogic.Output.AnnounceMessageInInventory output) => { InventoryMenu.DisplayMessage(output.Message); }) - .Handle((in InGameUILogic.Output.RemoveItemFromInventory output) => { InventoryMenu.RemoveItem(output.Item); }) - .Handle((in InGameUILogic.Output.ShowInventory _) => { InventoryMenu.RefreshInventoryScreen(); InventoryMenu.Show(); InventoryMenu.SetProcessInput(true); }) + .Handle((in InGameUILogic.Output.AnnounceMessageInInventory output) => { }) + .Handle((in InGameUILogic.Output.RemoveItemFromInventory output) => { }) + .Handle((in InGameUILogic.Output.ShowInventory _) => { InventoryMenu.Show(); InventoryMenu.SetProcessInput(true); }) .Handle((in InGameUILogic.Output.HideInventory _) => { CloseInventory(); }); InGameUILogic.Start(); @@ -60,7 +60,7 @@ public partial class InGameUI : Control, IInGameUI public override void _UnhandledInput(InputEvent @event) { - if (@event.IsActionPressed(GameInputs.Inventory)) + if (@event.IsActionPressed(GameInputs.Inventory) && !InventoryMenu.Visible) { GD.Print("Inventory button pressed"); InGameUILogic.Input(new InGameUILogic.Input.ShowInventory()); diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs b/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs new file mode 100644 index 00000000..2d82eb5f --- /dev/null +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs @@ -0,0 +1,7 @@ +using Chickensoft.GodotNodeInterfaces; + +namespace Zennysoft.Game.Ma; + +public interface IInventoryMenu : IControl +{ +} diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs.uid b/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs.uid new file mode 100644 index 00000000..e5828154 --- /dev/null +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/IInventoryMenu.cs.uid @@ -0,0 +1 @@ +uid://nhhwcce8ma3a diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/IItemSlot.cs b/Zennysoft.Game.Ma/src/ui/inventory_menu/IItemSlot.cs index c9c3199d..5335f38a 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/IItemSlot.cs +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/IItemSlot.cs @@ -1,18 +1,19 @@ +using Chickensoft.Collections; using Chickensoft.GodotNodeInterfaces; -using Zennysoft.Game.Implementation; +using System; using Zennysoft.Ma.Adapter; namespace Zennysoft.Game.Ma; -public interface IItemSlot : IHBoxContainer +public interface IItemSlot : IButton { - public InventoryItem Item { get; set; } + public AutoProp Item { get; } + + public bool IsSelected { get; set; } public void SetItemStyle(); - public void SetSelectedItemStyle(); - - public void SetEquippedItemStyle(); - - public void SetEquippedSelectedItemStyle(); + public event Action ItemPressed; + public event Action ItemEnterFocus; + public event Action ItemExitFocus; } diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs index 4d4d29a6..51c1bd4d 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs @@ -1,32 +1,52 @@ using Chickensoft.AutoInject; -using Chickensoft.GodotNodeInterfaces; using Chickensoft.Introspection; using Godot; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Zennysoft.Ma.Adapter; namespace Zennysoft.Game.Ma; -public interface IInventoryMenu : IControl -{ - public Task RefreshInventoryScreen(); - - public Task DisplayMessage(string message); - - public void RemoveItem(InventoryItem item); -} - [Meta(typeof(IAutoNode))] public partial class InventoryMenu : Control, IInventoryMenu { public override void _Notification(int what) => this.Notify(what); + [Node] public VBoxContainer ItemsPage { get; set; } + + [Node] public Label ATKValue { get; set; } + [Node] public Label ATKBonusLabel { get; set; } + [Node] public Label DEFValue { get; set; } + [Node] public Label DEFBonusLabel { get; set; } + + [Node] public Button UseButton { get; set; } + [Node] public Button ThrowButton { get; set; } + [Node] public Button DropButton { get; set; } + + [Node] public Label ItemDescriptionTitle { get; set; } + [Node] public Label UseItemPrompt { get; set; } + [Node] public Label ItemEffectLabel { get; set; } + + [Node] public Label BackArrow { get; set; } = default!; + [Node] public Label ForwardArrow { get; set; } = default!; + + [Node] public ItemSlot ItemSlot1 { get; set; } + [Node] public ItemSlot ItemSlot2 { get; set; } + [Node] public ItemSlot ItemSlot3 { get; set; } + [Node] public ItemSlot ItemSlot4 { get; set; } + [Node] public ItemSlot ItemSlot5 { get; set; } + [Node] public ItemSlot ItemSlot6 { get; set; } + [Node] public ItemSlot ItemSlot7 { get; set; } + [Node] public ItemSlot ItemSlot8 { get; set; } + [Node] public ItemSlot ItemSlot9 { get; set; } + [Node] public ItemSlot ItemSlot10 { get; set; } + + [Dependency] private IPlayer _player => this.DependOn(); + [Dependency] private IGame _game => this.DependOn(); [Dependency] private IGameRepo _gameRepo => this.DependOn(); - [Dependency] public IGame Game => this.DependOn(); - - [Dependency] public IPlayer Player => this.DependOn(); + private List ItemSlots; private InventoryPageNumber _currentPageNumber = InventoryPageNumber.FirstPage; @@ -34,203 +54,208 @@ public partial class InventoryMenu : Control, IInventoryMenu private const int _itemsPerPage = 10; - private int _currentIndex = 0; + private IItemSlot _currentlySelectedItem = null; - private IItemSlot[] ItemSlots => [.. ItemsPage.GetChildren().OfType()]; - - #region Control Nodes - [Node] public Label ATKValue { get; set; } = default!; - [Node] public Label ATKBonusLabel { get; set; } = default!; - [Node] public Label DEFValue { get; set; } = default!; - [Node] public Label DEFBonusLabel { get; set; } = default!; - [Node] public Label ItemDescriptionTitle { get; set; } = default!; - [Node] public Label ItemEffectLabel { get; set; } = default!; - - // Item Menu - [Node] public Label BackArrow { get; set; } = default!; - [Node] public Label ForwardArrow { get; set; } = default!; - [Node] public Control ItemsPage { get; set; } = default!; - - // User Prompt Menu - [Node] public Label UseItemPrompt { get; set; } = default!; - [Node] public Button UseButton { get; set; } = default!; - [Node] public Button ThrowButton { get; set; } = default!; - [Node] public Button DropButton { get; set; } = default!; - - [Node] public AnimationPlayer AnimationPlayer { get; set; } = default!; - #endregion - - public InventoryMenu() + public override void _EnterTree() { SetProcessInput(false); - SetProcess(false); } public void OnResolved() { + ItemSlots = [ItemSlot1, ItemSlot2, ItemSlot3, ItemSlot4, ItemSlot5, ItemSlot6, ItemSlot7, ItemSlot8, ItemSlot9, ItemSlot10]; + _currentlySelectedItem = ItemSlot1; + foreach (var item in ItemSlots) + { + item.ItemPressed += Item_Pressed; + item.ItemEnterFocus += Item_FocusEntered; + item.ItemExitFocus += Item_ItemExitFocus; + } + + _player.AttackComponent.CurrentAttack.Sync += Attack_Sync; + _player.AttackComponent.MaximumAttack.Sync += Attack_Sync; + _player.DefenseComponent.CurrentDefense.Sync += Defense_Sync; + _player.DefenseComponent.MaximumDefense.Sync += Defense_Sync; + _player.EquipmentComponent.EquipmentChanged += EquipmentComponent_EquipmentChanged; + _player.Inventory.InventoryChanged += Inventory_InventoryChanged; + UseButton.Pressed += UseButtonPressed; ThrowButton.Pressed += ThrowButtonPressed; DropButton.Pressed += DropButtonPressed; - Player.AttackComponent.CurrentAttack.Sync += AttackSync; - Player.AttackComponent.MaximumAttack.Sync += AttackSync; - Player.EquipmentComponent.EquippedWeapon.Sync += BonusSync; - Player.EquipmentComponent.EquippedArmor.Sync += BonusSync; - Player.EquipmentComponent.EquippedAccessory.Sync += BonusSync; - Player.DefenseComponent.CurrentDefense.Sync += DefenseSync; - Player.DefenseComponent.MaximumDefense.Sync += DefenseSync; + + VisibilityChanged += InventoryMenu2_VisibilityChanged; } - public void OnExitTree() - { - Player.AttackComponent.CurrentAttack.Sync -= AttackSync; - Player.AttackComponent.MaximumAttack.Sync -= AttackSync; - Player.EquipmentComponent.EquippedWeapon.Sync -= BonusSync; - Player.EquipmentComponent.EquippedArmor.Sync -= BonusSync; - Player.EquipmentComponent.EquippedAccessory.Sync -= BonusSync; - Player.DefenseComponent.CurrentDefense.Sync -= DefenseSync; - Player.DefenseComponent.MaximumDefense.Sync -= DefenseSync; - } - - public async Task DisplayMessage(string message) - { - SetProcessInput(false); - await HideUserActionPrompt(); - await ShowInventoryInfo(); - ItemEffectLabel.Text = message; - await ToSignal(GetTree().CreateTimer(1f), "timeout"); - await RefreshInventoryScreen(); - SetProcessInput(true); - } - - private void AttackSync(int obj) => ATKValue.Text = $"{Player.AttackComponent.CurrentAttack.Value}/{Player.AttackComponent.MaximumAttack.Value}"; - - private void BonusSync(EquipableItem equip) - { - ATKBonusLabel.Text = $"{Player.EquipmentComponent.BonusAttack:+0;-#;\\.\\.\\.}"; - DEFBonusLabel.Text = $"{Player.EquipmentComponent.BonusDefense:+0;-#;\\.\\.\\.}"; - } - - private void DefenseSync(int obj) => DEFValue.Text = $"{Player.DefenseComponent.CurrentDefense.Value}/{Player.DefenseComponent.MaximumDefense.Value}"; - - public async Task RefreshInventoryScreen() - { - await ClearItems(); - PopulateInventory(); - PopulatePlayerInfo(); - await HideUserActionPrompt(); - await ShowInventoryInfo(); - } - -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed public override void _Input(InputEvent @event) { - if (@event.IsActionPressed(GameInputs.UiCancel)) - { - if (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus()) - { - HideUserActionPrompt(); - ShowInventoryInfo(); - Autoload.AudioManager.Play(SoundEffect.Cancel); - } - else - { - Autoload.AudioManager.Play(SoundEffect.Cancel); - _gameRepo.CloseInventory(); - } - } - - if (ItemSlots.Length == 0 || UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus()) + if (!Visible) return; - if (@event.IsActionPressed(GameInputs.UiRight) && _currentPageNumber == InventoryPageNumber.FirstPage) + if (Input.IsActionJustPressed(GameInputs.Inventory) && !(UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())) { - var inventory = Player.Inventory; - if (inventory.Items.Count > _itemsPerPage) - ChangeInventoryPage(InventoryPageNumber.SecondPage); + AcceptEvent(); + _gameRepo.CloseInventory(); + } + if (Input.IsActionJustPressed(GameInputs.UiCancel) && (UseButton.HasFocus() || DropButton.HasFocus() || ThrowButton.HasFocus())) + { + AcceptEvent(); + HideUserActionPrompt(); + } + else if (Input.IsActionJustPressed(GameInputs.UiCancel)) + { + AcceptEvent(); + _gameRepo.CloseInventory(); + } + if (_currentPageNumber == InventoryPageNumber.FirstPage && _player.Inventory.Items.Count > 10 && Input.IsActionJustPressed(GameInputs.MoveRight)) + { + _currentPageNumber = InventoryPageNumber.SecondPage; + Inventory_InventoryChanged(); + _currentlySelectedItem = ItemSlot1; + ItemSlot1.GrabFocus(); + } + else if (_currentPageNumber == InventoryPageNumber.SecondPage && Input.IsActionJustPressed(GameInputs.MoveLeft)) + { + _currentPageNumber = InventoryPageNumber.FirstPage; + Inventory_InventoryChanged(); + _currentlySelectedItem = ItemSlot1; + ItemSlot1.GrabFocus(); } - if (@event.IsActionPressed(GameInputs.UiLeft) && _currentPageNumber == InventoryPageNumber.SecondPage) - ChangeInventoryPage(InventoryPageNumber.FirstPage); - - if (@event.IsActionPressed(GameInputs.UiDown)) + if (Input.IsActionJustPressed(GameInputs.InventorySort)) { - var oldIndex = _currentIndex; - var newIndex = new[] { _currentIndex + 1, _itemsPerPage - 1, ItemSlots.Length - 1 }.Min(); - if (oldIndex == newIndex) - return; - - SetToUnselectedStyle(ItemSlots.ElementAt(oldIndex)); - SetToSelectedStyle(ItemSlots.ElementAt(newIndex)); - Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions); - _currentIndex = newIndex; - } - - if (@event.IsActionPressed(GameInputs.UiUp)) - { - var oldIndex = _currentIndex; - var newIndex = new[] { _currentIndex - 1, 0 }.Max(); - - if (oldIndex == newIndex) - return; - - SetToUnselectedStyle(ItemSlots.ElementAt(oldIndex)); - SetToSelectedStyle(ItemSlots.ElementAt(newIndex)); - Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions); - _currentIndex = newIndex; - } - - if (@event.IsActionPressed(GameInputs.UiAccept)) - { - DisplayUserActionPrompt(); - Autoload.AudioManager.Play(SoundEffect.Select); - } - - if (@event.IsActionPressed(GameInputs.InventorySort)) - { - var inventory = Player.Inventory; - inventory.Sort(Player.EquipmentComponent.EquippedWeapon.Value, Player.EquipmentComponent.EquippedArmor.Value, Player.EquipmentComponent.EquippedAccessory.Value); - if (_currentIndex > inventory.Items.Count - 1) - _currentIndex = inventory.Items.Count - 1; - RefreshInventoryScreen(); + _player.Inventory.Sort(_player.EquipmentComponent.EquippedWeapon.Value, _player.EquipmentComponent.EquippedArmor.Value, _player.EquipmentComponent.EquippedAccessory.Value); + Inventory_InventoryChanged(); + foreach (var slot in ItemSlots) + slot.SetItemStyle(); + Item_ItemExitFocus(_currentlySelectedItem); + _currentlySelectedItem = ItemSlot1; + _currentlySelectedItem.GrabFocus(); } } -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - public async void RemoveItem(InventoryItem item) + private void InventoryMenu2_VisibilityChanged() { - Player.Inventory.Remove(item); - if (_currentIndex >= ItemSlots.Length - 1) - _currentIndex--; - if (_currentIndex <= 0) - _currentIndex = 0; + _currentlySelectedItem.GrabFocus(); } - private void PopulateItems() + private void Item_ItemExitFocus(IItemSlot itemSlot) { - PopulateInventory(); - PopulatePlayerInfo(); - } - - private async Task ClearItems() - { - foreach (var item in ItemSlots) - ItemsPage.RemoveChildEx(item); - ItemDescriptionTitle.Text = string.Empty; ItemEffectLabel.Text = string.Empty; + itemSlot.IsSelected = false; + itemSlot.SetItemStyle(); } - private void PopulatePlayerInfo() + private void Item_FocusEntered(IItemSlot itemSlot) { - if (ItemSlots.Length != 0) + if (itemSlot.Item.Value == null) + return; + + ItemDescriptionTitle.Text = $"{itemSlot.Item.Value.ItemName}"; + ItemEffectLabel.Text = $"{itemSlot.Item.Value.Description}"; + _currentlySelectedItem = itemSlot; + itemSlot.IsSelected = true; + itemSlot.SetItemStyle(); + } + + private void Item_Pressed(InventoryItem item) => DisplayUserActionPrompt(item); + + private async void Inventory_InventoryChanged() + { + foreach (var slot in ItemSlots) { - var item = ItemSlots.ElementAt(_currentIndex).Item; - ItemDescriptionTitle.Text = $"{item.ItemName}"; - ItemEffectLabel.Text = $"{item.Description}"; + slot.Visible = false; + slot.SetItemStyle(); + } + + if (_currentPageNumber == InventoryPageNumber.SecondPage && _player.Inventory.Items.Count <= 10) + { + _currentPageNumber = InventoryPageNumber.FirstPage; + var elementToSelect = _player.Inventory.Items.IndexOf(_player.Inventory.Items.Last()); + _currentlySelectedItem = ItemSlots.ElementAt(elementToSelect); + _currentlySelectedItem.GrabFocus(); + } + + var itemsToDisplay = new List(); + if (_currentPageNumber == InventoryPageNumber.FirstPage) + itemsToDisplay = [.. _player.Inventory.Items.Take(_itemsPerPage)]; + else + itemsToDisplay = [.. _player.Inventory.Items.TakeLast(_player.Inventory.Items.Count - _itemsPerPage)]; + + for (var i = 0; i < itemsToDisplay.Count; i++) + { + ItemSlots[i].Item.OnNext(itemsToDisplay[i]); + ItemSlots[i].Visible = true; + } + + SetPageIndicators(); + + if (!_player.Inventory.Items.Contains(_currentlySelectedItem.Item.Value)) + { + _currentlySelectedItem.Item.OnNext(null); + var elementToSelect = Mathf.Max(0, ItemSlots.IndexOf(_currentlySelectedItem) - 1); + _currentlySelectedItem = ItemSlots.ElementAt(elementToSelect); + _currentlySelectedItem.GrabFocus(); } } - private void DisplayUserActionPrompt() + private void SetPageIndicators() + { + if (_player.Inventory.Items.Count > 10 && _currentPageNumber == InventoryPageNumber.FirstPage) + { + ForwardArrow.Text = "►"; + BackArrow.Text = ""; + } + else if (_player.Inventory.Items.Count > 10 && _currentPageNumber == InventoryPageNumber.SecondPage) + { + ForwardArrow.Text = ""; + BackArrow.Text = "◄"; + } + } + + private void Attack_Sync(int obj) => ATKValue.Text = $"{_player.AttackComponent.CurrentAttack.Value}/{_player.AttackComponent.MaximumAttack.Value}"; + private void Defense_Sync(int obj) => DEFValue.Text = $"{_player.DefenseComponent.CurrentDefense.Value}/{_player.DefenseComponent.MaximumDefense.Value}"; + + private void EquipmentComponent_EquipmentChanged(EquipableItem equipableItem) + { + ATKBonusLabel.Text = $"{_player.EquipmentComponent.BonusAttack:+0;-#;\\.\\.\\.}"; + DEFBonusLabel.Text = $"{_player.EquipmentComponent.BonusDefense:+0;-#;\\.\\.\\.}"; + } + + private async void UseButtonPressed() + { + UseButton.Disabled = true; + if (_currentlySelectedItem.Item.Value is EquipableItem equipable) + await EquipOrUnequipItem(equipable); + else + await _game.UseItem(_currentlySelectedItem.Item.Value); + UseButton.Disabled = false; + + + HideUserActionPrompt(); + await ShowInventoryInfo(); + await ToSignal(GetTree().CreateTimer(1f), "timeout"); + } + + private async void ThrowButtonPressed() + { + _game.ThrowItem(_currentlySelectedItem.Item.Value); + _player.Inventory.Remove(_currentlySelectedItem.Item.Value); + HideUserActionPrompt(); + await ShowInventoryInfo(); + _gameRepo.CloseInventory(); + } + + private async void DropButtonPressed() + { + _game.DropItem(_currentlySelectedItem.Item.Value); + _player.Inventory.Remove(_currentlySelectedItem.Item.Value); + HideUserActionPrompt(); + await ShowInventoryInfo(); + _gameRepo.CloseInventory(); + } + + private void DisplayUserActionPrompt(InventoryItem item) { ItemDescriptionTitle.Hide(); ItemEffectLabel.Hide(); @@ -239,11 +264,9 @@ public partial class InventoryMenu : Control, IInventoryMenu ThrowButton.Show(); DropButton.Show(); - var currentItem = ItemSlots.ElementAt(_currentIndex).Item; - - if (currentItem is EquipableItem equipable) + if (item is EquipableItem equipable) { - var isItemEquipped = Player.EquipmentComponent.IsItemEquipped(equipable); + var isItemEquipped = _player.EquipmentComponent.IsItemEquipped(equipable); UseButton.Text = isItemEquipped ? "Unequip" : "Equip"; ThrowButton.Disabled = isItemEquipped; ThrowButton.FocusMode = isItemEquipped ? FocusModeEnum.None : FocusModeEnum.All; @@ -258,7 +281,7 @@ public partial class InventoryMenu : Control, IInventoryMenu UseButton.CallDeferred(MethodName.GrabFocus); } - private async Task HideUserActionPrompt() + private void HideUserActionPrompt() { UseItemPrompt.Hide(); UseButton.Hide(); @@ -267,6 +290,23 @@ public partial class InventoryMenu : Control, IInventoryMenu UseButton.ReleaseFocus(); ThrowButton.ReleaseFocus(); DropButton.ReleaseFocus(); + _currentlySelectedItem.GrabFocus(); + } + + private async Task EquipOrUnequipItem(EquipableItem equipable) + { + if (_player.EquipmentComponent.IsItemEquipped(equipable)) + { + ItemEffectLabel.Text = $"{equipable.GetType().Name} unequipped."; + _player.Unequip(equipable); + } + else + { + var itemSlot = _currentlySelectedItem; + ItemEffectLabel.Text = $"{equipable.GetType().Name} equipped."; + _player.Equip(equipable); + _currentlySelectedItem = itemSlot; + } } private async Task ShowInventoryInfo() @@ -275,149 +315,6 @@ public partial class InventoryMenu : Control, IInventoryMenu ItemEffectLabel.Show(); } - private async Task ChangeInventoryPage(InventoryPageNumber pageToChangeTo) - { - await ClearItems(); - await ToSignal(GetTree().CreateTimer(0.1f), "timeout"); - _currentIndex = 0; - _currentPageNumber = pageToChangeTo; - await RefreshInventoryScreen(); - Autoload.AudioManager.Play(SoundEffect.MoveThroughOptions); - } - - private async void PopulateInventory() - { - var inventory = Player.Inventory; - var numberOfItemsToDisplay = _currentPageNumber == InventoryPageNumber.FirstPage ? Mathf.Min(inventory.Items.Count, _itemsPerPage) : Mathf.Min(inventory.Items.Count - _itemsPerPage, _itemsPerPage); - var indexToStart = _currentPageNumber == InventoryPageNumber.FirstPage ? 0 : _itemsPerPage; - - ForwardArrow.Text = ""; - BackArrow.Text = ""; - - if (_currentPageNumber == InventoryPageNumber.FirstPage && inventory.Items.Count > _itemsPerPage) - { - ForwardArrow.Text = "►"; - BackArrow.Text = ""; - } - if (_currentPageNumber == InventoryPageNumber.SecondPage) - { - ForwardArrow.Text = ""; - BackArrow.Text = "◄"; - } - - - for (var i = 0; i < numberOfItemsToDisplay; i++) - { - var item = inventory.Items.ElementAt(i + indexToStart); - var itemScene = GD.Load(ITEM_SLOT_SCENE); - var itemSlot = itemScene.Instantiate(); - itemSlot.Item = item; - ItemsPage.AddChildEx(itemSlot); - - if (Player.EquipmentComponent.IsItemEquipped(itemSlot.Item)) - itemSlot.SetEquippedItemStyle(); - } - - if (ItemSlots.Length != 0) - { - ItemSlots.ElementAt(_currentIndex).SetSelectedItemStyle(); - if (Player.EquipmentComponent.IsItemEquipped(ItemSlots.ElementAt(_currentIndex).Item)) - ItemSlots.ElementAt(_currentIndex).SetEquippedSelectedItemStyle(); - } - } - - private async Task SetToUnselectedStyle(IItemSlot itemSlot) - { - await ToSignal(GetTree().CreateTimer(0.1f), "timeout"); - itemSlot.SetItemStyle(); - if (Player.EquipmentComponent.IsItemEquipped(itemSlot.Item)) - itemSlot.SetEquippedItemStyle(); - } - - private async Task SetToSelectedStyle(IItemSlot itemSlot) - { - await ToSignal(GetTree().CreateTimer(0.1f), "timeout"); - itemSlot.SetSelectedItemStyle(); - ItemDescriptionTitle.Text = $"{itemSlot.Item.ItemName}"; - ItemEffectLabel.Text = $"{itemSlot.Item.Description}"; - } - - private async Task EquipOrUnequipItem() - { - var itemSlot = ItemSlots[_currentIndex]; - if (itemSlot.Item is not EquipableItem) - return; - var equippableItem = (EquipableItem)itemSlot.Item; - if (Player.EquipmentComponent.IsItemEquipped(equippableItem)) - { - ItemEffectLabel.Text = $"{equippableItem.GetType().Name} unequipped."; - Player.EquipmentComponent.Unequip(equippableItem); - itemSlot.SetSelectedItemStyle(); - if (itemSlot.Item.ItemTag == ItemTag.BreaksOnChange) - Player.Inventory.Remove(equippableItem); - } - else - { - ItemEffectLabel.Text = $"{equippableItem.GetType().Name} equipped."; - Player.Equip(equippableItem); - itemSlot.SetEquippedSelectedItemStyle(); - } - - RefreshUIAfterUserSelection(); - } - - private async void UseButtonPressed() - { - UseButton.Disabled = true; - var currentItem = ItemSlots[_currentIndex].Item; - if (currentItem is EquipableItem) - await EquipOrUnequipItem(); - else - await Game.UseItem(currentItem); - - RefreshUIAfterUserSelection(); - UseButton.Disabled = false; - } - - private async void ThrowButtonPressed() - { - var currentItem = ItemSlots[_currentIndex].Item; - - Game.ThrowItem(currentItem); - Player.Inventory.Remove(currentItem); - - if (_currentIndex >= ItemSlots.Length - 1) - _currentIndex--; - if (_currentIndex <= 0) - _currentIndex = 0; - - _gameRepo.CloseInventory(); - } - - private async void DropButtonPressed() - { - var currentItem = ItemSlots[_currentIndex].Item; - Game.DropItem(currentItem); - Player.Inventory.Remove(currentItem); - - if (_currentIndex >= ItemSlots.Length - 1) - _currentIndex--; - if (_currentIndex <= 0) - _currentIndex = 0; - - _gameRepo.CloseInventory(); - } - - private async void RefreshUIAfterUserSelection() - { - SetProcessInput(false); - await HideUserActionPrompt(); - await ShowInventoryInfo(); - await RefreshInventoryScreen(); - await ToSignal(GetTree().CreateTimer(1f), "timeout"); - SetProcessInput(true); - } - private enum InventoryPageNumber { FirstPage, diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs.uid b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs.uid index d9843f4c..68950098 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs.uid +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.cs.uid @@ -1 +1 @@ -uid://cmtet15hi5oiy +uid://bi1xopts68paw diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.tscn b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.tscn index 895c3c17..c47e6f10 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.tscn +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/InventoryMenu.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=29 format=3 uid="uid://dlj8qdg1c5048"] +[gd_scene load_steps=27 format=3 uid="uid://dlj8qdg1c5048"] -[ext_resource type="Script" uid="uid://cmtet15hi5oiy" path="res://src/ui/inventory_menu/InventoryMenu.cs" id="1_l64wl"] +[ext_resource type="Script" uid="uid://bi1xopts68paw" path="res://src/ui/inventory_menu/InventoryMenu.cs" id="1_b6rkr"] [ext_resource type="Shader" uid="uid://cnphwvmr05hp1" path="res://src/ui/inventory_menu/InventoryMenu.gdshader" id="2_0fvsh"] [ext_resource type="FontFile" uid="uid://cm8j5vcdop5x0" path="res://src/ui/fonts/Mrs-Eaves-OT-Roman_31443.ttf" id="3_lm4o1"] +[ext_resource type="PackedScene" uid="uid://c005nd0m2eim" path="res://src/ui/inventory_menu/ItemSlot.tscn" id="4_aiji3"] [ext_resource type="FontFile" uid="uid://cb41qqmxqurj8" path="res://src/ui/fonts/FT88-Bold.ttf" id="4_rg5yb"] [ext_resource type="FontFile" uid="uid://dit3vylt7hmmx" path="res://src/ui/fonts/FT88-Regular.ttf" id="5_2qnnx"] [ext_resource type="LabelSettings" uid="uid://ca1q6yu8blwxf" path="res://src/ui/label_settings/InventoryMainTextBold.tres" id="6_tmdno"] @@ -71,67 +72,6 @@ font_color = Color(0.737255, 0.705882, 0.690196, 1) [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ct6ql"] -[sub_resource type="Animation" id="Animation_dg155"] -length = 0.001 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("InventoryInfo/HBoxContainer/PlayerInfo/HBoxContainer/VBoxContainer/ItemEffectLabel:visible") -tracks/0/interp = 1 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [true] -} -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("InventoryInfo/HBoxContainer/PlayerInfo/HBoxContainer/VBoxContainer/ItemEffectLabel:text") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(0), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [""] -} - -[sub_resource type="Animation" id="Animation_7by7u"] -resource_name = "status_up" -length = 2.5 -tracks/0/type = "value" -tracks/0/imported = false -tracks/0/enabled = true -tracks/0/path = NodePath("InventoryInfo/HBoxContainer/PlayerInfo/HBoxContainer/VBoxContainer/ItemEffectLabel:visible") -tracks/0/interp = 1 -tracks/0/loop_wrap = true -tracks/0/keys = { -"times": PackedFloat32Array(0, 2.5), -"transitions": PackedFloat32Array(1, 1), -"update": 1, -"values": [true, false] -} -tracks/1/type = "value" -tracks/1/imported = false -tracks/1/enabled = true -tracks/1/path = NodePath("InventoryInfo/HBoxContainer/PlayerInfo/HBoxContainer/VBoxContainer/ItemEffectLabel:text") -tracks/1/interp = 1 -tracks/1/loop_wrap = true -tracks/1/keys = { -"times": PackedFloat32Array(2.5), -"transitions": PackedFloat32Array(1), -"update": 1, -"values": [""] -} - -[sub_resource type="AnimationLibrary" id="AnimationLibrary_eivo2"] -_data = { -&"RESET": SubResource("Animation_dg155"), -&"status_up": SubResource("Animation_7by7u") -} - [node name="InventoryMenu" type="Control"] custom_minimum_size = Vector2(1440, 1080) layout_mode = 3 @@ -140,7 +80,8 @@ offset_right = 1440.0 offset_bottom = 1080.0 size_flags_horizontal = 3 size_flags_vertical = 3 -script = ExtResource("1_l64wl") +mouse_filter = 2 +script = ExtResource("1_b6rkr") [node name="BG" type="TextureRect" parent="."] material = SubResource("ShaderMaterial_i55tv") @@ -150,6 +91,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +mouse_filter = 2 texture = SubResource("PlaceholderTexture2D_3ynpe") expand_mode = 2 @@ -205,7 +147,6 @@ horizontal_alignment = 2 [node name="ItemsPage" type="VBoxContainer" parent="InventoryInfo/HBoxContainer/ItemInfo"] unique_name_in_owner = true layout_mode = 2 -size_flags_vertical = 0 theme_override_constants/separation = 15 alignment = 1 @@ -213,8 +154,59 @@ alignment = 1 custom_minimum_size = Vector2(0, 14) layout_mode = 2 +[node name="ItemSlot1" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot2" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot3" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot4" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot5" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot6" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot7" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot8" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot9" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="ItemSlot10" parent="InventoryInfo/HBoxContainer/ItemInfo/ItemsPage" instance=ExtResource("4_aiji3")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + [node name="PlayerInfo" type="VBoxContainer" parent="InventoryInfo/HBoxContainer"] layout_mode = 2 +mouse_filter = 2 theme_override_constants/separation = 20 [node name="VTBox" type="HBoxContainer" parent="InventoryInfo/HBoxContainer/PlayerInfo"] @@ -431,9 +423,3 @@ theme_override_styles/normal = SubResource("StyleBoxEmpty_ct6ql") button_mask = 0 text = "Drop" alignment = 0 - -[node name="AnimationPlayer" type="AnimationPlayer" parent="."] -unique_name_in_owner = true -libraries = { -&"": SubResource("AnimationLibrary_eivo2") -} diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.cs b/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.cs index af98f2d3..766a466d 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.cs +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.cs @@ -1,18 +1,19 @@ using Chickensoft.AutoInject; +using Chickensoft.Collections; using Chickensoft.Introspection; using Godot; +using System; using Zennysoft.Game.Abstractions; -using Zennysoft.Game.Implementation; using Zennysoft.Ma.Adapter; namespace Zennysoft.Game.Ma; [Meta(typeof(IAutoNode))] -public partial class ItemSlot : HBoxContainer, IItemSlot +public partial class ItemSlot : Button, IItemSlot { public override void _Notification(int what) => this.Notify(what); - [Dependency] public IPlayer Player => this.DependOn(); + [Dependency] private IPlayer _player => this.DependOn(); [Node] public TextureRect ItemTexture { get; set; } = default!; @@ -20,6 +21,8 @@ public partial class ItemSlot : HBoxContainer, IItemSlot [Node] public Label ItemCount { get; set; } = default!; + public AutoProp Item { get; } = new AutoProp(default); + private static LabelSettings ItemFont => GD.Load("res://src/ui/label_settings/MainTextBold.tres"); private static LabelSettings SelectedItemFont => GD.Load("res://src/ui/label_settings/MainTextFontItalicized.tres"); @@ -28,53 +31,89 @@ public partial class ItemSlot : HBoxContainer, IItemSlot private static LabelSettings SelectedEquippedItemFont => GD.Load("res://src/ui/label_settings/MainTextFontSelectedEquipped.tres"); - public void OnReady() - { - ItemName.Text = Item.ItemName; - ItemTexture.Texture = Item.GetTexture(); - Player.EquipmentComponent.EquippedWeapon.Sync += EquipableItem_Sync; - Player.EquipmentComponent.EquippedArmor.Sync += EquipableItem_Sync; - Player.EquipmentComponent.EquippedAccessory.Sync += EquipableItem_Sync; + public event Action ItemPressed; + public event Action ItemEnterFocus; + public event Action ItemExitFocus; - if (Item is IStackable stackableItem) - { - ItemCount.Text = $"{stackableItem.Count:D2}"; - ItemCount.Visible = true; - } - } + public bool IsSelected { get; set; } = false; - private void EquipableItem_Sync(EquipableItem obj) + public void OnResolved() { - if (Item is EquipableItem equipableItem && equipableItem == obj) - { - SetEquippedSelectedItemStyle(); - } - if (Item is EquipableItem unequippedItem && unequippedItem != obj) - { - SetItemStyle(); - } + Item.Changed += Item_Changed; + _player.EquipmentComponent.EquippedWeapon.Sync += EquipableItem_Sync; + _player.EquipmentComponent.EquippedArmor.Sync += EquipableItem_Sync; + _player.EquipmentComponent.EquippedAccessory.Sync += EquipableItem_Sync; + + FocusEntered += ItemSlot_FocusEntered; + FocusExited += ItemSlot_FocusExited; + + Pressed += ItemSlot_Pressed; } public void SetItemStyle() { - ItemName.LabelSettings = ItemFont; + if (_player.EquipmentComponent.IsItemEquipped(Item.Value) && IsSelected) + SetEquippedSelectedItemStyle(); + else if (_player.EquipmentComponent.IsItemEquipped(Item.Value)) + SetEquippedItemStyle(); + else if (IsSelected) + SetSelectedItemStyle(); + else + SetToUnselectedStyle(); } - public void SetSelectedItemStyle() + + public void SetToUnselectedStyle() { - if (Player.EquipmentComponent.IsItemEquipped(Item)) + SetItemFont(); + if (_player.EquipmentComponent.IsItemEquipped(Item.Value)) + SetEquippedItemStyle(); + } + + private void EquipableItem_Sync(EquipableItem item) => SetItemStyle(); + + private void ItemSlot_Pressed() + { + if (Item.Value == null) + return; + + ItemPressed?.Invoke(Item.Value); + } + + private void ItemSlot_FocusExited() => ItemExitFocus?.Invoke(this); + + private void ItemSlot_FocusEntered() => ItemEnterFocus?.Invoke(this); + + private void Item_Changed(InventoryItem obj) + { + if (obj == null) + return; + + ItemName.Text = obj.ItemName; + ItemTexture.Texture = obj.GetTexture(); + if (obj is IStackable stackableItem) + { + ItemCount.Text = $"{stackableItem.Count:D2}"; + ItemCount.Visible = true; + } + else + { + ItemCount.Text = string.Empty; + ItemCount.Visible = false; + } + } + + private void SetToSelectedStyle() => SetSelectedItemStyle(); + + private void SetSelectedItemStyle() + { + if (_player.EquipmentComponent.IsItemEquipped(Item.Value)) ItemName.LabelSettings = SelectedEquippedItemFont; else ItemName.LabelSettings = SelectedItemFont; } - public void SetEquippedItemStyle() - { - ItemName.LabelSettings = EquippedItemFont; - } + private void SetItemFont() => ItemName.LabelSettings = ItemFont; - public void SetEquippedSelectedItemStyle() - { - ItemName.LabelSettings = SelectedEquippedItemFont; - } + private void SetEquippedItemStyle() => ItemName.LabelSettings = EquippedItemFont; - public InventoryItem Item { get; set; } = default!; + private void SetEquippedSelectedItemStyle() => ItemName.LabelSettings = SelectedEquippedItemFont; } diff --git a/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.tscn b/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.tscn index dfc8b2ca..89fe32a0 100644 --- a/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.tscn +++ b/Zennysoft.Game.Ma/src/ui/inventory_menu/ItemSlot.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=7 format=3 uid="uid://c005nd0m2eim"] +[gd_scene load_steps=8 format=3 uid="uid://c005nd0m2eim"] [ext_resource type="Script" uid="uid://cglxk7v8hpesn" path="res://src/ui/inventory_menu/ItemSlot.cs" id="1_yttxt"] [ext_resource type="Texture2D" uid="uid://0r1dws4ajhdx" path="res://src/items/accessory/textures/MASK 01.PNG" id="2_7kdbd"] @@ -6,42 +6,65 @@ [ext_resource type="FontFile" uid="uid://bohbd123672ea" path="res://src/ui/fonts/FT88-Italic.ttf" id="4_vcxwm"] [ext_resource type="LabelSettings" uid="uid://bl5xpqyq8vjtv" path="res://src/ui/inventory_menu/InventoryLabelSettings.tres" id="5_a7hko"] +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lt1pw"] + [sub_resource type="LabelSettings" id="LabelSettings_lgjx0"] font = ExtResource("4_vcxwm") font_size = 30 font_color = Color(0, 0.682353, 0.937255, 1) -[node name="ItemSlot" type="HBoxContainer"] +[node name="ItemSlot" type="Button"] custom_minimum_size = Vector2(100, 60) -offset_right = 1748.0 -offset_bottom = 85.0 +anchors_preset = -1 +anchor_right = 0.885 +anchor_bottom = 0.093 +offset_right = 0.799927 +offset_bottom = -0.440002 +theme_override_styles/focus = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/disabled_mirrored = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/disabled = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/hover_pressed_mirrored = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/hover_pressed = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/hover_mirrored = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/hover = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/pressed_mirrored = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/pressed = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/normal_mirrored = SubResource("StyleBoxEmpty_lt1pw") +theme_override_styles/normal = SubResource("StyleBoxEmpty_lt1pw") +flat = true +alignment = 0 script = ExtResource("1_yttxt") -[node name="ReferenceRect" type="ReferenceRect" parent="."] +[node name="HBox" type="HBoxContainer" parent="."] +custom_minimum_size = Vector2(100, 60) +layout_mode = 0 +offset_right = 1700.0 +offset_bottom = 100.0 + +[node name="ReferenceRect" type="ReferenceRect" parent="HBox"] custom_minimum_size = Vector2(100, 0) layout_mode = 2 -[node name="ItemTexture" type="TextureRect" parent="."] +[node name="ItemTexture" type="TextureRect" parent="HBox"] unique_name_in_owner = true layout_mode = 2 texture = ExtResource("2_7kdbd") expand_mode = 2 -[node name="ReferenceRect2" type="ReferenceRect" parent="."] +[node name="ReferenceRect2" type="ReferenceRect" parent="HBox"] custom_minimum_size = Vector2(40, 0) layout_mode = 2 -[node name="ItemName" type="Label" parent="."] +[node name="ItemName" type="Label" parent="HBox"] unique_name_in_owner = true custom_minimum_size = Vector2(550, 50) layout_mode = 2 text = "Mask of the Goddess of Destruction" label_settings = SubResource("LabelSettings_lgjx0") vertical_alignment = 1 -autowrap_mode = 2 script = ExtResource("3_xlgl0") -[node name="ItemCount" type="Label" parent="."] +[node name="ItemCount" type="Label" parent="HBox"] unique_name_in_owner = true visible = false layout_mode = 2 diff --git a/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs b/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs index ac01f09d..c7755f49 100644 --- a/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs +++ b/Zennysoft.Game.Ma/src/ui/pause_menu/PauseDebugMenu.cs @@ -1,10 +1,8 @@ using Chickensoft.AutoInject; using Chickensoft.Introspection; using Godot; -using System; using System.Collections.Immutable; using System.Linq; -using Zennysoft.Game.Implementation; using Zennysoft.Ma.Adapter; namespace Zennysoft.Game.Ma;