Compare commits

..

150 Commits

Author SHA1 Message Date
zenny 34c125e6bb no message 2026-02-15 01:14:09 -08:00
zenny 66905c9b53 Overhaul 2026-02-15 01:06:46 -08:00
Pal a1f4a29eb3 Mask of Zeal, Mask Placeholder, Health Item Placeholder, VT pickup animation added. Temp Environment added to Final Floor. 2026-02-15 00:57:19 -08:00
zenny a6ea1b1873 laptop 2026-02-14 19:26:33 -08:00
Pal 47ceb2f613 Remove E, fix UI, Altar scale better 2026-02-14 16:49:16 -08:00
zenny bf6b0d50c3 Additional in progress changes 2026-02-13 23:39:49 -08:00
zenny c7603a163f Revert "update"
This reverts commit fe0241ac88.
2026-02-13 16:33:44 -08:00
zenny a20c80d922 Remaining changes 2026-02-13 16:33:30 -08:00
zenny e14007b7f4 Merge branch 'main' into item_changes 2026-02-13 16:24:40 -08:00
zenny b17c134c9a Add debug info folder that got filtered by git 2026-02-13 16:12:34 -08:00
zenny fe0241ac88 update 2026-02-13 16:11:38 -08:00
zenny 0ab6ef1343 In progress item changes 2026-02-13 15:59:10 -08:00
Pal 638946d23a UI Mockup Ver.2 With Correct Transparencies 2026-02-13 15:48:04 -08:00
Pal b56668dcbe 3D Render Icons Wave 1 Added 2026-02-13 14:44:03 -08:00
zenny d6faf8642a Fix up debug info overlay 2026-02-13 10:14:42 -08:00
zenny 68b1455c53 Fix restorative 2026-02-12 23:28:51 -08:00
zenny 9615e1e251 Audio fade 2026-02-12 22:42:18 -08:00
zenny c755485855 Add sample Stele to overworld 2026-02-12 21:39:10 -08:00
zenny d503413140 Fix up effect items
Fix up minimap
Add some debug info
2026-02-12 20:49:09 -08:00
Pal ac31c3ae65 Deleted a bunch of unused files.
Added environment to A2 exit, deleted environment from other A2 rooms.
2026-02-12 20:43:52 -08:00
Pal 549040c339 Gaps Fixed, Overworld Visual Fixes, Some lighting and additions to block rooms, may have accidentally moved a folder but cant find which. 2026-02-12 20:17:13 -08:00
zenny c246d8d654 Fix crit calculation and bonus attack/def/luck 2026-02-12 02:58:30 -08:00
zenny b475df6f68 Implement most jewels 2026-02-12 02:36:25 -08:00
zenny 230b47061d Add item spawn menu
Fix game over bug
Start adding more implementation for jewels
2026-02-11 15:25:20 -08:00
zenny 8ce38c3c13 Redesign and reimplement inventory menu
Add jewels but no implementation yet (needed redesign of inventory menu to function correctly)
2026-02-11 04:08:42 -08:00
zenny 5451f0b31f Persuader 2026-02-10 18:42:46 -08:00
zenny 92b4e8662f Add projectiles 2026-02-10 18:03:53 -08:00
zenny 2f377d2d7a Add curse element, increase throw speed/damage of Ciello 2026-02-10 15:37:12 -08:00
zenny 363ee1cd33 Turn more HQs on 2026-02-10 15:02:55 -08:00
zenny 97198afe18 Fix dropped/thrown item behavior 2026-02-10 14:57:41 -08:00
Pal fdc4a6f2c1 Small Map Fixes 2026-02-10 01:55:56 -08:00
Pal 843a100218 Small Changes 2026-02-10 01:21:47 -08:00
Pal 8001556f37 Item Icons Wave 2 2026-02-10 00:49:15 -08:00
zenny 90d054a3c6 Clean up events, add weapon tag for weaker on swing 2026-02-09 23:50:15 -08:00
zenny aba325ff2b Implement plasma sword instakill 2026-02-09 23:01:52 -08:00
zenny bfaa324e6a Fix references and start more weapon tag implementation 2026-02-09 22:16:49 -08:00
zenny f08c69fa10 Add weapons 2026-02-09 21:37:40 -08:00
zenny 9d6aa6d88d Update item icons 2026-02-09 21:05:45 -08:00
zenny 654e368a65 Fix exported project not loading issue 2026-02-09 20:24:48 -08:00
zenny ce727b523a Change import settings for textures 2026-02-09 18:52:31 -08:00
Pal 6a474576f0 wave 1 of item icons
(cherry picked from commit 6355ffb26d1199c47486e522902c08e878d18317)
2026-02-09 17:20:39 -08:00
zenny 8dd194a202 Fix level up, fix gallery room height, fix enemy heights, fix gospel of dimension spawn height, put minimap on right visual layer for some floors, add script to floor scene file 2026-02-06 03:24:34 -08:00
zenny 70a33d68cf Add navigation regions to all floors
Actually fix the annoying LookAt bug for real this time I hope
2026-02-06 00:00:22 -08:00
zenny da8c4209d7 Fix annoying issue with LookAt
Add jump scare
2026-02-05 20:10:41 -08:00
zenny c6fbc9f553 Rework dimmable audio stream 2026-02-05 14:45:28 -08:00
zenny 36b851254e Add collisions to A2 corridors
Start implementation of fading ambient audio in/out on level load
2026-02-05 14:17:45 -08:00
zenny 147f04d2ff Modify palm of heaven, remove random box item in altar 2026-02-05 10:52:13 -08:00
zenny 8ea881edb3 Item holy and all element implementation, still need to work out some of the math for it
Fix bonus defense for weapons
2026-02-05 10:48:24 -08:00
zenny 8a99771491 Add more boxes, implement weapon that gets stronger on lower HP 2026-02-04 23:51:07 -08:00
zenny d45bc67722 Partially implementation of box items 2026-02-04 22:30:10 -08:00
zenny 13ebe54474 Fix some control issues 2026-02-04 02:34:23 -08:00
zenny b9a1888bfc Add implementation for box item 2026-02-04 02:11:00 -08:00
zenny 5ae556cb4b Fix some broken stuff (player spawn, disabled input after loading next floor) 2026-02-04 02:04:04 -08:00
zenny 52dc8fb9e4 Box item implementation 2026-02-04 01:49:17 -08:00
zenny affa5e1f79 Player step SFX pitch change 2026-02-03 22:15:02 -08:00
Pal 35a625f636 merge 2026-02-03 21:06:28 -08:00
Pal d5de5f7379 player anim changes 2026-02-03 21:05:20 -08:00
zenny 3e6e21977e Fix Altar scaling 2026-02-03 20:52:14 -08:00
Pal 4a2d131276 Boss floor changes minor 2026-02-03 19:54:05 -08:00
Pal d9c2ba7ed1 Added Memorial Steles, boss A floor visual polish, re-enabled overworld distance scenery 2026-02-03 04:18:25 -08:00
zenny 051ffbbcb1 Fix bug with Boss A (hit box didn't get scaled correctly)
Fix reset of death animation
2026-02-03 02:24:54 -08:00
zenny 9747d7d2c5 Revert boss floor environment lighting change 2026-02-03 02:04:21 -08:00
zenny 34dce8c5a2 Loading screen fixes, transition screen UI fixes 2026-02-03 01:47:42 -08:00
zenny 51010c4f7d Fix overworld collisions 2026-02-03 00:34:19 -08:00
zenny fd96eb2dc9 Fix minimap height
Rework boss code to separate boss A concerns from demon wall
2026-02-02 23:24:48 -08:00
zenny 51c8f26e50 Minor fixes for NPC dialogue stuff
Add collisions for overworld
2026-02-02 22:39:33 -08:00
zenny 4c90eb6f07 Fix additional collisions 2026-02-02 21:59:48 -08:00
zenny 30f0a078a9 Boss A fixes 2026-02-02 20:01:54 -08:00
zenny 6e4a4d605c Partially fix boss room A, only one guy moves though 2026-02-02 00:27:11 -08:00
Pal 836b9eb26d idk merghhe 2026-02-01 16:48:25 -08:00
Pal cb2df83079 idk 2026-02-01 16:48:02 -08:00
zenny 0282ef68f3 Collisions again 2026-02-01 16:00:44 -08:00
Pal 1678d79bbd Added E symbol to Exit floors 2026-02-01 14:02:52 -08:00
Pal 20d2890b37 Pit A, Dismantled, Puer texture fixes 2026-02-01 13:46:14 -08:00
Pal e85c8d51f1 1A fix 2026-02-01 05:07:13 -08:00
Pal 3e178257aa merger 2026-02-01 05:03:10 -08:00
Pal 25b6d53ec4 Minimap added to all floors, all minimaps added and refined.
Less overlap neccessary for revealer cubes
2026-02-01 05:02:15 -08:00
zenny a9ed8fda10 Add minimap shadows 2026-01-31 18:31:50 -08:00
Pal 4801d7d9b3 Special Collision Maps Added 2026-01-31 14:15:43 -08:00
Pal f08817a586 Merging 2026-01-30 20:35:10 -08:00
Pal 4b23c2ca6f Floor Maps and room updates 2026-01-30 20:34:07 -08:00
zenny 39b2bc631d Collisions for floor set B 2026-01-29 01:12:44 -08:00
zenny f346f0f529 Fix up Set A room collisions more and organize node structure 2026-01-28 00:53:35 -08:00
zenny 4ffe04fcff Exploding wall implementation 2026-01-26 02:15:03 -08:00
zenny 2ef838f270 Forgot a bit 2026-01-26 01:43:40 -08:00
zenny e63a94210c Looking into room collisions we need; set up plastique behavior 2026-01-25 19:40:59 -08:00
zenny 865934399d Boss fixes, various changes 2026-01-25 19:01:34 -08:00
zenny 2622ed4423 Fix maze floor 2026-01-25 16:56:05 -08:00
zenny 79dd6eb33a Fix player height 2026-01-25 16:06:20 -08:00
Pal bba0bb5ecd Minimap Reveal system tscns added, floor adjustments, collisions added for mazefloor 2026-01-25 02:19:11 -08:00
Pal d6b20ce4c2 Merge branch 'main' of https://git.zenny.quest/zenayi/GameJamDungeon 2026-01-24 18:55:38 -08:00
Pal 60d8c55c7d Finished All Basic Floor layouts 2026-01-24 18:55:35 -08:00
zenny d3a3c18d13 Fix doors 2026-01-24 15:13:27 -08:00
Pal 12993bced0 Floors 1ABC and 2ABC complete 2026-01-23 18:02:20 -08:00
Pal 8fd7a5133d A1 Floor Progress 2026-01-22 17:54:12 -08:00
Pal 97472f2a61 Deleted old unused Corridor tscns, B set (Floors 9-15) complete 2026-01-22 16:14:58 -08:00
zenny 5284a7c00d Some geometry fixes, fix spawn height for overworld 2026-01-22 01:08:25 -08:00
Pal 92b39c1ee9 Lighting for new rooms etc 2026-01-21 14:46:48 -08:00
zenny faf3288061 Script types for block rooms 2026-01-21 01:59:12 -08:00
zenny b715d6b459 Add collisions to block rooms 2026-01-21 01:25:28 -08:00
zenny 9897acffac Unlockable door implementation (currently opens with regular attack) 2026-01-21 00:50:21 -08:00
zenny aa9e14c498 Make overworld doors be of unlockable type, add behavior later 2026-01-20 23:33:28 -08:00
zenny 945c5e14bb Licking all the room files 2026-01-20 22:01:22 -08:00
zenny a1f67c3d71 Fix dying loop, add death counter, add die button to debug 2026-01-20 21:18:10 -08:00
Pal 670f8baabf Added/Fixed Rooms 2026-01-20 18:29:21 -08:00
Pal 6a62d3d943 Room fixes, started mapping floors 2026-01-17 00:00:37 -08:00
zenny 3fe45cb3e7 First floor set collisions 2026-01-15 01:37:57 -08:00
Pal db218f26e7 Merge branch 'main' of https://git.zenny.quest/zenayi/GameJamDungeon 2026-01-14 00:46:57 -08:00
Pal eb4e901c8a Boss 1 death animation animations added, wall explosion animation added. 2026-01-14 00:46:15 -08:00
zenny 48a00933d9 Adjust sprite sizes to accomodate pixel melt 2026-01-13 23:12:49 -08:00
Pal be8bbcac28 Corridor Assetts Implemented 2026-01-13 23:06:06 -08:00
zenny c5cb586e4b Change item effect service to only affect regular enemy types for strong item effects 2026-01-13 21:38:12 -08:00
zenny 1e97eb9ede Elemental resistance calculation changes 2026-01-13 21:25:09 -08:00
Pal 0591dccc31 Merge branch 'main' of https://git.zenny.quest/zenayi/GameJamDungeon 2026-01-12 22:06:57 -08:00
Pal 70f7642d0f Special room with bombable wall added + start on plastique explosoin 2026-01-12 22:06:43 -08:00
zenny 7e8826f143 Restore health fountain 2026-01-12 20:26:05 -08:00
Pal f13afc9fdc Re-scaled environment models imported and implemented. 2026-01-12 19:39:34 -08:00
zenny 04543fcfac Add plastique item
Add Door object
Initial work for look up animation
2026-01-02 17:06:01 -08:00
zenny 5b9de11e5a Add Resume/Exit buttons to pause menu, handle logic for returning to main menu and starting a new game 2025-12-05 20:05:28 -08:00
zenny 678916be89 Enemy viewer revamp, loading screen improvement 2025-12-05 16:30:13 -08:00
Pal f39bd8ecdb Added many player.tscn animated sprite2Ds 2025-12-04 20:21:24 -08:00
Pal 76f4adc5be gallery and options rendered images set to linear filterling 2025-12-04 01:18:03 -08:00
Pal 95227946d1 Merge branch 'main' of https://git.zenny.quest/zenayi/GameJamDungeon 2025-12-03 23:54:32 -08:00
Pal 1ee3e97f85 Overworld Gate object added, visual assetts added, test animation added 2025-12-03 23:54:20 -08:00
zenny 6f90a0985a General UI Work 2025-12-03 23:21:29 -08:00
zenny 34742d568e Play test animation 2025-12-03 22:29:24 -08:00
Pal 3c369f79f7 Added shadows to enemies 2025-12-01 21:05:33 -08:00
Pal fe45f0bcf2 Screen renders added, fonts added, altar model updated 2025-12-01 20:13:15 -08:00
zenny edff41af22 Edit dialogue balloons 2025-12-01 01:01:33 -08:00
zenny 4ee4e02a51 Take damage/Screen shake 2025-11-30 22:35:26 -08:00
zenny 8f8cc217dc Death animation 2025-11-30 21:37:31 -08:00
zenny c491ea5050 Gallery Menu initial implementation 2025-11-30 20:17:08 -08:00
Pal 725547d388 Late November Merge 2025-11-26 02:38:43 -08:00
Pal 539430d112 VFX Assetts Added 2025-11-26 02:37:45 -08:00
zenny 46402401b4 Add basic implementation for footsteps
Add disengage to eden pillar
Fix level up sound effect trigger
2025-11-26 02:33:03 -08:00
zenny ed9e611fd9 Work SFX work
Fix up Eden Pillar behavior
2025-11-26 02:12:24 -08:00
zenny db7a1df1f7 Started implementing SFX
Fixed Shield animation jumps and secondary attack
Fixed demon wall stone behavior
Made overworld ambient sounds unpausable
2025-11-25 03:04:07 -08:00
Pal 3e8c11d55d VFX assetts imported 2025-11-22 15:55:13 -08:00
zenny 613fc3bf60 Setup player screen FX 2025-11-19 00:05:20 -08:00
zenny f69e219643 Add game tab to options menu, fix focus of GUI elements, add Delete option for save file 2025-11-18 20:28:43 -08:00
Pal 0afbf38bf9 Merge branch 'main' of https://git.zenny.quest/zenayi/GameJamDungeon 2025-11-17 19:18:01 -08:00
Pal 23fdf7309d VFX Assetts Added 2025-11-17 19:17:52 -08:00
zenny fcffdb3b35 Fix shadows 2025-11-16 20:07:44 -08:00
zenny 7210133330 Add projectiles to various enemies 2025-11-16 16:35:48 -08:00
Pal e7bae342c9 SFX additions 2025-11-16 16:12:21 -08:00
Pal c22fde3bb5 November Merge 2025-11-12 16:14:43 -08:00
Pal 7cb5b20293 weird things I cant undo 2025-11-12 16:14:14 -08:00
zenny a5846e08dc Rewrite and simplify Inventory Menu, various fixes for item effects 2025-11-04 01:12:16 -08:00
zenny 7b7fc910bd Button remapping work 2025-11-03 02:48:05 -08:00
zenny 9fc875eda5 Add options menu persistence 2025-10-30 23:59:05 -07:00
13398 changed files with 153166 additions and 80501 deletions
+5
View File
@@ -715,3 +715,8 @@ healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
/Zennysoft.Game.Ma/src/map/dungeon/models/Area 2/Puer/A2-Puer.glb.import
/Zennysoft.Game.Ma/src/audio/AMB/amb_beach.wav.import
/Zennysoft.Game.Ma/src/audio/AMB/amb_perlin.wav.import
/Zennysoft.Game.Ma/src/audio/AMB/amb_white_noise.wav.import
/Zennysoft.Game.Ma/src/audio/AMB/amb_wind_loop_altar.wav.import
@@ -10,6 +10,8 @@ public interface IAppRepo : IDisposable
event Action? MainMenuEntered;
event Action? DataViewerExited;
void SkipSplashScreen();
void OnMainMenuEntered();
@@ -19,4 +21,6 @@ public interface IAppRepo : IDisposable
void OnExitGame();
void OnGameOver();
void OnDataViewerExited();
}
@@ -1,8 +0,0 @@
namespace Zennysoft.Game.Abstractions;
public interface IStackable
{
int Count { get; }
void SetCount(int count);
}
@@ -2,5 +2,5 @@
public interface IHealthPack
{
public double RestoreAmount { get; }
public int RestoreAmount { get; }
}
@@ -11,4 +11,8 @@ public interface ISaveFileManager
public Task<T?> ReadFromFile<T>(params IJsonTypeInfoResolver?[] resolvers);
public Task<T?> ReadFromFile<T>(string filePath, params IJsonTypeInfoResolver?[] resolvers);
public void DeleteSaveData(string filePath);
public void DeleteSaveData();
}
@@ -0,0 +1,54 @@
namespace Zennysoft.Game.Implementation;
using Chickensoft.GodotNodeInterfaces;
using Godot;
using Zennysoft.Game.Abstractions;
public partial class DimmableAudioStreamPlayer3D : AudioStreamPlayer3D, IDimmableAudioStreamPlayer
{
#region Constants
// -60 to -80 is considered inaudible for decibels.
public const float VOLUME_DB_INAUDIBLE = -80f;
public const double FADE_DURATION = 3d; // seconds
#endregion Constants
public ITween? FadeTween { get; set; }
public float InitialVolumeDb;
public override void _Ready()
{
InitialVolumeDb = VolumeDb;
VolumeDb = VOLUME_DB_INAUDIBLE;
}
public void FadeIn()
{
SetupFade(InitialVolumeDb, Tween.EaseType.Out);
Play();
}
public void FadeOut()
{
SetupFade(VOLUME_DB_INAUDIBLE, Tween.EaseType.In);
FadeTween!.TweenCallback(Callable.From(Stop));
}
public void SetupFade(float volumeDb, Tween.EaseType ease)
{
FadeTween?.Kill();
FadeTween = GodotInterfaces.Adapt<ITween>(CreateTween());
FadeTween.TweenProperty(
this,
"volume_db",
volumeDb,
FADE_DURATION
).SetTrans(Tween.TransitionType.Circ).SetEase(ease);
}
public override void _EnterTree() => FadeIn();
public override void _ExitTree() => FadeOut();
}
@@ -0,0 +1,10 @@
using Chickensoft.Collections;
namespace Zennysoft.Game.Implementation;
public interface IStackable
{
AutoProp<int> Count { get; }
void SetCount(int count);
}
@@ -74,4 +74,8 @@ public class SaveFileManager : ISaveFileManager
var json = JsonSerializer.Serialize(gameData, jsonOptions);
await _fileSystem.File.WriteAllTextAsync(filePath, json);
}
public void DeleteSaveData() => DeleteSaveData(_defaultSaveLocation);
public void DeleteSaveData(string filePath) => _fileSystem.File.Delete(filePath);
}
@@ -13,7 +13,7 @@
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.7.6" />
<PackageReference Include="System.IO.Abstractions" Version="22.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zennysoft.Game.Abstractions\Zennysoft.Game.Abstractions.csproj" />
@@ -8,6 +8,7 @@ public class AppRepo : IAppRepo
public event Action? MainMenuEntered;
public event Action? GameEntered;
public event Action? GameExited;
public event Action? DataViewerExited;
private bool _disposedValue;
@@ -21,6 +22,8 @@ public class AppRepo : IAppRepo
public void OnGameOver() => GameExited?.Invoke();
public void OnDataViewerExited() => DataViewerExited?.Invoke();
protected void Dispose(bool disposing)
{
if (!_disposedValue)
@@ -24,6 +24,8 @@ public partial class AppLogic
public readonly record struct EnemyViewerOpened;
public readonly record struct EnemyViewerExited;
public readonly record struct GalleryOpened;
}
}
@@ -4,6 +4,8 @@ public partial class AppLogic
{
public static class Output
{
public readonly record struct Initialize;
public readonly record struct FadeToBlack;
public readonly record struct ShowSplashScreen;
@@ -24,6 +26,8 @@ public partial class AppLogic
public readonly record struct ShowMainMenu;
public readonly record struct CloseGame;
public readonly record struct ExitGame;
public readonly record struct GameOver;
@@ -32,6 +36,8 @@ public partial class AppLogic
public readonly record struct EnemyViewerOpened;
public readonly record struct EnemyViewerExited;
public readonly record struct GalleryOpened;
}
}
@@ -9,5 +9,5 @@ public interface IAppLogic : ILogicBlock<AppLogic.State>;
[LogicBlock(typeof(State), Diagram = true)]
public partial class AppLogic : LogicBlock<AppLogic.State>, IAppLogic
{
public override Transition GetInitialState() => To<State.MainMenu>();
public override Transition GetInitialState() => To<State.Initialize>();
}
@@ -9,7 +9,7 @@ public partial class AppLogic
public partial record State
{
[Meta]
public partial record EnemyViewer : State
public partial record EnemyViewer : State, IGet<Input.EnemyViewerExited>
{
public EnemyViewer()
{
@@ -18,7 +18,13 @@ public partial class AppLogic
{
Output(new Output.EnemyViewerOpened());
});
this.OnExit(() =>
{
Output(new Output.EnemyViewerExited());
});
}
public Transition On(in Input.EnemyViewerExited input) => To<MainMenu>();
}
}
}
@@ -9,7 +9,7 @@ public partial class AppLogic
public partial record State
{
[Meta]
public partial record GameStarted : State
public partial record GameStarted : State, IGet<Input.QuitGame>
{
public GameStarted()
{
@@ -26,6 +26,11 @@ public partial class AppLogic
OnDetach(() => Get<IAppRepo>().GameExited -= OnGameExited);
}
public Transition On(in Input.QuitGame input)
{
Output(new Output.CloseGame());
return To<MainMenu>();
}
public void OnGameExited() => Input(new Input.QuitGame());
}
}
@@ -0,0 +1,21 @@
namespace Zennysoft.Ma.Adapter;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial class AppLogic
{
public partial record State
{
[Meta]
public partial record Initialize : State, IGet<Input.SaveFileLoaded>
{
public Initialize()
{
this.OnEnter(() => Output(new Output.Initialize()));
}
public Transition On(in Input.SaveFileLoaded input) => To<SplashScreen>();
}
}
}
@@ -15,7 +15,7 @@ public partial class AppLogic
this.OnEnter(() => Output(new Output.StartLoadingSaveFile()));
}
public Transition On(in Input.SaveFileLoaded input) => To<GameStarted>();
public Transition On(in Input.SaveFileLoaded input) => To<MainMenu>();
}
}
}
@@ -7,16 +7,21 @@ public partial class AppLogic
public partial record State
{
[Meta]
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.LoadGame>, IGet<Input.EnemyViewerOpened>
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.EnemyViewerOpened>, IGet<Input.QuitGame>
{
public MainMenu()
{
OnAttach(() => Output(new Output.ShowMainMenu()));
}
public Transition On(in Input.NewGame input) => To<GameStarted>();
public Transition On(in Input.EnemyViewerOpened input) => To<EnemyViewer>();
public Transition On(in Input.LoadGame input) => To<LoadingSaveFile>();
public Transition On(in Input.QuitGame input)
{
Output(new Output.ExitGame());
return ToSelf();
}
}
}
}
@@ -16,15 +16,17 @@ public partial class AppLogic
this.OnEnter(() => Output(new Output.ShowSplashScreen()));
OnAttach(
() => Get<IAppRepo>().SplashScreenSkipped += OnSplashScreenSkipped
);
() =>
{
Get<IAppRepo>().SplashScreenSkipped += OnSplashScreenSkipped;
});
OnDetach(
() => Get<IAppRepo>().SplashScreenSkipped -= OnSplashScreenSkipped
);
}
public Transition On(in Input.FadeOutFinished input) => To<GameStarted>();
public Transition On(in Input.FadeOutFinished input) => To<MainMenu>();
public void OnSplashScreenSkipped() =>
Output(new Output.HideSplashScreen());
@@ -1,30 +0,0 @@
using Godot;
namespace Zennysoft.Ma.Adapter;
public partial class AudioManager : Node
{
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private static string _sfxPath = $"res://src/audio/sfx";
private AudioStreamPlayer _audioPlayer;
private IDictionary<SoundEffects, AudioStream> _sfxDictionary;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
#pragma warning restore IDE0044 // Add readonly modifier
public override void _Ready()
{
_audioPlayer = new AudioStreamPlayer();
_sfxDictionary = new Dictionary<SoundEffects, AudioStream>();
var soundEffects = Enum.GetValues(typeof(SoundEffects));
foreach (var effect in soundEffects)
_sfxDictionary.Add((SoundEffects)effect, GD.Load<AudioStream>(_sfxPath + effect + ".ogg"));
}
public void Play(SoundEffects soundEffect)
{
_sfxDictionary.TryGetValue(soundEffect, out var stream);
_audioPlayer.Stream = stream;
_audioPlayer.Play();
}
}
@@ -1,14 +0,0 @@
namespace Zennysoft.Ma.Adapter;
public enum SoundEffects
{
Cancel,
Equip,
Heal,
MenuBack,
MoveThroughOptions,
PlayerAttack,
PlayerHitWall,
Sort,
Unequip
}
@@ -12,7 +12,6 @@ namespace Zennysoft.Ma.Adapter
calculatedDamage = CalculateDefenseResistance(calculatedDamage, defense);
if (!damage.IgnoreElementalResistance)
calculatedDamage = CalculateElementalResistance(calculatedDamage, elementalResistanceSet.ElementalResistance[damage.ElementType]);
return Mathf.Max(1, calculatedDamage);
}
@@ -7,5 +7,8 @@ public enum ElementType
Telluric,
Hydric,
Igneous,
Ferrum
Ferrum,
Holy,
Curse,
All
}
@@ -1,20 +1,27 @@
using Chickensoft.Collections;
using Godot;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Ma.Adapter;
public interface IEquipmentComponent : IEntityComponent
{
public IAutoProp<EquipableItem> EquippedWeapon { get; }
public IAutoProp<IWeapon> EquippedWeapon { get; }
public IAutoProp<EquipableItem> EquippedArmor { get; }
public IAutoProp<IArmor> EquippedArmor { get; }
public IAutoProp<EquipableItem> EquippedAccessory { get; }
public IAutoProp<IAccessory> EquippedAccessory { get; }
public void Equip(EquipableItem equipable);
public IAutoProp<IEquipableItem> EquippedAmmo { get; }
public void Unequip(EquipableItem equipable);
public void Equip(IEquipableItem equipable);
public bool IsItemEquipped(InventoryItem item);
public void Unequip(IEquipableItem equipable);
public bool IsItemEquipped(IEquipableItem item);
public void UpdateEquipment(IEquipableItem equipable);
public bool AugmentableEquipmentExists();
public int BonusAttack { get; }
@@ -28,4 +35,5 @@ public interface IEquipmentComponent : IEntityComponent
public ElementalResistanceSet ElementalResistance { get; }
public event Action<IEquipableItem> EquipmentChanged;
}
@@ -12,7 +12,13 @@ public interface IExperiencePointsComponent : IEntityComponent
public IAutoProp<int> Level { get; }
public void ModifyExpGainRate(double newRate);
public void Gain(int baseExpGain);
public void GainUnmodified(int flateRateExpGain);
public void LevelUp();
public event Action PlayerLevelUp;
}
@@ -21,5 +21,5 @@ public interface IHealthComponent : IEntityComponent
public void SetMaximumHealth(int health);
public void RaiseMaximumHP(int raiseAmount);
public void RaiseMaximumHP(int raiseAmount, bool restoreHP = false);
}
@@ -16,5 +16,7 @@ public interface IVTComponent : IEntityComponent
public void SetVT(int vt);
public void RaiseMaximumVT(int raiseAmount);
public void RaiseMaximumVT(int raiseAmount, bool restoreVT = true);
public void SetMaximumVT(int vt);
}
@@ -7,9 +7,9 @@ namespace Zennysoft.Ma.Adapter.Entity
[Save("elemental_resist_set")]
public Dictionary<ElementType, double> ElementalResistance { get; }
public static ElementalResistanceSet None => new ElementalResistanceSet(0, 0, 0, 0, 0);
public static ElementalResistanceSet None => new ElementalResistanceSet(0, 0, 0, 0, 0, 0, 0);
public ElementalResistanceSet(double aeolicResistance, double hydricResistance, double igneousResistance, double ferrumResistance, double telluricResistance)
public ElementalResistanceSet(double aeolicResistance, double hydricResistance, double igneousResistance, double ferrumResistance, double telluricResistance, double holyResistance, double curseResistance)
{
ElementalResistance = new Dictionary<ElementType, double>
{
@@ -19,6 +19,9 @@ namespace Zennysoft.Ma.Adapter.Entity
{ ElementType.Igneous, igneousResistance },
{ ElementType.Ferrum, ferrumResistance },
{ ElementType.Telluric, telluricResistance },
{ ElementType.Holy, holyResistance },
{ ElementType.Curse, curseResistance },
{ ElementType.All, aeolicResistance + hydricResistance + igneousResistance + ferrumResistance + telluricResistance + holyResistance + curseResistance },
};
}
@@ -29,7 +32,9 @@ namespace Zennysoft.Ma.Adapter.Entity
left.ElementalResistance[ElementType.Hydric] + right.ElementalResistance[ElementType.Hydric],
left.ElementalResistance[ElementType.Igneous] + right.ElementalResistance[ElementType.Igneous],
left.ElementalResistance[ElementType.Ferrum] + right.ElementalResistance[ElementType.Ferrum],
left.ElementalResistance[ElementType.Telluric] + right.ElementalResistance[ElementType.Telluric]);
left.ElementalResistance[ElementType.Telluric] + right.ElementalResistance[ElementType.Telluric],
left.ElementalResistance[ElementType.Holy] + right.ElementalResistance[ElementType.Holy],
left.ElementalResistance[ElementType.Curse] + right.ElementalResistance[ElementType.Curse]);
}
}
}
@@ -1,4 +1,5 @@
using Chickensoft.GodotNodeInterfaces;
using Godot;
using System.Collections.Immutable;
using Zennysoft.Game.Ma;
@@ -16,6 +17,10 @@ namespace Zennysoft.Ma.Adapter.Entity
public void ReturnToDefaultState();
public void OnAbsorb();
public void OnMorph();
public IDungeonRoom GetCurrentRoom(ImmutableList<IDungeonRoom> dungeonRooms);
public void MoveEnemyToNewRoom(IDungeonRoom newRoom);
@@ -26,6 +31,8 @@ namespace Zennysoft.Ma.Adapter.Entity
public IDefenseComponent DefenseComponent { get; }
public ElementalResistanceSet ElementalResistanceSet { get; }
public int InitialHP { get; }
public int InitialAttack { get; }
@@ -0,0 +1,167 @@
using Zennysoft.Ma.Adapter;
public class Augment
{
public JewelTags AugmentTag;
public Augment(JewelTags tag, IAugmentType augment)
{
AugmentTag = tag;
AugmentType = augment;
}
public IAugmentType AugmentType { get; set; }
}
public class HPRecoverySpeedAugment : IAugmentType
{
private readonly IPlayer _player;
public HPRecoverySpeedAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.HealthTimerHPRate += 2;
public void Remove() => _player.HealthTimerHPRate -= 2;
}
public class BasicAugment : IAugmentType
{
public void Apply()
{
// do nothing
}
public void Remove()
{
// do nothing
}
}
public class HastenVTAugment : IAugmentType
{
private readonly IPlayer _player;
public HastenVTAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.ModifyHealthTimerSpeed(_player.HealthTimerSpeedModifier + 0.25f);
public void Remove() => _player.ModifyHealthTimerSpeed(_player.HealthTimerSpeedModifier - 0.25f);
}
public class SlowVTReductionAugment : IAugmentType
{
private readonly IPlayer _player;
public SlowVTReductionAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.ModifyHealthTimerSpeed(_player.HealthTimerSpeedModifier - 0.25f);
public void Remove() => _player.ModifyHealthTimerSpeed(_player.HealthTimerSpeedModifier + 0.25f);
}
public class IncreaseEXPRateAugment : IAugmentType
{
private readonly IPlayer _player;
public IncreaseEXPRateAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value + 0.25f);
public void Remove() => _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value - 0.25f);
}
public class LowerEXPRateAugment : IAugmentType
{
private readonly IPlayer _player;
public LowerEXPRateAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value - 0.25f);
public void Remove() => _player.ExperiencePointsComponent.ModifyExpGainRate(_player.ExperiencePointsComponent.ExpGainRate.Value + 0.25f);
}
public class LowerHPRecoveryAugment : IAugmentType
{
private readonly IPlayer _player;
public LowerHPRecoveryAugment(IPlayer player)
{
_player = player;
}
public void Apply() => _player.HealthTimerHPRate -= 1;
public void Remove() => _player.HealthTimerHPRate += 1;
}
public class IdentifyAllItemsAugment : IAugmentType
{
private readonly IPlayer _player;
public IdentifyAllItemsAugment(IPlayer player)
{
_player = player;
}
public void Apply()
{
_player.AutoIdentifyItems = true;
foreach (var item in _player.Inventory.Items.ToList())
{
if (item.ItemTag == ItemTag.MysteryItem)
_player.IdentifyItem(item);
}
}
public void Remove()
{
var weaponAugment = _player.EquipmentComponent.EquippedWeapon.Value.Augment;
var armorAugment = _player.EquipmentComponent.EquippedArmor.Value.Augment;
var accessoryAugment = _player.EquipmentComponent.EquippedAccessory.Value.Augment;
var augments = new List<Augment?>() { weaponAugment, armorAugment, accessoryAugment };
if (augments.Count(x => x != null && x.AugmentTag == JewelTags.AutoIdentifyAllItems) > 1)
return;
else
_player.AutoIdentifyItems = false;
}
}
public class RevivePlayerAugment : IAugmentType
{
private readonly IPlayer _player;
public RevivePlayerAugment(IPlayer player)
{
_player = player;
}
public void Apply()
{
_player.AutoRevive = true;
}
public void Remove()
{
var weaponAugment = _player.EquipmentComponent.EquippedWeapon.Value.Augment;
var armorAugment = _player.EquipmentComponent.EquippedArmor.Value.Augment;
var accessoryAugment = _player.EquipmentComponent.EquippedAccessory.Value.Augment;
var augments = new List<Augment?>() { weaponAugment, armorAugment, accessoryAugment };
if (augments.Count(x => x != null && x.AugmentTag == JewelTags.ReviveUserOnce) > 1)
return;
else
_player.AutoRevive = false;
}
}
@@ -1,22 +0,0 @@
using Chickensoft.Introspection;
using Chickensoft.Serialization;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Ma.Adapter;
[Meta, Id("equipable_item")]
public abstract partial class EquipableItem : InventoryItem
{
[Save("bonus_attack_stats")]
public virtual int BonusAttack { get; }
[Save("bonus_defense_stats")]
public virtual int BonusDefense { get; }
[Save("bonus_hp_stats")]
public virtual int BonusHP { get; }
[Save("bonus_vt_stats")]
public virtual int BonusVT { get; }
[Save("bonus_luck_stats")]
public virtual int BonusLuck { get; }
[Save("bonus_elemental_resist_stats")]
public virtual ElementalResistanceSet ElementalResistance { get; } = new ElementalResistanceSet(0, 0, 0, 0, 0);
}
@@ -0,0 +1,6 @@
public interface IAugmentType
{
void Apply();
void Remove();
}
@@ -1,26 +0,0 @@
using Chickensoft.Introspection;
using Chickensoft.Serialization;
using Godot;
namespace Zennysoft.Ma.Adapter;
[Meta, Id("inventory_item")]
public abstract partial class InventoryItem : Node3D
{
[Save("inventory_item_id")]
public Guid ID => Guid.NewGuid();
[Save("inventory_item_name")]
public abstract string ItemName { get; }
[Save("inventory_item_description")]
public abstract string Description { get; }
[Save("inventory_item_spawn_rate")]
public abstract float SpawnRate { get; }
[Save("inventory_item_throw_damage")]
public abstract int ThrowDamage { get; }
[Save("inventory_item_throw_speed")]
public abstract float ThrowSpeed { get; }
[Save("inventory_item_tag")]
public abstract ItemTag ItemTag { get; }
public abstract Texture2D GetTexture();
}
@@ -4,5 +4,21 @@ public enum ItemTag
{
None,
BreaksOnChange,
MysteryItem
}
MysteryItem,
DamagesPlayer,
ContainsRestorative,
ContainsWeapon,
ContainsArmor,
ContainsBox,
RandomSpell,
ContainsAccessory,
DropTo1HPAndGainRareItem,
TradeOneRandomItem,
TradeAllRandomItems,
ContainsUnobtainedItem,
ContainsBasicItem,
RestrictUnequip,
UnequipAllItems,
EjectAllItems,
UseAllItems
}
@@ -0,0 +1,21 @@
public enum JewelTags
{
None,
AeolicElement,
IncreaseHPRecovery,
HastenVT,
LowerEXPGain,
Glue,
ItemRescue,
HydricElement,
IgneousElement,
IncreaseEXPGain,
LowerHPRecovery,
SlowVTReduction,
AutoIdentifyAllItems,
ReviveUserOnce,
TelluricElement,
IncreaseAtkDefLuck,
IncreaseLuck
}
@@ -1,11 +0,0 @@
namespace Zennysoft.Ma.Adapter;
public enum ThrowableItemTag
{
None,
DoubleExp,
LowerTargetTo1HP,
CanChangeAffinity,
TeleportToRandomLocation,
WarpToExitIfFound
}
@@ -16,4 +16,9 @@ public enum UsableItemTag
RaiseCurrentDefenseArmor,
RaiseLevel,
RandomEffect,
DoubleExp,
LowerTargetTo1HP,
CanChangeAffinity,
TeleportToRandomLocation,
WarpToExitIfFound
}
@@ -7,4 +7,12 @@ public enum WeaponTag
IgnoreAffinity,
IgnoreDefense,
Knockback,
InverseHPAttackPower,
RustChanceSelfAndEnemy,
Instakill,
DegradeOnSwing,
DoubleAttack,
TripleAttack,
ElementalProjectile,
KineticProjectile
}
@@ -2,6 +2,7 @@
using Godot;
using Zennysoft.Game.Abstractions;
using Zennysoft.Game.Implementation;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Ma.Adapter;
@@ -19,7 +20,7 @@ public interface IGameRepo : IDisposable
event Action? DoubleExpTimeEnd;
event Action<InventoryItem>? RemoveItemFromInventoryEvent;
event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent;
event Action? PlayerAttack;
@@ -27,11 +28,11 @@ public interface IGameRepo : IDisposable
event Action? PlayerAttackedEnemy;
event Action<EquipableItem>? EquippedItem;
event Action<IEquipableItem>? EquippedItem;
event Action<EquipableItem>? UnequippedItem;
event Action<IEquipableItem>? UnequippedItem;
event Action<IHealthPack>? RestorativePickedUp;
event Action<IEnemy>? EnemyDied;
void Pause();
@@ -47,21 +48,21 @@ public interface IGameRepo : IDisposable
public void AnnounceMessageInInventory(string message);
public void RemoveItemFromInventory(InventoryItem item);
public void RemoveItemFromInventory(IBaseInventoryItem item);
public void OnPlayerAttack();
public void OnPlayerAttackedWall();
public void OnRestorativePickedUp(IHealthPack restorative);
public void CloseInventory();
public void GameEnded();
public void OnEquippedItem(EquipableItem item);
public void OnEquippedItem(IEquipableItem item);
public void OnUnequippedItem(EquipableItem item);
public void OnUnequippedItem(IEquipableItem item);
public void OnEnemyDied(IEnemy enemy);
public double ExpRate { get; }
}
@@ -74,13 +75,13 @@ public class GameRepo : IGameRepo
public event Action<string>? AnnounceMessageInInventoryEvent;
public event Action<int>? DoubleExpTimeStart;
public event Action? DoubleExpTimeEnd;
public event Action<InventoryItem>? RemoveItemFromInventoryEvent;
public event Action<IBaseInventoryItem>? RemoveItemFromInventoryEvent;
public event Action? PlayerAttack;
public event Action? PlayerAttackedWall;
public event Action? PlayerAttackedEnemy;
public event Action<EquipableItem>? EquippedItem;
public event Action<EquipableItem>? UnequippedItem;
public event Action<IHealthPack>? RestorativePickedUp;
public event Action<IEquipableItem>? EquippedItem;
public event Action<IEquipableItem>? UnequippedItem;
public event Action<IEnemy>? EnemyDied;
public IAutoProp<bool> IsPaused => _isPaused;
private readonly AutoProp<bool> _isPaused;
@@ -110,14 +111,14 @@ public class GameRepo : IGameRepo
{
AnnounceMessageInInventory("Experience points temporarily doubled.");
DoubleExpTimeStart?.Invoke(lengthOfEffect.Seconds);
ExpRate = 2;
ExpRate *= 2;
}
public void EndDoubleExp()
{
AnnounceMessageOnMainScreen("Experience points effect wore off.");
DoubleExpTimeEnd?.Invoke();
ExpRate = 1;
ExpRate /= 2;
}
public void AnnounceMessageOnMainScreen(string message)
@@ -130,7 +131,7 @@ public class GameRepo : IGameRepo
AnnounceMessageInInventoryEvent?.Invoke(message);
}
public void RemoveItemFromInventory(InventoryItem item)
public void RemoveItemFromInventory(IBaseInventoryItem item)
{
RemoveItemFromInventoryEvent?.Invoke(item);
}
@@ -145,19 +146,16 @@ public class GameRepo : IGameRepo
PlayerAttackedWall?.Invoke();
}
public void OnRestorativePickedUp(IHealthPack restorative)
{
RestorativePickedUp?.Invoke(restorative);
}
public void CloseInventory()
{
CloseInventoryEvent?.Invoke();
}
public void OnEquippedItem(EquipableItem item) => EquippedItem?.Invoke(item);
public void OnEquippedItem(IEquipableItem item) => EquippedItem?.Invoke(item);
public void OnUnequippedItem(EquipableItem item) => UnequippedItem?.Invoke(item);
public void OnUnequippedItem(IEquipableItem item) => UnequippedItem?.Invoke(item);
public void OnEnemyDied(IEnemy enemy) => EnemyDied?.Invoke(enemy);
public void GameEnded()
{
@@ -8,10 +8,14 @@ public partial class GameState
public readonly record struct LoadGame;
public readonly record struct ExitGame;
public readonly record struct LoadNextFloor;
public readonly record struct InventoryButtonPressed;
public readonly record struct InteractButtonPressed;
public readonly record struct PauseButtonPressed;
public readonly record struct DebugButtonPressed;
@@ -24,6 +28,8 @@ public partial class GameState
public readonly record struct CloseTeleport;
public readonly record struct CloseInventory;
public readonly record struct GameOver;
}
}
@@ -6,6 +6,8 @@ public partial class GameState
{
public readonly record struct InitializeGame;
public readonly record struct ExitGame;
public readonly record struct LoadGameFromFile;
public readonly record struct OpenInventoryMenu;
@@ -8,7 +8,7 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record DebugMenu : State, IGet<Input.DebugButtonPressed>
public partial record DebugMenu : InGame, IGet<Input.DebugButtonPressed>
{
public Transition On(in Input.DebugButtonPressed input)
{
@@ -8,13 +8,20 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record GameOver : State, IGet<Input.NewGame>
public partial record GameOver : InGame, IGet<Input.NewGame>, IGet<Input.ExitGame>
{
public Transition On(in Input.NewGame input)
{
Output(new Output.InitializeGame());
return To<InGame>();
}
public Transition On(in Input.ExitGame input)
{
Output(new Output.ClosePauseScreen());
Output(new Output.ExitGame());
return To<State>();
}
}
}
}
@@ -8,9 +8,15 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record InventoryScreen : State, IGet<Input.InventoryButtonPressed>
public partial record InventoryScreen : State, IGet<Input.InteractButtonPressed>, IGet<Input.CloseInventory>
{
public Transition On(in Input.InventoryButtonPressed input)
public Transition On(in Input.InteractButtonPressed input)
{
Output(new Output.CloseInventoryMenu());
return To<InGame>();
}
public Transition On(in Input.CloseInventory input)
{
Output(new Output.CloseInventoryMenu());
return To<InGame>();
@@ -1,5 +1,6 @@
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
using static Zennysoft.Ma.Adapter.GameState.Output;
namespace Zennysoft.Ma.Adapter;
@@ -8,13 +9,20 @@ public partial class GameState
public partial record State
{
[Meta, LogicBlock(typeof(State), Diagram = true)]
public partial record PauseScreen : State, IGet<Input.PauseButtonPressed>
public partial record PauseScreen : State, IGet<Input.PauseButtonPressed>, IGet<Input.ExitGame>
{
public Transition On(in Input.PauseButtonPressed input)
{
Output(new Output.ClosePauseScreen());
return To<InGame>();
}
public Transition On(in Input.ExitGame input)
{
Output(new Output.ClosePauseScreen());
Output(new Output.ExitGame());
return To<State>();
}
}
}
}
@@ -0,0 +1,5 @@
using Zennysoft.Ma.Adapter;
public interface IAccessory : IEquipableItem, IAugmentableItem
{
}
@@ -0,0 +1,5 @@
using Zennysoft.Ma.Adapter;
public interface IArmor : IEquipableItem, IAugmentableItem
{
}
@@ -0,0 +1,5 @@
public interface IAugmentItem : IBaseInventoryItem
{
public IAugmentType Augment { get; }
}
@@ -0,0 +1,7 @@
namespace Zennysoft.Ma.Adapter
{
public interface IAugmentableItem
{
public Augment? Augment { get; }
}
}
@@ -0,0 +1,15 @@
using Godot;
using Zennysoft.Ma.Adapter;
public interface IBaseInventoryItem
{
public string ItemName { get; }
public string Description { get; }
public float SpawnRate { get; }
public int ThrowDamage { get; }
public float ThrowSpeed { get; }
public ItemTag ItemTag { get; }
public abstract Texture2D GetTexture();
}
@@ -4,6 +4,6 @@
{
void RescueItem();
public InventoryItem Item { get; }
public IBaseInventoryItem Item { get; }
}
}
@@ -0,0 +1,14 @@
using Zennysoft.Ma.Adapter.Entity;
public interface IEquipableItem : IBaseInventoryItem
{
public int BonusAttack { get; }
public int BonusDefense { get; }
public int BonusHP { get; }
public int BonusVT { get; }
public int BonusLuck { get; }
public bool Glued { get; set; }
public ElementalResistanceSet ElementalResistance { get; }
}
@@ -2,17 +2,20 @@
public interface IInventory
{
public bool PickUpItem(InventoryItem item);
public bool PickUpItem(IBaseInventoryItem item);
public List<InventoryItem> Items { get; }
public List<IBaseInventoryItem> Items { get; }
public bool TryAdd(InventoryItem inventoryItem);
public bool TryAdd(IBaseInventoryItem inventoryItem);
public bool TryInsert(InventoryItem inventoryItem, int index);
public bool TryInsert(IBaseInventoryItem inventoryItem, int index);
public void Remove(InventoryItem inventoryItem);
public void Remove(IBaseInventoryItem inventoryItem);
public void Sort(EquipableItem currentWeapon, EquipableItem currentArmor, EquipableItem currentAccessory);
public bool Sort(IWeapon currentWeapon, IArmor currentArmor, IAccessory currentAccessory, IEquipableItem ammo);
public bool AtCapacity();
public event Action<string> BroadcastMessage;
public event Action InventoryChanged;
}
@@ -2,5 +2,5 @@
public interface IThrownItem
{
public InventoryItem ItemThatIsThrown { get; set; }
public IBaseInventoryItem ItemThatIsThrown { get; set; }
}
@@ -0,0 +1,5 @@
using Zennysoft.Ma.Adapter;
public interface IWeapon : IEquipableItem, IAugmentableItem
{
}
@@ -7,10 +7,10 @@ namespace Zennysoft.Ma.Adapter;
public partial class RescuedItemDatabase
{
[Save("rescued_item_list")]
public List<InventoryItem> Items { get; init; }
public List<IBaseInventoryItem> Items { get; init; }
public RescuedItemDatabase()
{
Items = new List<InventoryItem>();
Items = new List<IBaseInventoryItem>();
}
}
@@ -11,9 +11,6 @@ public partial class ItemTagEnumContext : JsonSerializerContext;
[JsonSerializable(typeof(AccessoryTag))]
public partial class AccessoryTagEnumContext : JsonSerializerContext;
[JsonSerializable(typeof(ThrowableItemTag))]
public partial class ThrowableItemTagEnumContext : JsonSerializerContext;
[JsonSerializable(typeof(UsableItemTag))]
public partial class UsableItemTagEnumContext : JsonSerializerContext;
@@ -7,9 +7,11 @@ public interface IDungeonFloor : INode3D
{
void InitializeDungeon();
public Transform3D GetPlayerSpawnPoint();
public abstract (Vector3 Rotation, Vector3 Position) GetPlayerSpawnPoint();
public ImmutableList<IDungeonRoom> Rooms { get; }
public void FadeOutAudio();
public bool FloorIsLoaded { get; set; }
}
@@ -18,11 +18,17 @@ public interface IPlayer : IKillable, ICharacterBody3D
public void LevelUp();
public void TeleportPlayer(Transform3D newTransform);
public void TeleportPlayer((Vector3 Rotation, Vector3 Position) newTransform);
public void Equip(EquipableItem equipable);
public void Equip(IEquipableItem equipable);
public void Unequip(EquipableItem equipable);
public void Unequip(IEquipableItem equipable);
public void PlayJumpScareAnimation();
public void ApplyNewAugment(IAugmentItem jewel, IAugmentableItem equipableItem);
public void IdentifyItem(IBaseInventoryItem unidentifiedItem);
public IInventory Inventory { get; }
@@ -40,6 +46,22 @@ public interface IPlayer : IKillable, ICharacterBody3D
public IEquipmentComponent EquipmentComponent { get; }
public void SetHealthTimerStatus(bool isActive);
public void ModifyHealthTimerSpeed(float newModifier);
public bool AutoRevive { get; set; }
public int TotalAttack { get; }
public int TotalDefense { get; }
public int TotalLuck { get; }
public int HealthTimerHPRate { get; set; }
public float HealthTimerSpeedModifier { get; }
public bool AutoIdentifyItems { get; set; }
public event Action PlayerDied;
public delegate InventoryItem RerollItem(InventoryItem item);
public delegate IBaseInventoryItem RerollItem(IBaseInventoryItem item);
}
@@ -8,8 +8,6 @@ public partial class PlayerLogic
{
public readonly record struct PhysicsTick(double Delta);
public readonly record struct Moved(Vector3 GlobalPosition, Transform3D GlobalTransform);
public readonly record struct Enable;
public readonly record struct Attack;
@@ -6,6 +6,9 @@ namespace Zennysoft.Ma.Adapter;
[Meta, Id("quest_data")]
public partial record QuestData
{
[Save("death_count")]
public int DeathCount { get; set; } = 0;
[Save("quest_data_1")]
public bool QuestMarker1 { get; set; } = false;
}
@@ -19,7 +19,7 @@ public sealed class MaSaveFileManager : IMaSaveFileManager
public MaSaveFileManager(ISaveFileManager saveFileManager)
{
_saveFileManager = saveFileManager;
_converters = [WeaponTagEnumContext.Default, ItemTagEnumContext.Default, ElementTypeEnumContext.Default, AccessoryTagEnumContext.Default, ThrowableItemTagEnumContext.Default, UsableItemTagEnumContext.Default, BoxItemTagEnumContext.Default];
_converters = [WeaponTagEnumContext.Default, ItemTagEnumContext.Default, ElementTypeEnumContext.Default, AccessoryTagEnumContext.Default, UsableItemTagEnumContext.Default, BoxItemTagEnumContext.Default];
}
public async Task Save<T>(T gameData)
@@ -38,7 +38,7 @@ public partial class InGameUILogic
Output(new Output.AnnounceMessageInInventory(message));
}
private void OnRemoveItemFromInventory(InventoryItem item) => Output(new Output.RemoveItemFromInventory(item));
private void OnRemoveItemFromInventory(IBaseInventoryItem item) => Output(new Output.RemoveItemFromInventory(item));
}
}
@@ -8,7 +8,7 @@ public partial class InGameUILogic
{
public readonly record struct AnnounceMessageOnMainScreen(string Message);
public readonly record struct AnnounceMessageInInventory(string Message);
public readonly record struct RemoveItemFromInventory(InventoryItem Item);
public readonly record struct RemoveItemFromInventory(IBaseInventoryItem Item);
public readonly record struct ShowInventory;
public readonly record struct HideInventory;
}
@@ -7,8 +7,11 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="Actions\**" />
<Compile Remove="Game\state\states\**" />
<EmbeddedResource Remove="Actions\**" />
<EmbeddedResource Remove="Game\state\states\**" />
<None Remove="Actions\**" />
<None Remove="Game\state\states\**" />
</ItemGroup>
@@ -28,8 +31,4 @@
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Actions\" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Godot.NET.Sdk/4.4.0">
<Project Sdk="Godot.NET.Sdk/4.4.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
-51
View File
@@ -1,51 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.4.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<!-- Use NativeAOT. -->
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<Compile Remove="src\items\weapons\models\**" />
<EmbeddedResource Remove="src\items\weapons\models\**" />
</ItemGroup>
<ItemGroup>
<!-- Root the assemblies to avoid trimming. -->
<TrimmerRootAssembly Include="GodotSharp" />
<TrimmerRootAssembly Include="$(TargetName)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Chickensoft.AutoInject" Version="2.5.0" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.0" />
<PackageReference Include="Chickensoft.Introspection" Version="2.2.0" />
<PackageReference Include="Chickensoft.Introspection.Generator" Version="2.2.0" />
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.16.0" />
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.16.0" />
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.1.0" />
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.7.6" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0-250131-2115.Release" />
<PackageReference Include="SimpleInjector" Version="5.5.0" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="Zeroconf" Version="3.7.16" />
</ItemGroup>
<ItemGroup>
<Folder Include="src\ui\dialogue\" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
<ProjectReference Include="..\Zennysoft.Game.Ma.Implementation\Zennysoft.Ma.Adapter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Godot.SourceGenerators" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharp" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharpEditor" Version="4.4.0-dev.2" />
</ItemGroup>
</Project>
-38
View File
@@ -1,38 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.4.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<WarningsAsErrors>CS9057</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Chickensoft.AutoInject" Version="2.5.0" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.0" />
<PackageReference Include="Chickensoft.Introspection" Version="2.2.0" />
<PackageReference Include="Chickensoft.Introspection.Generator" Version="2.2.0" />
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.16.0" />
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.16.0" />
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.1.0" />
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.7.6" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0-250131-2115.Release" />
<PackageReference Include="SimpleInjector" Version="5.5.0" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="Zeroconf" Version="3.7.16" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
<ProjectReference Include="..\Zennysoft.Game.Ma.Implementation\Zennysoft.Ma.Adapter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Godot.SourceGenerators" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharp" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharpEditor" Version="4.4.0-dev.2" />
</ItemGroup>
</Project>
-38
View File
@@ -1,38 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.4.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<WarningsAsErrors>CS9057</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Chickensoft.AutoInject" Version="2.5.0" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.0" />
<PackageReference Include="Chickensoft.Introspection" Version="2.2.0" />
<PackageReference Include="Chickensoft.Introspection.Generator" Version="2.2.0" />
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.16.0" />
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.16.0" />
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.1.0" />
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.7.6" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0-250131-2115.Release" />
<PackageReference Include="SimpleInjector" Version="5.5.0" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="Zeroconf" Version="3.7.16" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
<ProjectReference Include="..\Zennysoft.Game.Ma.Implementation\Zennysoft.Ma.Adapter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Godot.SourceGenerators" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharp" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharpEditor" Version="4.4.0-dev.2" />
</ItemGroup>
</Project>
-38
View File
@@ -1,38 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.4.1">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<WarningsAsErrors>CS9057</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Chickensoft.AutoInject" Version="2.5.0" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.0" />
<PackageReference Include="Chickensoft.Introspection" Version="2.2.0" />
<PackageReference Include="Chickensoft.Introspection.Generator" Version="2.2.0" />
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.16.0" />
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.16.0" />
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.1.0" />
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.7.6" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0-250131-2115.Release" />
<PackageReference Include="SimpleInjector" Version="5.5.0" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.IO.Abstractions" Version="22.0.11" />
<PackageReference Include="Zeroconf" Version="3.7.16" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
<ProjectReference Include="..\Zennysoft.Game.Ma.Implementation\Zennysoft.Ma.Adapter.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Godot.SourceGenerators" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharp" Version="4.4.0-dev.2" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="GodotSharpEditor" Version="4.4.0-dev.2" />
</ItemGroup>
</Project>
@@ -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
}
}
@@ -0,0 +1 @@
uid://cfs4dgwrfvb11
@@ -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.
@@ -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

