November Merge

This commit is contained in:
Pal
2025-11-12 16:14:43 -08:00
96 changed files with 3725 additions and 870 deletions

View File

@@ -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<InputEvent> GetKeyboardOrJoypadInputsForAction(string action)
{
return (Array<InputEvent>)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<string> actions = null)
{
if (actions == null)
{
actions = new Array<string>();
}
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<InputEvent> GetKeyboardInputsForAction(string action)
{
return (Array<InputEvent>)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<InputEvent> GetJoypadInputsForAction(string action)
{
return (Array<InputEvent>)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
}
}

View File

@@ -0,0 +1 @@
uid://cfs4dgwrfvb11

View File

@@ -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.

View File

@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="300"
height="80"
viewBox="0 0 79.374999 21.166667"
version="1.1"
id="svg291"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
sodipodi:docname="update.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview293"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
width="1920px"
units="px"
borderlayer="true"
inkscape:showpageshadow="false"
inkscape:zoom="4"
inkscape:cx="121.625"
inkscape:cy="43.25"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="2552"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
inkscape:deskcolor="#d1d1d1"
showguides="true">
<sodipodi:guide
position="-15.575132,19.553027"
orientation="0,-1"
id="guide2089"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs288" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g2335"
transform="matrix(0.93072355,0,0,0.92874157,17.062816,18.409989)"
style="stroke-width:0.85374062;stroke-dasharray:none;stroke:none">
<path
id="rect2095"
style="fill:#349684;fill-opacity:1;stroke:#152f2c;stroke-width:0.56916041;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:normal"
d="m 24.364404,-16.012976 a 1.7811118,1.7811118 0 0 0 -1.568379,0.938961 1.7811118,1.7811118 0 0 0 -1.557528,-0.918291 1.7811118,1.7811118 0 0 0 -1.383895,0.660942 c -1.816891,-1.243302 -4.115395,-0.49791 -5.162476,1.678967 l -2.844787,5.914368 c -1.050638,2.1842718 -0.431729,4.957652 1.388029,6.2187419 1.819758,1.26108987 4.130958,0.5177068 5.181596,-1.666565 l 1.240234,-2.5786539 h 6.335531 l 1.240234,2.5786539 c 1.050638,2.1842718 3.361322,2.92765487 5.181079,1.666565 1.819758,-1.2610899 2.439184,-4.0344701 1.388546,-6.2187419 L 30.9578,-13.652397 c -1.050637,-2.184271 -3.361838,-2.927654 -5.181596,-1.666565 -1.97e-4,1.37e-4 -3.19e-4,3.8e-4 -5.16e-4,5.17e-4 a 1.7811118,1.7811118 0 0 0 -1.411284,-0.694531 z" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect2101"
width="1.403165"
height="4.2796535"
x="17.394386"
y="-13.427068"
ry="0.75541198"
rx="0.75380331" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect2103"
width="1.403165"
height="4.2796535"
x="-11.988823"
y="-20.235798"
ry="0.75380331"
rx="0.75541198"
transform="rotate(90)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse2105"
cx="28.192503"
cy="2.7587082"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse2107"
cx="28.216581"
cy="4.7943339"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse2109"
cx="30.279696"
cy="2.6987464"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse2111"
cx="30.320311"
cy="4.767447"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect2117"
width="1.109892"
height="2.1669323"
x="22.090807"
y="-10.531444"
ry="0.52878839"
rx="0.52766228" />
</g>
<path
id="rect1625"
style="fill:#50fa7b;fill-opacity:1;stroke:#0f451d;stroke-width:0.593381;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 50.52517,5.3570989 c -0.643062,0 -1.160653,0.5900216 -1.160653,1.3229166 v 1.0764201 h -1.07642 c -0.732895,0 -1.322916,0.5175908 -1.322916,1.1606533 0,0.643061 0.590021,1.1611691 1.322916,1.1611691 h 1.07642 v 1.076421 c 0,0.732895 0.517591,1.322917 1.160653,1.322917 0.643062,0 1.160653,-0.590022 1.160653,-1.322917 v -1.076421 h 1.076937 c 0.732895,0 1.322916,-0.5181081 1.322916,-1.1611691 0,-0.6430625 -0.590021,-1.1606533 -1.322916,-1.1606533 H 51.685823 V 6.6800155 c 0,-0.732895 -0.517591,-1.3229166 -1.160653,-1.3229166 z" />
<path
id="path2678"
style="fill:#50fa7b;fill-opacity:1;stroke:#0f451d;stroke-width:0.593381;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 57.791691,5.357099 c -0.643062,0 -1.160653,0.5900216 -1.160653,1.3229166 v 1.0764201 h -1.07642 c -0.732895,0 -1.322916,0.5175908 -1.322916,1.1606522 0,0.643062 0.590021,1.1611701 1.322916,1.1611701 h 1.07642 v 1.076421 c 0,0.732895 0.517591,1.322917 1.160653,1.322917 0.643062,0 1.160653,-0.590022 1.160653,-1.322917 v -1.076421 h 1.076937 c 0.732895,0 1.322916,-0.5181081 1.322916,-1.1611701 0,-0.6430614 -0.590021,-1.1606522 -1.322916,-1.1606522 H 58.952344 V 6.6800156 c 0,-0.732895 -0.517591,-1.3229166 -1.160653,-1.3229166 z" />
<rect
style="fill:#000000;fill-opacity:1;stroke-width:0.72829;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
id="rect413"
width="10.200269"
height="8.7847834"
x="-21.316095"
y="3.0372066" />
<rect
style="fill:#000000;fill-opacity:1;stroke-width:0.622475;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
id="rect415"
width="7.9928446"
height="15.312933"
x="-20.546837"
y="12.977094"
ry="4.375123"
transform="matrix(0.82192626,0.56959391,-0.43346431,0.90117073,0,0)"
rx="4.375123" />
<rect
style="fill:#000000;fill-opacity:1;stroke-width:0.622475;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
id="rect471"
width="7.9928446"
height="15.312933"
x="9.1578636"
y="-5.7980561"
ry="4.375123"
transform="matrix(-0.82192626,0.56959391,0.43346431,0.90117073,0,0)"
rx="4.375123" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect1137"
width="1.403165"
height="4.2796535"
x="-22.240166"
y="3.9990206"
ry="0.70158249"
rx="0.70158249" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect1295"
width="1.403165"
height="4.2796535"
x="5.4372649"
y="19.398754"
ry="0.70158249"
rx="0.70158249"
transform="rotate(90)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse1297"
cx="-13.849753"
cy="-1.3102485"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse1299"
cx="-13.825675"
cy="0.72537726"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse1301"
cx="-11.762562"
cy="-1.3702103"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<ellipse
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="ellipse1303"
cx="-11.721946"
cy="0.69849032"
rx="0.82257289"
ry="0.79261416"
transform="rotate(-30)" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="path2085"
cx="-17.863199"
cy="3.3752983"
r="1.7811118" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="circle2087"
cx="-14.737003"
cy="3.3542202"
r="1.7811118" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
id="rect2091"
width="0.98221546"
height="1.917659"
x="-16.946886"
y="7.1795278"
ry="0.49110773"
rx="0.49110773" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1 @@
uid://dcff0mowkn6km

View File

@@ -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"]

View File

@@ -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

View File

@@ -0,0 +1 @@
uid://cholww48njaeh

View File

@@ -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"

View File

@@ -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()

View File

@@ -0,0 +1 @@
uid://cul6evy00vr55

View File

@@ -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

View File

@@ -0,0 +1 @@
uid://yqplm6a6focp

View File

@@ -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()

View File

@@ -0,0 +1 @@
uid://1t3qhgrro2es

View File

@@ -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"]

View File

@@ -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={

View File

@@ -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<EquipableItem> _equippedAccessory;
public event Action<EquipableItem> 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)

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<IAppRepo>.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<string> _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<IAppRepo, AppRepo>();
container.RegisterSingleton<IAppLogic, AppLogic>();
_container = new SimpleInjector.Container();
_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
_container.RegisterSingleton<IAppRepo, AppRepo>();
_container.RegisterSingleton<IAppLogic, AppLogic>();
_container.RegisterSingleton<IFileSystem, FileSystem>();
_container.RegisterSingleton<ISaveFileManager, SaveFileManager>();
_saveFileManager = _container.GetInstance<ISaveFileManager>();
_optionsSavePath = $"{OS.GetUserDataDir()}/options.json";
_controllerSavePath = $"{OS.GetUserDataDir()}/controls.json";
Task.Run(() => _saveFileManager.ReadFromFile<OptionsData>(_optionsSavePath).ContinueWith((data) =>
{
if (data.IsCompletedSuccessfully)
OptionsMenu.CallDeferred("Load", data.Result);
}));
Task.Run(() => _saveFileManager.ReadFromFile<string>(_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<IAppRepo>();
AppLogic = container.GetInstance<IAppLogic>();
OptionsMenu.OptionsMenuExited += OptionsMenu_OptionsMenuExited;
AppRepo = _container.GetInstance<IAppRepo>();
AppLogic = _container.GetInstance<IAppLogic>();
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<ISaveFileManager>();
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());

View File

@@ -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

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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<AudioStream>(sfxPath + effect + ".ogg"));
_audioPlayer.Bus = "SFX";
}
public void Play(SoundEffect soundEffect)