@@ -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
@@ -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)
@@ -0,0 +1 @@
uid://dcff0mowkn6km
@@ -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"]
@@ -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
@@ -0,0 +1 @@
uid://cholww48njaeh
@@ -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"
@@ -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()
@@ -0,0 +1 @@
uid://cul6evy00vr55
@@ -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
@@ -0,0 +1 @@
uid://yqplm6a6focp
@@ -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()
@@ -0,0 +1 @@
uid://1t3qhgrro2es
@@ -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"]
+15 -1
View File
@@ -1,4 +1,8 @@
[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://c2mk6c27y0mdf"]
[gd_resource type="AudioBusLayout" load_steps=4 format=3 uid="uid://c2mk6c27y0mdf"]
[sub_resource type="AudioEffectLimiter" id="AudioEffectLimiter_j3pel"]
resource_name = "Limiter"
soft_clip_db = 1.0
[sub_resource type="AudioEffectReverb" id="AudioEffectReverb_j3pel"]
resource_name = "Reverb"
@@ -7,8 +11,16 @@ damping = 0.9
dry = 0.99
wet = 0.05
[sub_resource type="AudioEffectLimiter" id="AudioEffectLimiter_g28q7"]
resource_name = "Limiter"
ceiling_db = -0.5
threshold_db = -0.6
soft_clip_db = 1.5
[resource]
bus/0/volume_db = -0.130497
bus/0/effect/0/effect = SubResource("AudioEffectLimiter_j3pel")
bus/0/effect/0/enabled = true
bus/1/name = &"AMBIENT"
bus/1/solo = false
bus/1/mute = false
@@ -23,6 +35,8 @@ bus/2/volume_db = 0.0
bus/2/send = &"Master"
bus/2/effect/0/effect = SubResource("AudioEffectReverb_j3pel")
bus/2/effect/0/enabled = true
bus/2/effect/1/effect = SubResource("AudioEffectLimiter_g28q7")
bus/2/effect/1/enabled = true
bus/3/name = &"MUSIC"
bus/3/solo = false
bus/3/mute = false
+7 -53
View File
@@ -1,23 +1,22 @@
[preset.0]
name="Steamdeck"
platform="Linux"
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
advanced_options=false
advanced_options=true
dedicated_server=false
custom_features=""
export_filter="exclude"
export_files=PackedStringArray()
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path=""
export_path="Export/Ma.exe"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
script_export_mode=1
[preset.0.options]
@@ -28,51 +27,6 @@ binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
binary_format/architecture="x86_64"
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash
export DISPLAY=:0
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
\"{temp_dir}/{exe_name}\" {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")
rm -rf \"{temp_dir}\""
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
[preset.1]
name="Windows Desktop"
platform="Windows Desktop"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="Output/Ma.zip"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.1.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=0
binary_format/embed_pck=true
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
binary_format/architecture="x86_64"
codesign/enable=false
codesign/timestamp=true
codesign/timestamp_server_url=""
@@ -111,5 +65,5 @@ ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debu
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
Remove-Item -Recurse -Force '{temp_dir}'"
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false
+77 -21
View File
@@ -12,9 +12,8 @@ config_version=5
config/name="Ma"
run/main_scene="uid://d1gjaijijd5ot"
run/print_header=false
config/features=PackedStringArray("4.4", "C#", "GL Compatibility")
run/delta_smoothing=false
run/max_fps=60
boot_splash/show_image=false
[autoload]
@@ -23,6 +22,8 @@ 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"
SfxDatabase="*res://src/audio/SFXDatabase.tscn"
[dialogue_manager]
@@ -34,16 +35,19 @@ runtime/advanced/uses_dotnet=true
window/size/viewport_width=1920
window/size/viewport_height=1080
window/stretch/mode="viewport"
window/stretch/aspect="expand"
window/stretch/mode="canvas_items"
[dotnet]
project/assembly_name="Ma"
[editor]
export/convert_text_resources_to_binary=false
[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]
@@ -53,6 +57,20 @@ folder_colors={
"res://src/game/": "orange",
"res://src/items/": "teal",
"res://src/map/": "gray",
"res://src/map/dungeon/floors/Floor01/": "red",
"res://src/map/dungeon/floors/Floor02/": "red",
"res://src/map/dungeon/floors/Floor03/": "red",
"res://src/map/dungeon/floors/Floor04/": "red",
"res://src/map/dungeon/floors/Floor05/": "red",
"res://src/map/dungeon/floors/Floor06/": "red",
"res://src/map/dungeon/floors/Floor07/": "red",
"res://src/map/dungeon/floors/Floor09/": "teal",
"res://src/map/dungeon/floors/Floor10/": "teal",
"res://src/map/dungeon/floors/Floor11/": "teal",
"res://src/map/dungeon/floors/Floor12/": "teal",
"res://src/map/dungeon/floors/Floor13/": "teal",
"res://src/map/dungeon/floors/Floor14/": "teal",
"res://src/map/dungeon/floors/Floor15/": "teal",
"res://src/player/": "blue",
"res://src/ui/inventory_menu/": "green"
}
@@ -61,10 +79,16 @@ folder_colors={
import/blender/enabled=false
[global_group]
DimmableAudio=""
[importer_defaults]
texture={
"detect_3d/compress_to": 0
&"compress/high_quality": true,
&"compress/mode": 2,
&"detect_3d/compress_to": 0
}
[input]
@@ -72,9 +96,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,58 +105,69 @@ 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)
, 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":4194305,"key_label":0,"unicode":0,"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={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
MoveLeft={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
MoveRight={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
MoveDown={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
]
}
Attack={
@@ -218,10 +251,30 @@ AltAttack={
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":2,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
CameraForward={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
]
}
CameraBack={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
]
}
EnemyViewerIdle={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null)
]
}
EnemyViewerWalk={
"deadzone": 0.2,
"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
]
}
[internationalization]
locale/translations_pot_files=PackedStringArray("res://src/dialog/Dialogue.dialogue", "res://src/npc/Ran/ran.dialogue", "res://src/npc/Rat/ratdialogue.dialogue")
locale/translations_pot_files=PackedStringArray("res://src/dialog/Dialogue.dialogue", "res://src/npc/Ran/ran.dialogue", "res://src/npc/Rat/ratdialogue.dialogue", "res://src/dialog/Altar.dialogue", "res://stone.dialogue", "res://src/npc/Proscenium/JumpScare.dialogue", "res://tutorialstone.dialogue")
[layer_names]
@@ -237,6 +290,8 @@ locale/translations_pot_files=PackedStringArray("res://src/dialog/Dialogue.dialo
3d_physics/layer_10="Minimap"
3d_physics/layer_11="ItemRescue"
3d_physics/layer_12="EnemyHitbox"
3d_physics/layer_13="UnlockableDoor"
3d_physics/layer_14="ExplodableWall"
3d_physics/layer_32="Navigation"
[navigation]
@@ -247,6 +302,7 @@ locale/translations_pot_files=PackedStringArray("res://src/dialog/Dialogue.dialo
3d/run_on_separate_thread=true
common/physics_ticks_per_second=144
jolt_physics_3d/simulation/areas_detect_static_bodies=true
[rendering]
@@ -0,0 +1 @@
uid://bevfcpew3kket
@@ -1,21 +1,28 @@
using Chickensoft.Collections;
using System;
using Zennysoft.Ma.Adapter;
using Zennysoft.Ma.Adapter.Entity;
namespace Zennysoft.Game.Ma;
public class EquipmentComponent : IEquipmentComponent
{
public IAutoProp<EquipableItem> EquippedWeapon => _equippedWeapon;
public IAutoProp<IWeapon> EquippedWeapon => _equippedWeapon;
public IAutoProp<EquipableItem> EquippedArmor => _equippedArmor;
public IAutoProp<IArmor> EquippedArmor => _equippedArmor;
public IAutoProp<EquipableItem> EquippedAccessory => _equippedAccessory;
public IAutoProp<IAccessory> EquippedAccessory => _equippedAccessory;
public AutoProp<EquipableItem> _equippedWeapon;
public IAutoProp<IEquipableItem> EquippedAmmo => _equippedAmmo;
public AutoProp<EquipableItem> _equippedArmor;
public AutoProp<IWeapon> _equippedWeapon;
public AutoProp<EquipableItem> _equippedAccessory;
public AutoProp<IArmor> _equippedArmor;
public AutoProp<IAccessory> _equippedAccessory;
public AutoProp<IEquipableItem> _equippedAmmo;
public event Action<IEquipableItem> EquipmentChanged;
public int BonusAttack => _equippedWeapon.Value.BonusAttack + _equippedArmor.Value.BonusAttack + _equippedAccessory.Value.BonusAttack;
@@ -31,9 +38,10 @@ public class EquipmentComponent : IEquipmentComponent
public EquipmentComponent()
{
_equippedWeapon = new AutoProp<EquipableItem>(new Weapon());
_equippedArmor = new AutoProp<EquipableItem>(new Armor());
_equippedAccessory = new AutoProp<EquipableItem>(new Accessory());
_equippedWeapon = new AutoProp<IWeapon>(new Weapon());
_equippedArmor = new AutoProp<IArmor>(new Armor());
_equippedAccessory = new AutoProp<IAccessory>(new Accessory());
_equippedAmmo = new AutoProp<IEquipableItem>(new Ammo());
}
public void Reset()
@@ -41,9 +49,10 @@ public class EquipmentComponent : IEquipmentComponent
_equippedWeapon.OnNext(new Weapon());
_equippedArmor.OnNext(new Armor());
_equippedAccessory.OnNext(new Accessory());
_equippedAmmo.OnNext(new Ammo());
}
public void Equip(EquipableItem equipable)
public void Equip(IEquipableItem equipable)
{
if (equipable is Weapon weapon)
_equippedWeapon.OnNext(weapon);
@@ -51,9 +60,12 @@ public class EquipmentComponent : IEquipmentComponent
_equippedArmor.OnNext(armor);
if (equipable is Accessory accessory)
_equippedAccessory.OnNext(accessory);
if (equipable is Ammo ammo)
_equippedAmmo.OnNext(ammo);
EquipmentChanged?.Invoke(equipable);
}
public void Unequip(EquipableItem equipable)
public void Unequip(IEquipableItem equipable)
{
if (equipable is Weapon weapon)
_equippedWeapon.OnNext(new Weapon());
@@ -61,13 +73,20 @@ public class EquipmentComponent : IEquipmentComponent
_equippedArmor.OnNext(new Armor());
if (equipable is Accessory accessory)
_equippedAccessory.OnNext(new Accessory());
if (equipable is Ammo ammo)
_equippedAmmo.OnNext(new Ammo());
EquipmentChanged?.Invoke(equipable);
}
public bool IsItemEquipped(InventoryItem item)
public bool IsItemEquipped(IEquipableItem item)
{
if (item is not EquipableItem)
return false;
return item == _equippedWeapon.Value || item == _equippedArmor.Value || item == _equippedAccessory.Value || item == _equippedAmmo.Value;
}
return item == _equippedWeapon.Value || item == _equippedArmor.Value || item == _equippedAccessory.Value;
public void UpdateEquipment(IEquipableItem equipable) => EquipmentChanged?.Invoke(equipable);
public bool AugmentableEquipmentExists()
{
return (_equippedWeapon.Value.ItemName != null && _equippedWeapon.Value.Augment == null) || (_equippedArmor.Value.ItemName != null && _equippedArmor.Value.Augment == null) || (_equippedAccessory.Value.ItemName != null && _equippedAccessory.Value.Augment == null);
}
}
@@ -22,6 +22,8 @@ public class ExperiencePointsComponent : IExperiencePointsComponent
private readonly AutoProp<int> _level;
public event Action PlayerLevelUp;
public ExperiencePointsComponent()
{
var firstLevelExpRequirement = ExpToNextLevelCalculation(1);
@@ -50,12 +52,25 @@ public class ExperiencePointsComponent : IExperiencePointsComponent
_currentExp.OnNext(cappedAmount);
}
public void GainUnmodified(int flatRateExp)
{
var newCurrentExpTotal = flatRateExp + _currentExp.Value;
while (flatRateExp + _currentExp.Value >= _expToNextLevel.Value)
LevelUp();
var cappedAmount = Math.Min(flatRateExp + _currentExp.Value, _expToNextLevel.Value);
_currentExp.OnNext(cappedAmount);
}
public void ModifyExpGainRate(double newRate) => _expGainRate.OnNext(newRate);
public void LevelUp()
{
SfxDatabase.Instance.Play(SoundEffect.LevelUp);
_level.OnNext(_level.Value + 1);
var expToNextLevel = ExpToNextLevelCalculation(_level.Value);
_currentExp.OnNext(_currentExp.Value - _expToNextLevel.Value);
_expToNextLevel.OnNext(expToNextLevel);
PlayerLevelUp?.Invoke();
}
private int ExpToNextLevelCalculation(int nextLevel)
@@ -36,12 +36,18 @@ public class HealthComponent : IHealthComponent
public void Heal(int healAmount)
{
if (CurrentHP.Value <= 0)
return;
var cappedAmount = Math.Min(healAmount + _currentHP.Value, _maximumHP.Value);
_currentHP.OnNext(cappedAmount);
}
public void Damage(int damageAmount)
{
if (CurrentHP.Value <= 0)
return;
var cappedAmount = Math.Max(_currentHP.Value - damageAmount, 0);
_currentHP.OnNext(cappedAmount);
@@ -60,11 +66,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);
}
}
@@ -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);
}
}
-5
View File
@@ -2,15 +2,10 @@ namespace Zennysoft.Game.Ma;
using Godot;
#if DEBUG
using System.Reflection;
#endif
public partial class Main : Node
{
public override void _Ready()
{
// If we don't need to run tests, we can just switch to the game scene.
CallDeferred("RunScene");
}
+155 -42
View File
@@ -1,12 +1,13 @@
using Chickensoft.AutoInject;
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using Godot.Collections;
using NathanHoad;
using SimpleInjector.Lifestyles;
using System.Linq;
using System.IO.Abstractions;
using System.Threading.Tasks;
using Zennysoft.Game.Abstractions;
using Zennysoft.Game.Implementation;
using Zennysoft.Ma.Adapter;
namespace Zennysoft.Game.Ma;
@@ -26,7 +27,9 @@ public partial class App : Node, IApp
[Node] private LoadingScreen LoadingScreen { get; set; } = default!;
public IInstantiator Instantiator { get; set; } = default!;
[Node] private OptionsMenu OptionsMenu { get; set; }
[Node] private GalleryMenu GalleryMenu { get; set; }
IAppRepo IProvide<IAppRepo>.Value() => AppRepo;
@@ -34,41 +37,92 @@ public partial class App : Node, IApp
public IAppLogic AppLogic { get; set; } = default!;
public AppLogic.IBinding AppBinding { get; set; } = default!;
private Array _progress;
private Godot.Collections.Array _progress;
private SimpleInjector.Container _container;
private AutoProp<string> _loadedScene = new(string.Empty);
private DataViewer _dataViewer;
private bool _loadingGame = false;
private bool _loadingEnemyViewer = false;
private string _optionsSavePath = string.Empty;
private string _controllerSavePath = string.Empty;
private ISaveFileManager _saveFileManager;
private IGame _game;
private IDataViewer _enemyViewer;
private double _reportedProgress = 0;
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";
MainMenu.StartGame += OnStartGame;
MainMenu.EnemyViewer += OnEnemyViewer;
MainMenu.Gallery += OnGallery;
MainMenu.Options += OnOptions;
MainMenu.Quit += OnQuit;
_loadedScene.Changed += OnGameLoaded;
AppRepo = container.GetInstance<IAppRepo>();
AppLogic = container.GetInstance<IAppLogic>();
GalleryMenu.GalleryExited += GalleryExited;
OptionsMenu.OptionsMenuExited += OptionsMenu_OptionsMenuExited;
OptionsMenu.DeleteSaveData += DeleteSaveData;
AppRepo = _container.GetInstance<IAppRepo>();
AppLogic = _container.GetInstance<IAppLogic>();
Task.Run(() => _saveFileManager.ReadFromFile<string>(_controllerSavePath).ContinueWith((data) =>
{
if (data.IsCompletedSuccessfully)
OptionsMenu.Controller.CallDeferred(nameof(OptionsMenu.Controller.LoadControllerInput), data.Result);
}));
AppLogic.Set(AppRepo);
AppLogic.Set(new AppLogic.Data());
AppRepo.DataViewerExited += DataViewerExited;
Input.MouseMode = Input.MouseModeEnum.Visible;
_progress = [];
this.Provide();
}
private void OnGameLoaded(string sceneName)
private void GameExitRequested()
{
LoadingScreen.Hide();
var scene = (PackedScene)ResourceLoader.LoadThreadedGet(sceneName);
var node = scene.Instantiate();
AddChild(node);
AppLogic.Input(new AppLogic.Input.QuitGame());
}
private void DeleteSaveData()
{
var saveFileManager = _container.GetInstance<ISaveFileManager>();
saveFileManager.DeleteSaveData();
}
private void DataViewerExited()
{
AppLogic.Input(new AppLogic.Input.EnemyViewerExited());
}
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 GalleryExited()
{
GalleryMenu.Hide();
MainMenu.GalleryButton.GrabFocus();
}
public void OnReady()
@@ -76,32 +130,53 @@ public partial class App : Node, IApp
AppBinding = AppLogic.Bind();
AppBinding
.Handle((in AppLogic.Output.Initialize _) =>
{
Task.Run(() => _saveFileManager.ReadFromFile<string>(_optionsSavePath).ContinueWith((data) =>
{
AppLogic.Input(new AppLogic.Input.SaveFileLoaded());
}));
})
.Handle((in AppLogic.Output.ShowSplashScreen _) =>
{
AppLogic.Input(new AppLogic.Input.FadeOutFinished());
})
.Handle((in AppLogic.Output.HideSplashScreen _) =>
{
})
.Handle((in AppLogic.Output.SetupGameScene _) =>
{
ResourceLoader.LoadThreadedRequest(GAME_SCENE_PATH);
_loadingGame = true;
MainMenu.Hide();
LoadingScreen.ShowLoadingScreen();
LoadGame(GAME_SCENE_PATH);
})
.Handle((in AppLogic.Output.ShowMainMenu _) =>
{
MainMenu.CallDeferred(MainMenu.MethodName.FadeIn);
})
.Handle((in AppLogic.Output.ShowGame _) =>
.Handle((in AppLogic.Output.CloseGame _) =>
{
LoadingScreen.HideLoadingScreen();
_game.GameExitRequested -= GameExitRequested;
MainMenu.StartGameButton.GrabFocus();
_game.CallDeferred(MethodName.QueueFree, []);
GetTree().Paused = false;
})
.Handle((in AppLogic.Output.StartLoadingSaveFile _) =>
{
})
.Handle((in AppLogic.Output.EnemyViewerOpened _) =>
{
ResourceLoader.LoadThreadedRequest(ENEMY_VIEWER_PATH);
_loadingEnemyViewer = true;
LoadingScreen.ShowLoadingScreen();
MainMenu.Hide();
LoadEnemyViewer(ENEMY_VIEWER_PATH);
})
.Handle((in AppLogic.Output.EnemyViewerExited _) =>
{
LoadingScreen.HideLoadingScreen();
if (_enemyViewer != null && _enemyViewer is DataViewer enemyViewer)
enemyViewer.CallDeferred(MethodName.QueueFree);
MainMenu.Show();
MainMenu.EnemyViewerButton.GrabFocus();
})
.Handle((in AppLogic.Output.ExitGame _) =>
{
@@ -109,33 +184,65 @@ public partial class App : Node, IApp
});
AppLogic.Start();
MainMenu.Show();
}
public override void _Process(double delta)
{
if (_loadingGame)
{
ResourceLoader.LoadThreadedGetStatus(GAME_SCENE_PATH, _progress);
LoadingScreen.ProgressBar.Value = (double)_progress.Single();
if ((double)_progress.Single() == 1)
_loadedScene.OnNext(GAME_SCENE_PATH);
}
if (_loadingEnemyViewer)
{
ResourceLoader.LoadThreadedGetStatus(ENEMY_VIEWER_PATH, _progress);
LoadingScreen.ProgressBar.Value = (double)_progress.Single();
if ((double)_progress.Single() == 1)
_loadedScene.OnNext(ENEMY_VIEWER_PATH);
}
LoadingScreen.ProgressBar.Value = Mathf.RoundToInt(Mathf.Lerp(LoadingScreen.ProgressBar.Value, _reportedProgress * 100, (float)delta * 2));
}
public void OnStartGame() => AppLogic.Input(new AppLogic.Input.NewGame());
private void OnEnemyViewer() => AppLogic.Input(new AppLogic.Input.EnemyViewerOpened());
private void OnLoadGame() => AppLogic.Input(new AppLogic.Input.LoadGame());
private void OnGalleryViewer() => AppLogic.Input(new AppLogic.Input.GalleryOpened());
private async void LoadGame(string sceneName)
{
var scene = await LoadSceneInternal(sceneName);
_game = scene as IGame;
_game.GameLoaded += OnGameLoaded;
_game.GameExitRequested += GameExitRequested;
CallDeferred(MethodName.AddChild, scene);
}
private void OnGameLoaded() => LoadingScreen.HideLoadingScreen();
private async void LoadEnemyViewer(string sceneName)
{
var scene = await LoadSceneInternal(sceneName);
_enemyViewer = scene as IDataViewer;
CallDeferred(MethodName.AddChild, scene);
LoadingScreen.HideLoadingScreen();
}
private async Task<Node> LoadSceneInternal(string sceneName)
{
LoadingScreen.ShowLoadingScreen();
LoadingScreen.ProgressBar.Value = 0;
var sceneLoader = new SceneLoader();
CallDeferred(MethodName.AddChild, sceneLoader);
sceneLoader.LoadSceneRequest(sceneName);
sceneLoader.SceneReportedProgress += SceneLoader_SceneReportedProgress;
await ToSignal(sceneLoader, SceneLoader.SignalName.SceneLoaded);
var result = sceneLoader.LoadedScene;
sceneLoader.QueueFree();
return result;
}
private void SceneLoader_SceneReportedProgress(double progress) => _reportedProgress = progress;
private async void OnOptions()
{
OptionsMenu.Show();
OptionsMenu.GameTab.GrabFocus();
}
private async void OnGallery()
{
GalleryMenu.Show();
GalleryMenu.ItemButton1.GrabFocus();
}
public void OnQuit() => AppLogic.Input(new AppLogic.Input.QuitGame());
@@ -152,7 +259,13 @@ public partial class App : Node, IApp
MainMenu.StartGame -= OnStartGame;
MainMenu.EnemyViewer -= OnEnemyViewer;
MainMenu.Gallery -= OnGallery;
MainMenu.Options -= OnOptions;
MainMenu.Quit -= OnQuit;
_loadedScene.Changed -= OnGameLoaded;
GalleryMenu.GalleryExited -= GalleryExited;
OptionsMenu.OptionsMenuExited -= OptionsMenu_OptionsMenuExited;
OptionsMenu.DeleteSaveData -= DeleteSaveData;
}
}
+26 -124
View File
@@ -1,140 +1,42 @@
// Attach to a ColorRect in front of texture/background
shader_type canvas_item;
// Handles the resolution changes, color depth, and dithering
group_uniforms resolution_and_colors;
uniform bool change_color_depth = false;
uniform int target_color_depth : hint_range(1, 8) = 5;
uniform bool dithering = false;
uniform bool scale_resolution = false;
uniform int target_resolution_scale = 3;
// Handles the concentric ripples
uniform float frequency: hint_range(0, 15, 0.01) = 4.0;
uniform float amplitude: hint_range(0, 3, 0.1) = 2.0;
uniform float ripple_rate : hint_range(0, 20.0, 1) = 5;
// Handles the LUTish recoloring
group_uniforms gradient_recoloring;
uniform bool enable_recolor = false;
uniform sampler2D to_gradient: hint_default_black;
// Handles the waves themselves
uniform float wave_amplitude: hint_range(0.001, 0.1, 0.001) = 0.05;
uniform float wave_frequency: hint_range(0, 15, 0.01) = 4.0;
int dithering_pattern(ivec2 fragcoord) {
const int pattern[] = {
-4, +0, -3, +1,
+2, -2, +3, -1,
-3, +1, -4, +0,
+3, -1, +2, -2
};
uniform sampler2D noise;
int x = fragcoord.x % 4;
int y = fragcoord.y % 4;
uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_linear_mipmap;
return pattern[y * 4 + x];
}
vec3 rgb2hsv(vec3 rgb) { //Converts RGB values to HSV
float r = rgb.r;
float g = rgb.g;
float b = rgb.b;
float cmax = max(r,max(g,b));
float cmin = min(r,min(g,b));
float delta = cmax - cmin;
float h = 0.f; //hue
if (delta > 0.f){
if (cmax == r){
h = (g-b)/delta;
h = mod(h,6.f);
} else if (cmax == g){
h = ((b - r) / delta) + 2.f;
} else {
h = ((r-g)/delta) + 4.f;
}
h = h * 60.f;
}
float s = 0.f; //saturation
if (cmax > 0.f){
s = delta / cmax;
}
return vec3(h,s,cmax); // Keep original alpha value
}
vec3 hsv2rgb(vec3 hsv) { //Converts HSV values to RGB
float h = hsv.r;
float s = hsv.g;
float v = hsv.b;
float c = v * s;
//X = C × (1 - |(H / 60°) mod 2 - 1|)
float x = h / 60.f;
x = mod(x,2.f);
x = abs(x - 1.f);
x = c * (1.f - x);
float m = v - c;
vec3 rgb = vec3(0.f,0.f,0.f);
if (h < 60.f) {
rgb = vec3(c,x,0.f);
} else if (h < 120.f){
rgb = vec3(x,c,0.f);
} else if (h < 180.f){
rgb = vec3(0.f,c,x);
} else if (h < 240.f){
rgb = vec3(0.f,x,c);
} else if (h < 300.f){
rgb = vec3(x,0.f,c);
} else if (h < 360.f){
rgb = vec3(c,0.f,x);
}
rgb[0] = rgb[0] + m;
rgb[1] = rgb[1] + m;
rgb[2] = rgb[2] + m;
return rgb;
vec2 wave(vec2 uv, float time) {
return vec2(
uv.x + sin(uv.y * wave_frequency + time) * wave_amplitude,
uv.y + sin(uv.x * wave_frequency + time) * wave_amplitude
);
}
void fragment() {
ivec2 uv;
vec3 color;
vec2 center_position = -1.0 + 2.0 * UV / (1.0 / TEXTURE_PIXEL_SIZE);
float center_distance = length(center_position);
if(scale_resolution){
uv = ivec2(FRAGCOORD.xy / float(target_resolution_scale));
color = texelFetch(TEXTURE, uv * target_resolution_scale, 0).rgb;
} else {
uv = ivec2(FRAGCOORD.xy);
color = texelFetch(TEXTURE, uv, 0).rgb;
}
float ripple = sin(center_distance * -frequency * PI + ripple_rate * TIME) * amplitude / (center_distance + 1.0);
if(enable_recolor){
vec3 hsv = rgb2hsv(color);
float color_pos = (hsv.x / 360.0);
vec3 new_color = texture(to_gradient, vec2((color_pos), 0.5)).rgb;
vec3 new_hsv = rgb2hsv(new_color);
hsv.x = new_hsv.x;
vec3 final_rgb = hsv2rgb(hsv);
vec2 uv = FRAGCOORD.xy / (1.0 / SCREEN_PIXEL_SIZE).xy + (center_position/center_distance) * ripple * wave_amplitude;
vec2 background_wave = wave(uv, TIME);
vec4 background_texture = texture(SCREEN_TEXTURE,background_wave) * sqrt(amplitude);
color.rgb = final_rgb;
}
float alpha_scalar = (1.0 - min(center_distance, 1.0)) * background_texture.x * 2.5;
background_texture.a *= 1.0 * alpha_scalar * (ripple + background_texture.x * background_texture.y);
background_texture.a = max(background_texture.a - (background_texture.y * 0.45), 0.0);
// Convert from [0.0, 1.0] range to [0, 255] range
ivec3 c = ivec3(round(color * 255.0));
COLOR = vec4(background_texture.xyz, background_texture.a);
// Apply the dithering pattern
if (dithering) {
c += ivec3(dithering_pattern(uv));
}
vec3 final_color;
if(change_color_depth){
// Truncate from 8 bits to color_depth bits
c >>= (8 - target_color_depth);
final_color = vec3(c) / float(1 << target_color_depth);
} else {
final_color = vec3(c) / float(1 << 8);
}
// Convert back to [0.0, 1.0] range
COLOR.rgb = final_color;
}
+23 -3
View File
@@ -1,16 +1,36 @@
[gd_scene load_steps=4 format=3 uid="uid://cagfc5ridmteu"]
[gd_scene load_steps=6 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"]
[ext_resource type="PackedScene" uid="uid://cm6fo70yb2hip" path="res://src/ui/gallery/GalleryMenu.tscn" id="5_iuu71"]
[node name="App" type="Node"]
process_mode = 3
script = ExtResource("1_rt73h")
[node name="LoadingScreen" parent="." instance=ExtResource("3_3st5l")]
unique_name_in_owner = true
[node name="ColorRect" type="ColorRect" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 1)
[node name="MainMenu" parent="." instance=ExtResource("2_1uiag")]
unique_name_in_owner = true
[node name="OptionsMenu" parent="." instance=ExtResource("2_v0mgf")]
unique_name_in_owner = true
visible = false
[node name="GalleryMenu" parent="." instance=ExtResource("5_iuu71")]
unique_name_in_owner = true
visible = false
[node name="LoadingScreen" parent="." instance=ExtResource("3_3st5l")]
unique_name_in_owner = true
visible = false
top_level = true
z_index = 999

Some files were not shown because too many files have changed in this diff Show More