View File

@@ -1 +1 @@
uid://dnh47mgvakhvh
uid://ry5iewox61vs

View File

@@ -1 +1 @@
uid://btpqk2uvk834y
uid://t0j1h13hgo0t

View File

@@ -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);

View File

@@ -19,6 +19,7 @@ public partial class Inventory : Node, IInventory
private const int _maxInventorySize = 20;
public event Action<string> 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)
{

View File

@@ -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]

View File

@@ -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];

View File

@@ -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")]

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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);
}

View File

@@ -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
"

View File

@@ -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);
}

View File

@@ -0,0 +1 @@
uid://bhfrdlnv7xyf6

View File

@@ -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<JoypadRemapButton> _actionJoyMap = [];
private List<KeyboardRemapButton> _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<IFileSystem, FileSystem>();
_container.RegisterSingleton<ISaveFileManager, SaveFileManager>();
_saveFileManager = _container.GetInstance<ISaveFileManager>();
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<InputMapButton>(_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<InputMapButton>(_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<InputMapButton>(_actionJoyMap);
foreach (var button in allButtons)
button.Disabled = true;
ReleaseFocus();
}
}

View File

@@ -0,0 +1 @@
uid://c6lw5yp8p0wb5

View File

@@ -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")

View File

@@ -0,0 +1 @@
uid://bwd066xv65pbi

View File

@@ -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();
}
}

View File

@@ -0,0 +1 @@
uid://bo7vk56h1lr07

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1 @@
uid://b70br20xue678

View File

@@ -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; }
}

View File

@@ -0,0 +1 @@
uid://jrl2hyudv6jn

View File

@@ -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));
}
}

View File

@@ -0,0 +1 @@
uid://cjxmdvhixcj6e

View File

@@ -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 "

View File

@@ -99,173 +99,179 @@ public partial class Player : CharacterBody3D, IPlayer, IProvide<IPlayer>
public void Initialize()
{
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
var container = new SimpleInjector.Container();
container.Register<IPlayerLogic, PlayerLogic>(Lifestyle.Singleton);
PlayerLogic = container.GetInstance<IPlayerLogic>();
PlayerLogic.Set(this as IPlayer);
PlayerLogic.Set(Settings);
PlayerLogic = container.GetInstance<IPlayerLogic>();
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<IPlayer>
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<PackedScene>("res://src/items/throwable/ThrowableItem.tscn");
var throwItem = itemScene.Instantiate<ThrowableItem>();
GetTree().Root.AddChildEx(throwItem);
throwItem.GlobalPosition = CurrentPosition + new Vector3(0, 3.5f, 0);
throwItem.GlobalRotation = GlobalRotation;
var itemScene = GD.Load<PackedScene>("res://src/items/throwable/ThrowableItem.tscn");
var throwItem = itemScene.Instantiate<ThrowableItem>();
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();
}
}

View File

@@ -0,0 +1,3 @@
[gd_resource type="Shortcut" format=3 uid="uid://dumkrjur22k2a"]
[resource]

View File

@@ -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;

View File

@@ -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());

View File

@@ -0,0 +1,7 @@
using Chickensoft.GodotNodeInterfaces;
namespace Zennysoft.Game.Ma;
public interface IInventoryMenu : IControl
{
}

View File

@@ -0,0 +1 @@
uid://nhhwcce8ma3a

View File

@@ -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<InventoryItem> Item { get; }
public bool IsSelected { get; set; }
public void SetItemStyle();
public void SetSelectedItemStyle();
public void SetEquippedItemStyle();
public void SetEquippedSelectedItemStyle();
public event Action<InventoryItem> ItemPressed;
public event Action<IItemSlot> ItemEnterFocus;
public event Action<IItemSlot> ItemExitFocus;
}

View File

@@ -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<IPlayer>();
[Dependency] private IGame _game => this.DependOn<IGame>();
[Dependency] private IGameRepo _gameRepo => this.DependOn<IGameRepo>();
[Dependency] public IGame Game => this.DependOn<IGame>();
[Dependency] public IPlayer Player => this.DependOn<IPlayer>();
private List<IItemSlot> 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<IItemSlot>()];
#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<InventoryItem>();
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<PackedScene>(ITEM_SLOT_SCENE);
var itemSlot = itemScene.Instantiate<IItemSlot>();
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,

View File

@@ -1 +1 @@
uid://cmtet15hi5oiy
uid://bi1xopts68paw

View File

@@ -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")
}

View File

@@ -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<IPlayer>();
[Dependency] private IPlayer _player => this.DependOn<IPlayer>();
[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<InventoryItem> Item { get; } = new AutoProp<InventoryItem>(default);
private static LabelSettings ItemFont => GD.Load<LabelSettings>("res://src/ui/label_settings/MainTextBold.tres");
private static LabelSettings SelectedItemFont => GD.Load<LabelSettings>("res://src/ui/label_settings/MainTextFontItalicized.tres");
@@ -28,53 +31,89 @@ public partial class ItemSlot : HBoxContainer, IItemSlot
private static LabelSettings SelectedEquippedItemFont => GD.Load<LabelSettings>("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<InventoryItem> ItemPressed;
public event Action<IItemSlot> ItemEnterFocus;
public event Action<IItemSlot> 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;
}

View File

@@ -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

View File

@@ -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;