Compare commits
56 Commits
d692d5c705
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b9de11e5a | |||
| 678916be89 | |||
|
|
f39bd8ecdb | ||
|
|
76f4adc5be | ||
|
|
95227946d1 | ||
|
|
1ee3e97f85 | ||
| 6f90a0985a | |||
| 34742d568e | |||
|
|
3c369f79f7 | ||
|
|
fe45f0bcf2 | ||
| edff41af22 | |||
| 4ee4e02a51 | |||
| 8f8cc217dc | |||
| c491ea5050 | |||
|
|
725547d388 | ||
|
|
539430d112 | ||
| 46402401b4 | |||
| ed9e611fd9 | |||
| db7a1df1f7 | |||
|
|
3e8c11d55d | ||
| 613fc3bf60 | |||
| f69e219643 | |||
|
|
0afbf38bf9 | ||
|
|
23fdf7309d | ||
| fcffdb3b35 | |||
| 7210133330 | |||
|
|
e7bae342c9 | ||
|
|
c22fde3bb5 | ||
|
|
7cb5b20293 | ||
| a5846e08dc | |||
| 7b7fc910bd | |||
| 9fc875eda5 | |||
|
|
dc3c458414 | ||
|
|
ba38c6443c | ||
| 575a565a2c | |||
| 21d8c4770d | |||
|
|
1a77695155 | ||
|
|
11cbb8c4e5 | ||
| eed50bc04e | |||
| 2d55ae9cc4 | |||
| 0b909e4e7e | |||
| 7e6dca1c29 | |||
| 720696aed0 | |||
| 286c221530 | |||
| f5360adbf1 | |||
| bc161a58b3 | |||
| f0c4e65783 | |||
| 6ec45c4805 | |||
| 44fd8c82b0 | |||
|
|
20b659681a | ||
|
|
2786c95c22 | ||
|
|
b0f6aa176e | ||
| e29bef51c5 | |||
|
|
9ed5f8600c | ||
|
|
4c5281c852 | ||
|
|
8e030aabcd |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -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();
|
||||
}
|
||||
|
||||
5
Zennysoft.Game.Abstractions/Entity/IBehavior.cs
Normal file
5
Zennysoft.Game.Abstractions/Entity/IBehavior.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Zennysoft.Game.Abstractions.Entity;
|
||||
|
||||
public interface IBehavior
|
||||
{
|
||||
}
|
||||
11
Zennysoft.Game.Abstractions/Entity/IEntity.cs
Normal file
11
Zennysoft.Game.Abstractions/Entity/IEntity.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Zennysoft.Game.Abstractions.Entity
|
||||
{
|
||||
public interface IAction
|
||||
{
|
||||
public Task PerformAction();
|
||||
}
|
||||
public interface IAction<T>
|
||||
{
|
||||
public Task PerformAction(T arg);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Zennysoft.Game.Abstractions;
|
||||
|
||||
public interface ICanPatrol
|
||||
{
|
||||
public void Patrol();
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Zennysoft.Game.Abstractions;
|
||||
|
||||
public interface ISaveFileManager<T>
|
||||
public interface ISaveFileManager
|
||||
{
|
||||
public Task WriteToFile(T gameData, params IJsonTypeInfoResolver?[] resolvers);
|
||||
public Task WriteToFile<T>(T gameData, params IJsonTypeInfoResolver?[] resolvers);
|
||||
|
||||
public Task WriteToFile(T gameData, string filePath, params IJsonTypeInfoResolver?[] resolvers);
|
||||
public Task WriteToFile<T>(T gameData, string filePath, params IJsonTypeInfoResolver?[] resolvers);
|
||||
|
||||
public Task<T?> ReadFromFile(params IJsonTypeInfoResolver?[] resolvers);
|
||||
public Task<T?> ReadFromFile<T>(params IJsonTypeInfoResolver?[] resolvers);
|
||||
|
||||
public Task<T?> ReadFromFile(string filePath, params IJsonTypeInfoResolver?[] resolvers);
|
||||
public Task<T?> ReadFromFile<T>(string filePath, params IJsonTypeInfoResolver?[] resolvers);
|
||||
|
||||
public void DeleteSaveData(string filePath);
|
||||
|
||||
public void DeleteSaveData();
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ using Zennysoft.Game.Abstractions;
|
||||
|
||||
namespace Zennysoft.Game.Implementation;
|
||||
|
||||
public class SaveFileManager<T> : ISaveFileManager<T>
|
||||
public class SaveFileManager : ISaveFileManager
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private string _defaultSaveLocation;
|
||||
public const string DEFAULT_SAVE_FILE_NAME = "game.json";
|
||||
|
||||
@@ -23,27 +22,17 @@ public class SaveFileManager<T> : ISaveFileManager<T>
|
||||
|
||||
GodotSerialization.Setup();
|
||||
Serializer.AddConverter(new Texture2DConverter());
|
||||
|
||||
var upgradeDependencies = new Blackboard();
|
||||
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
Converters = {
|
||||
new SerializableTypeConverter(upgradeDependencies)
|
||||
},
|
||||
WriteIndented = true
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Task<T?> ReadFromFile(params IJsonTypeInfoResolver?[] resolvers)
|
||||
public Task<T?> ReadFromFile<T>(params IJsonTypeInfoResolver?[] resolvers)
|
||||
{
|
||||
if (!_fileSystem.File.Exists(_defaultSaveLocation))
|
||||
throw new FileNotFoundException();
|
||||
return ReadFromFile(_defaultSaveLocation, resolvers);
|
||||
return ReadFromFile<T>(_defaultSaveLocation, resolvers);
|
||||
}
|
||||
|
||||
public async Task<T?> ReadFromFile(string filePath, params IJsonTypeInfoResolver?[] resolvers)
|
||||
public async Task<T?> ReadFromFile<T>(string filePath, params IJsonTypeInfoResolver?[] resolvers)
|
||||
{
|
||||
if (!_fileSystem.File.Exists(filePath))
|
||||
throw new FileNotFoundException();
|
||||
@@ -51,20 +40,42 @@ public class SaveFileManager<T> : ISaveFileManager<T>
|
||||
var json = await _fileSystem.File.ReadAllTextAsync(filePath);
|
||||
|
||||
var resolver = new SerializableTypeResolver();
|
||||
_jsonOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine([resolver, .. resolvers]);
|
||||
return JsonSerializer.Deserialize<T?>(json, _jsonOptions);
|
||||
var upgradeDependencies = new Blackboard();
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
Converters = {
|
||||
new SerializableTypeConverter(upgradeDependencies)
|
||||
},
|
||||
WriteIndented = true
|
||||
};
|
||||
jsonOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine([resolver, .. resolvers]);
|
||||
return JsonSerializer.Deserialize<T?>(json, jsonOptions);
|
||||
}
|
||||
|
||||
public Task WriteToFile(T gameData, params IJsonTypeInfoResolver?[] resolvers)
|
||||
public Task WriteToFile<T>(T gameData, params IJsonTypeInfoResolver?[] resolvers)
|
||||
{
|
||||
return WriteToFile(gameData, _defaultSaveLocation, resolvers);
|
||||
}
|
||||
|
||||
public async Task WriteToFile(T gameData, string filePath, params IJsonTypeInfoResolver?[] resolvers)
|
||||
public async Task WriteToFile<T>(T gameData, string filePath, params IJsonTypeInfoResolver?[] resolvers)
|
||||
{
|
||||
var resolver = new SerializableTypeResolver();
|
||||
_jsonOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine([resolver, .. resolvers]);
|
||||
var json = JsonSerializer.Serialize(gameData, _jsonOptions);
|
||||
var upgradeDependencies = new Blackboard();
|
||||
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
Converters = {
|
||||
new SerializableTypeConverter(upgradeDependencies)
|
||||
},
|
||||
WriteIndented = true
|
||||
};
|
||||
jsonOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine([resolver, .. resolvers]);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,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 +34,8 @@ public partial class AppLogic
|
||||
|
||||
public readonly record struct EnemyViewerOpened;
|
||||
|
||||
public readonly record struct EnemyViewerExited;
|
||||
|
||||
public readonly record struct GalleryOpened;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public partial class AppLogic
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.EnemyViewerOpened>
|
||||
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.EnemyViewerOpened>, IGet<Input.QuitGame>
|
||||
{
|
||||
public MainMenu()
|
||||
{
|
||||
@@ -16,6 +16,11 @@ public partial class AppLogic
|
||||
public Transition On(in Input.NewGame input) => To<GameStarted>();
|
||||
|
||||
public Transition On(in Input.EnemyViewerOpened input) => To<EnemyViewer>();
|
||||
public Transition On(in Input.QuitGame input)
|
||||
{
|
||||
Output(new Output.ExitGame());
|
||||
return ToSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -3,50 +3,31 @@ using Zennysoft.Ma.Adapter.Entity;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter
|
||||
{
|
||||
public class DamageCalculator : IDamageCalculator
|
||||
public static class DamageCalculator
|
||||
{
|
||||
public double CalculateDamage(double damage,
|
||||
ElementType elementType,
|
||||
double defense,
|
||||
ElementalResistanceSet elementalResistanceSet,
|
||||
bool isCriticalHit = false,
|
||||
bool ignoreDefense = false,
|
||||
bool ignoreElementalResistance = false)
|
||||
public static int CalculateDamage(AttackData damage, double defense, ElementalResistanceSet elementalResistanceSet)
|
||||
{
|
||||
var calculatedDamage = damage;
|
||||
if (!ignoreElementalResistance)
|
||||
calculatedDamage = CalculateElementalResistance(calculatedDamage, elementType, elementalResistanceSet);
|
||||
if (!ignoreDefense)
|
||||
var calculatedDamage = damage.BaseDamage;
|
||||
if (!damage.IgnoreDefense)
|
||||
calculatedDamage = CalculateDefenseResistance(calculatedDamage, defense);
|
||||
if (isCriticalHit)
|
||||
calculatedDamage *= 2;
|
||||
if (!damage.IgnoreElementalResistance)
|
||||
calculatedDamage = CalculateElementalResistance(calculatedDamage, elementalResistanceSet.ElementalResistance[damage.ElementType]);
|
||||
|
||||
return calculatedDamage;
|
||||
return Mathf.Max(1, calculatedDamage);
|
||||
}
|
||||
|
||||
private static double CalculateDefenseResistance(double incomingDamage, double defense)
|
||||
private static int CalculateDefenseResistance(int incomingDamage, double defense)
|
||||
{
|
||||
return Mathf.Max(incomingDamage - defense, 0.0);
|
||||
var result = incomingDamage - (int)(incomingDamage * (defense / 100));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double CalculateElementalResistance(
|
||||
double incomingDamage,
|
||||
ElementType incomingElementType,
|
||||
ElementalResistanceSet elementalResistanceSet)
|
||||
private static int CalculateElementalResistance(
|
||||
int incomingDamage,
|
||||
double elementalResistance)
|
||||
{
|
||||
var resistance = elementalResistanceSet.ElementalResistance[incomingElementType];
|
||||
return Mathf.Max(incomingDamage - (incomingDamage * resistance), 0.0);
|
||||
var result = incomingDamage - (int)(incomingDamage * (elementalResistance / 100));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDamageCalculator
|
||||
{
|
||||
public double CalculateDamage(double damage,
|
||||
ElementType elementType,
|
||||
double defense,
|
||||
ElementalResistanceSet elementalResistanceSet,
|
||||
bool isCriticalHit = false,
|
||||
bool ignoreDefense = false,
|
||||
bool ignoreElementalResistance = false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IAttackComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> CurrentAttack { get; }
|
||||
|
||||
public IAutoProp<int> MaximumAttack { get; }
|
||||
|
||||
public void Restore(int restoreAmount);
|
||||
|
||||
public void Reduce(int reduceAmount);
|
||||
|
||||
public void SetAttack(int attack);
|
||||
|
||||
public void RaiseMaximumAttack(int raiseAmount);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IDefenseComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> CurrentDefense { get; }
|
||||
|
||||
public IAutoProp<int> MaximumDefense { get; }
|
||||
|
||||
public void Restore(int restoreAmount);
|
||||
|
||||
public void Reduce(int reduceAmount);
|
||||
|
||||
public void SetDefense(int attack);
|
||||
|
||||
public void RaiseMaximumDefense(int raiseAmount);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IEntityComponent
|
||||
{
|
||||
public void Reset();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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<EquipableItem> EquippedArmor { get; }
|
||||
|
||||
public IAutoProp<EquipableItem> EquippedAccessory { get; }
|
||||
|
||||
public void Equip(EquipableItem equipable);
|
||||
|
||||
public void Unequip(EquipableItem equipable);
|
||||
|
||||
public bool IsItemEquipped(InventoryItem item);
|
||||
|
||||
public int BonusAttack { get; }
|
||||
|
||||
public int BonusDefense { get; }
|
||||
|
||||
public int BonusHP { get; }
|
||||
|
||||
public int BonusVT { get; }
|
||||
|
||||
public int BonusLuck { get; }
|
||||
|
||||
public ElementalResistanceSet ElementalResistance { get; }
|
||||
|
||||
public event Action<EquipableItem> EquipmentChanged;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IExperiencePointsComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> CurrentExp { get; }
|
||||
|
||||
public IAutoProp<int> ExpToNextLevel { get; }
|
||||
|
||||
public IAutoProp<double> ExpGainRate { get; }
|
||||
|
||||
public IAutoProp<int> Level { get; }
|
||||
|
||||
public void Gain(int baseExpGain);
|
||||
|
||||
public void LevelUp();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IHealthComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> CurrentHP { get; }
|
||||
|
||||
public IAutoProp<int> MaximumHP { get; }
|
||||
|
||||
public event Action? HealthReachedZero;
|
||||
public event Action? DamageTaken;
|
||||
|
||||
public bool AtFullHealth { get; }
|
||||
|
||||
public void Heal(int healAmount);
|
||||
|
||||
public void Damage(int damageAmount);
|
||||
|
||||
public void SetCurrentHealth(int health);
|
||||
|
||||
public void SetMaximumHealth(int health);
|
||||
|
||||
public void RaiseMaximumHP(int raiseAmount, bool restoreHP = false);
|
||||
}
|
||||
10
Zennysoft.Game.Ma.Implementation/Components/LuckComponent.cs
Normal file
10
Zennysoft.Game.Ma.Implementation/Components/LuckComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface ILuckComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> Luck { get; }
|
||||
|
||||
public void IncreaseLuck(int value);
|
||||
}
|
||||
22
Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs
Normal file
22
Zennysoft.Game.Ma.Implementation/Components/VTComponent.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Chickensoft.Collections;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IVTComponent : IEntityComponent
|
||||
{
|
||||
public IAutoProp<int> CurrentVT { get; }
|
||||
|
||||
public IAutoProp<int> MaximumVT { get; }
|
||||
|
||||
public bool AtFullVT { get; }
|
||||
|
||||
public void Restore(int restoreAmount);
|
||||
|
||||
public void Reduce(int reduceAmount);
|
||||
|
||||
public void SetVT(int vt);
|
||||
|
||||
public void RaiseMaximumVT(int raiseAmount, bool restoreVT = true);
|
||||
|
||||
public void SetMaximumVT(int vt);
|
||||
}
|
||||
3
Zennysoft.Game.Ma.Implementation/Entity/AttackData.cs
Normal file
3
Zennysoft.Game.Ma.Implementation/Entity/AttackData.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public record AttackData(int BaseDamage, ElementType ElementType, bool IgnoreDefense = false, bool IgnoreElementalResistance = false);
|
||||
@@ -0,0 +1 @@
|
||||
uid://ci7va4hsq6hyt
|
||||
@@ -0,0 +1 @@
|
||||
uid://87d8kluait8y
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://c7e5g8l6wuph"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://87d8kluait8y" path="res://src/enemy/behaviors/PatrolBehavior.cs" id="1_lobva"]
|
||||
|
||||
[node name="NavigationAgent" type="NavigationAgent3D"]
|
||||
avoidance_enabled = true
|
||||
debug_enabled = true
|
||||
script = ExtResource("1_lobva")
|
||||
_patrolSpeed = 100.0
|
||||
@@ -1,9 +1,14 @@
|
||||
namespace Zennysoft.Ma.Adapter.Entity
|
||||
using Chickensoft.Serialization;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter.Entity
|
||||
{
|
||||
public record ElementalResistanceSet
|
||||
{
|
||||
[Save("elemental_resist_set")]
|
||||
public Dictionary<ElementType, double> ElementalResistance { get; }
|
||||
|
||||
public static ElementalResistanceSet None => new ElementalResistanceSet(0, 0, 0, 0, 0);
|
||||
|
||||
public ElementalResistanceSet(double aeolicResistance, double hydricResistance, double igneousResistance, double ferrumResistance, double telluricResistance)
|
||||
{
|
||||
ElementalResistance = new Dictionary<ElementType, double>
|
||||
@@ -16,5 +21,15 @@
|
||||
{ ElementType.Telluric, telluricResistance },
|
||||
};
|
||||
}
|
||||
|
||||
public static ElementalResistanceSet operator +(ElementalResistanceSet left, ElementalResistanceSet right)
|
||||
{
|
||||
return new ElementalResistanceSet(
|
||||
left.ElementalResistance[ElementType.Aeolic] + right.ElementalResistance[ElementType.Aeolic],
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs
Normal file
39
Zennysoft.Game.Ma.Implementation/Entity/IEnemy.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using System.Collections.Immutable;
|
||||
using Zennysoft.Game.Ma;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter.Entity
|
||||
{
|
||||
public interface IEnemy : ICharacterBody3D
|
||||
{
|
||||
public void Activate();
|
||||
|
||||
public void Idle();
|
||||
|
||||
public void Die();
|
||||
|
||||
public void PerformAction();
|
||||
|
||||
public void ReturnToDefaultState();
|
||||
|
||||
public void OnAbsorb();
|
||||
|
||||
public void OnMorph();
|
||||
|
||||
public IDungeonRoom GetCurrentRoom(ImmutableList<IDungeonRoom> dungeonRooms);
|
||||
|
||||
public void MoveEnemyToNewRoom(IDungeonRoom newRoom);
|
||||
|
||||
public IHealthComponent HealthComponent { get; }
|
||||
|
||||
public IAttackComponent AttackComponent { get; }
|
||||
|
||||
public IDefenseComponent DefenseComponent { get; }
|
||||
|
||||
public int InitialHP { get; }
|
||||
|
||||
public int InitialAttack { get; }
|
||||
|
||||
public int InitialDefense { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Godot;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter.Entity
|
||||
{
|
||||
public interface IKnockbackable
|
||||
{
|
||||
void Knockback(float impulse, Vector3 direction);
|
||||
}
|
||||
}
|
||||
22
Zennysoft.Game.Ma.Implementation/Equipment/EquipableItem.cs
Normal file
22
Zennysoft.Game.Ma.Implementation/Equipment/EquipableItem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
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);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public abstract partial class InventoryItem : Node3D
|
||||
[Save("inventory_item_spawn_rate")]
|
||||
public abstract float SpawnRate { get; }
|
||||
[Save("inventory_item_throw_damage")]
|
||||
public abstract double ThrowDamage { get; }
|
||||
public abstract int ThrowDamage { get; }
|
||||
[Save("inventory_item_throw_speed")]
|
||||
public abstract float ThrowSpeed { get; }
|
||||
[Save("inventory_item_tag")]
|
||||
@@ -5,5 +5,6 @@ public enum WeaponTag
|
||||
None,
|
||||
SelfDamage,
|
||||
IgnoreAffinity,
|
||||
IgnoreDefense,
|
||||
Knockback,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.Serialization;
|
||||
using Zennysoft.Game.Ma;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
[Meta, Id("game_data")]
|
||||
public partial record GameData
|
||||
{
|
||||
[Save("player_data")]
|
||||
public required PlayerData PlayerData { get; init; }
|
||||
|
||||
[Save("map_data")]
|
||||
public required MapData MapData { get; init; }
|
||||
|
||||
[Save("rescued_items")]
|
||||
public required RescuedItemDatabase RescuedItems { get; init; }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Chickensoft.Collections;
|
||||
using Godot;
|
||||
using Zennysoft.Game.Abstractions;
|
||||
using Zennysoft.Game.Implementation;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
@@ -52,8 +53,6 @@ public interface IGameRepo : IDisposable
|
||||
|
||||
public void OnPlayerAttackedWall();
|
||||
|
||||
public void OnPlayerAttackedEnemy();
|
||||
|
||||
public void OnRestorativePickedUp(IHealthPack restorative);
|
||||
|
||||
public void CloseInventory();
|
||||
@@ -146,11 +145,6 @@ public class GameRepo : IGameRepo
|
||||
PlayerAttackedWall?.Invoke();
|
||||
}
|
||||
|
||||
public void OnPlayerAttackedEnemy()
|
||||
{
|
||||
PlayerAttackedEnemy?.Invoke();
|
||||
}
|
||||
|
||||
public void OnRestorativePickedUp(IHealthPack restorative)
|
||||
{
|
||||
RestorativePickedUp?.Invoke(restorative);
|
||||
|
||||
@@ -8,14 +8,14 @@ public partial class GameState
|
||||
|
||||
public readonly record struct LoadGame;
|
||||
|
||||
public readonly record struct ContinueGame;
|
||||
|
||||
public readonly record struct ReturnToMainMenu;
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,11 +16,6 @@ public partial class GameState
|
||||
OnAttach(() => Get<IGameRepo>().Pause());
|
||||
}
|
||||
|
||||
public Transition On(in Input.ReturnToMainMenu input)
|
||||
{
|
||||
return To<MainMenu>();
|
||||
}
|
||||
|
||||
public Transition On(in Input.UseTeleport input)
|
||||
{
|
||||
Output(new Output.OpenFloorExitScreen());
|
||||
|
||||
@@ -8,7 +8,7 @@ public partial class GameState
|
||||
public partial record State
|
||||
{
|
||||
[Meta, LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial record FloorExitScreen : State, IGet<Input.LoadNextFloor>, IGet<Input.ReturnToMainMenu>
|
||||
public partial record FloorExitScreen : State, IGet<Input.LoadNextFloor>
|
||||
{
|
||||
public FloorExitScreen()
|
||||
{
|
||||
@@ -20,11 +20,6 @@ public partial class GameState
|
||||
Output(new Output.LoadNextFloor());
|
||||
return To<InGame>();
|
||||
}
|
||||
|
||||
public Transition On(in Input.ReturnToMainMenu input)
|
||||
{
|
||||
return To<MainMenu>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,13 @@ public partial class GameState
|
||||
public partial record State
|
||||
{
|
||||
[Meta, LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial record GameOver : State, IGet<Input.ContinueGame>, IGet<Input.ReturnToMainMenu>
|
||||
public partial record GameOver : State, IGet<Input.NewGame>
|
||||
{
|
||||
public Transition On(in Input.ContinueGame input)
|
||||
public Transition On(in Input.NewGame input)
|
||||
{
|
||||
Output(new Output.InitializeGame());
|
||||
return To<InGame>();
|
||||
}
|
||||
|
||||
public Transition On(in Input.ReturnToMainMenu input)
|
||||
{
|
||||
return To<MainMenu>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ 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>
|
||||
{
|
||||
public Transition On(in Input.InventoryButtonPressed input)
|
||||
public Transition On(in Input.InteractButtonPressed input)
|
||||
{
|
||||
Output(new Output.CloseInventoryMenu());
|
||||
return To<InGame>();
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public partial class GameState
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta, LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial record MainMenu : State, IGet<Input.NewGame>, IGet<Input.ContinueGame>, IGet<Input.LoadGame>
|
||||
{
|
||||
public Transition On(in Input.NewGame input)
|
||||
{
|
||||
Output(new Output.InitializeGame());
|
||||
return To<InGame>();
|
||||
}
|
||||
|
||||
public Transition On(in Input.ContinueGame input)
|
||||
{
|
||||
Output(new Output.InitializeGame());
|
||||
return To<InGame>();
|
||||
}
|
||||
|
||||
public Transition On(in Input.LoadGame input)
|
||||
{
|
||||
Output(new Output.LoadGameFromFile());
|
||||
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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.Serialization;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
[Meta, Id("equipable_item")]
|
||||
public abstract partial class EquipableItem : InventoryItem
|
||||
{
|
||||
[Save("equipable_item_is_equipped")]
|
||||
public bool IsEquipped { get; set; }
|
||||
}
|
||||
9
Zennysoft.Game.Ma.Implementation/Item/IDroppedItem.cs
Normal file
9
Zennysoft.Game.Ma.Implementation/Item/IDroppedItem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Zennysoft.Ma.Adapter
|
||||
{
|
||||
public interface IDroppedItem
|
||||
{
|
||||
void RescueItem();
|
||||
|
||||
public InventoryItem Item { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
public interface IInventory
|
||||
{
|
||||
public bool PickUpItem(InventoryItem item);
|
||||
|
||||
public List<InventoryItem> Items { get; }
|
||||
|
||||
public bool TryAdd(InventoryItem inventoryItem);
|
||||
@@ -10,5 +12,8 @@ public interface IInventory
|
||||
|
||||
public void Remove(InventoryItem inventoryItem);
|
||||
|
||||
public void Sort();
|
||||
public bool Sort(EquipableItem currentWeapon, EquipableItem currentArmor, EquipableItem currentAccessory);
|
||||
|
||||
public event Action<string> BroadcastMessage;
|
||||
public event Action InventoryChanged;
|
||||
}
|
||||
|
||||
6
Zennysoft.Game.Ma.Implementation/Item/IThrownItem.cs
Normal file
6
Zennysoft.Game.Ma.Implementation/Item/IThrownItem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IThrownItem
|
||||
{
|
||||
public InventoryItem ItemThatIsThrown { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.Serialization;
|
||||
using Zennysoft.Game.Abstractions;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
|
||||
@@ -21,4 +21,4 @@ public partial class UsableItemTagEnumContext : JsonSerializerContext;
|
||||
public partial class BoxItemTagEnumContext : JsonSerializerContext;
|
||||
|
||||
[JsonSerializable(typeof(ElementType))]
|
||||
public partial class ElementTypeEnumContext : JsonSerializerContext;
|
||||
public partial class ElementTypeEnumContext : JsonSerializerContext;
|
||||
@@ -1,6 +1,6 @@
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Godot;
|
||||
using System.Collections.Immutable;
|
||||
using Zennysoft.Ma.Adapter.Entity;
|
||||
|
||||
namespace Zennysoft.Game.Ma;
|
||||
public interface IDungeonRoom : INode3D
|
||||
@@ -10,8 +10,8 @@ public class Module
|
||||
public static void Bootstrap(Container container)
|
||||
{
|
||||
container.RegisterSingleton<IFileSystem, FileSystem>();
|
||||
container.RegisterSingleton<ISaveFileManager<GameData>, SaveFileManager<GameData>>();
|
||||
container.RegisterSingleton<IMaSaveFileManager<GameData>, MaSaveFileManager<GameData>>();
|
||||
container.RegisterSingleton<ISaveFileManager, SaveFileManager>();
|
||||
container.RegisterSingleton<IMaSaveFileManager, MaSaveFileManager>();
|
||||
container.RegisterSingleton<IGameRepo, GameRepo>();
|
||||
container.RegisterSingleton<IGameState, GameState>();
|
||||
container.RegisterSingleton<IDimmableAudioStreamPlayer, DimmableAudioStreamPlayer>();
|
||||
|
||||
@@ -1,66 +1,47 @@
|
||||
using Chickensoft.Collections;
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Godot;
|
||||
using Zennysoft.Game.Abstractions;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IPlayer : IKillable
|
||||
public interface IPlayer : IKillable, ICharacterBody3D
|
||||
{
|
||||
public void InitializePlayerState();
|
||||
public void ResetPlayerData();
|
||||
|
||||
public void Activate();
|
||||
|
||||
public void Deactivate();
|
||||
|
||||
public void Attack();
|
||||
|
||||
public void TakeDamage(double damage, ElementType elementType = ElementType.None, bool isCriticalHit = false);
|
||||
public void TakeDamage(AttackData damage);
|
||||
|
||||
public void Knockback(float impulse);
|
||||
|
||||
public void GainExp(double expGained);
|
||||
|
||||
public void LevelUp();
|
||||
|
||||
public void Move(float delta);
|
||||
|
||||
public void TeleportPlayer(Transform3D newTransform);
|
||||
|
||||
public void HealHP(int amount);
|
||||
|
||||
public void RaiseHP(int amount);
|
||||
|
||||
public void HealVT(int amount);
|
||||
|
||||
public void RaiseVT(int amount);
|
||||
|
||||
public void ModifyBonusAttack(int amount);
|
||||
|
||||
public void ModifyBonusDefense(int amount);
|
||||
|
||||
public void ModifyMaximumHP(int amount);
|
||||
|
||||
public void ModifyMaximumVT(int amount);
|
||||
|
||||
public void ModifyBonusLuck(double amount);
|
||||
|
||||
public void SetHealthTimerStatus(bool isActive);
|
||||
|
||||
public IInventory Inventory { get; }
|
||||
|
||||
public PlayerStats Stats { get; }
|
||||
|
||||
public Vector3 CurrentPosition { get; }
|
||||
|
||||
public Basis CurrentBasis { get; }
|
||||
|
||||
public AutoProp<EquipableItem> EquippedWeapon { get; }
|
||||
|
||||
public AutoProp<EquipableItem> EquippedArmor { get; }
|
||||
|
||||
public AutoProp<EquipableItem> EquippedAccessory { get; }
|
||||
|
||||
public void Equip(EquipableItem equipable);
|
||||
|
||||
public void Unequip(EquipableItem equipable);
|
||||
|
||||
public IInventory Inventory { get; }
|
||||
|
||||
public IHealthComponent HealthComponent { get; }
|
||||
|
||||
public IVTComponent VTComponent { get; }
|
||||
|
||||
public IAttackComponent AttackComponent { get; }
|
||||
|
||||
public IDefenseComponent DefenseComponent { get; }
|
||||
|
||||
public IExperiencePointsComponent ExperiencePointsComponent { get; }
|
||||
|
||||
public ILuckComponent LuckComponent { get; }
|
||||
|
||||
public IEquipmentComponent EquipmentComponent { get; }
|
||||
|
||||
public void PlayTestAnimation();
|
||||
|
||||
public event Action PlayerDied;
|
||||
public delegate InventoryItem RerollItem(InventoryItem item);
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.Serialization;
|
||||
using Zennysoft.Ma.Adapter;
|
||||
|
||||
namespace Zennysoft.Game.Ma;
|
||||
|
||||
[Meta, Id("player_data")]
|
||||
public partial record PlayerData
|
||||
{
|
||||
[Save("player_stats")]
|
||||
public required PlayerStats PlayerStats { get; init; }
|
||||
|
||||
[Save("player_inventory")]
|
||||
public required IInventory Inventory { get; init; }
|
||||
}
|
||||
|
||||
[Meta, Id("map_data")]
|
||||
public partial record MapData
|
||||
{
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
using Chickensoft.Collections;
|
||||
using Godot;
|
||||
|
||||
public class PlayerStats
|
||||
{
|
||||
public PlayerStats(AutoProp<int> currentHP,
|
||||
AutoProp<int> maximumHP,
|
||||
AutoProp<int> currentVT,
|
||||
AutoProp<int> maximumVT,
|
||||
AutoProp<int> currentAttack,
|
||||
AutoProp<int> maxAttack,
|
||||
AutoProp<int> bonusAttack,
|
||||
AutoProp<int> currentDefense,
|
||||
AutoProp<int> maxDefense,
|
||||
AutoProp<int> bonusDefense,
|
||||
AutoProp<double> currentExp,
|
||||
AutoProp<int> expToNextLevel,
|
||||
AutoProp<int> currentLevel,
|
||||
AutoProp<double> luck)
|
||||
{
|
||||
CurrentHP = currentHP;
|
||||
MaximumHP = maximumHP;
|
||||
CurrentVT = currentVT;
|
||||
MaximumVT = maximumVT;
|
||||
CurrentAttack = currentAttack;
|
||||
MaxAttack = maxAttack;
|
||||
BonusAttack = bonusAttack;
|
||||
CurrentDefense = currentDefense;
|
||||
MaxDefense = maxDefense;
|
||||
BonusDefense = bonusDefense;
|
||||
CurrentExp = currentExp;
|
||||
ExpToNextLevel = expToNextLevel;
|
||||
CurrentLevel = currentLevel;
|
||||
Luck = luck;
|
||||
}
|
||||
|
||||
public AutoProp<int> CurrentHP { get; init; }
|
||||
public AutoProp<int> MaximumHP { get; init; }
|
||||
public AutoProp<int> CurrentVT { get; init; }
|
||||
public AutoProp<int> MaximumVT { get; init; }
|
||||
public AutoProp<int> CurrentAttack { get; init; }
|
||||
public AutoProp<int> MaxAttack { get; init; }
|
||||
public AutoProp<int> BonusAttack { get; init; }
|
||||
public AutoProp<int> CurrentDefense { get; init; }
|
||||
public AutoProp<int> MaxDefense { get; init; }
|
||||
public AutoProp<int> BonusDefense { get; init; }
|
||||
public AutoProp<double> CurrentExp { get; init; }
|
||||
public AutoProp<int> ExpToNextLevel { get; init; }
|
||||
public AutoProp<int> CurrentLevel { get; init; }
|
||||
public AutoProp<double> Luck { get; init; }
|
||||
|
||||
public void SetCurrentHP(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumHP.Value);
|
||||
CurrentHP.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumHP(int newValue)
|
||||
{
|
||||
MaximumHP.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentVT(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaximumVT.Value);
|
||||
CurrentVT.OnNext(clampedValue);
|
||||
}
|
||||
public void SetMaximumVT(int newValue)
|
||||
{
|
||||
MaximumVT.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentExp(double newValue)
|
||||
{
|
||||
CurrentExp.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentLevel(int newValue)
|
||||
{
|
||||
CurrentLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentAttack(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxAttack.Value);
|
||||
CurrentAttack.OnNext(clampedValue);
|
||||
}
|
||||
public void SetBonusAttack(int newValue)
|
||||
{
|
||||
BonusAttack.OnNext(newValue);
|
||||
}
|
||||
public void SetMaxAttack(int newValue)
|
||||
{
|
||||
MaxAttack.OnNext(newValue);
|
||||
}
|
||||
public void SetCurrentDefense(int newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, MaxDefense.Value);
|
||||
CurrentDefense.OnNext(clampedValue);
|
||||
}
|
||||
public void SetBonusDefense(int newValue)
|
||||
{
|
||||
BonusDefense.OnNext(newValue);
|
||||
}
|
||||
public void SetMaxDefense(int newValue)
|
||||
{
|
||||
MaxDefense.OnNext(newValue);
|
||||
}
|
||||
public void SetExpToNextLevel(int newValue)
|
||||
{
|
||||
ExpToNextLevel.OnNext(newValue);
|
||||
}
|
||||
public void SetLuck(double newValue)
|
||||
{
|
||||
var clampedValue = Mathf.Clamp(newValue, 0, 1.0);
|
||||
Luck.OnNext(clampedValue);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ public partial class PlayerLogic
|
||||
|
||||
public readonly record struct Attack;
|
||||
|
||||
public readonly record struct AttackAnimationFinished;
|
||||
|
||||
public readonly record struct Die;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ public interface IPlayerLogic : ILogicBlock<PlayerLogic.State>;
|
||||
[LogicBlock(typeof(State), Diagram = true)]
|
||||
public partial class PlayerLogic : LogicBlock<PlayerLogic.State>, IPlayerLogic
|
||||
{
|
||||
public override Transition GetInitialState() => To<State.Idle>();
|
||||
public override Transition GetInitialState() => To<State.Alive>();
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public partial record State
|
||||
{
|
||||
[Meta]
|
||||
public partial record Attacking : Alive, IGet<Input.AttackAnimationFinished>
|
||||
{
|
||||
public Transition On(in Input.AttackAnimationFinished input)
|
||||
{
|
||||
return To<Idle>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public partial class PlayerLogic
|
||||
{
|
||||
public abstract partial record State
|
||||
{
|
||||
[Meta, Id("player_logic_state_alive_idle")]
|
||||
public partial record Idle : Alive, IGet<Input.Attack>
|
||||
{
|
||||
|
||||
public virtual Transition On(in Input.Attack input)
|
||||
{
|
||||
GD.Print("Attacking...");
|
||||
Output(new Output.Animations.Attack());
|
||||
return To<Attacking>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public partial class PlayerLogic
|
||||
public partial record State
|
||||
{
|
||||
[Meta, Id("player_logic_alive")]
|
||||
public abstract partial record Alive : State, IGet<Input.PhysicsTick>, IGet<Input.Die>
|
||||
public partial record Alive : State, IGet<Input.PhysicsTick>, IGet<Input.Die>
|
||||
{
|
||||
public virtual Transition On(in Input.PhysicsTick input)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ public partial class PlayerLogic
|
||||
OnDetach(() => Get<IAppRepo>().GameEntered -= OnGameEntered);
|
||||
}
|
||||
|
||||
public Transition On(in Input.Enable input) => To<Idle>();
|
||||
public Transition On(in Input.Enable input) => To<Alive>();
|
||||
|
||||
public void OnGameEntered() => Input(new Input.Enable());
|
||||
}
|
||||
|
||||
11
Zennysoft.Game.Ma.Implementation/Quest/QuestData.cs
Normal file
11
Zennysoft.Game.Ma.Implementation/Quest/QuestData.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.Serialization;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
[Meta, Id("quest_data")]
|
||||
public partial record QuestData
|
||||
{
|
||||
[Save("quest_data_1")]
|
||||
public bool QuestMarker1 { get; set; } = false;
|
||||
}
|
||||
@@ -4,28 +4,28 @@ using Zennysoft.Game.Abstractions;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
public interface IMaSaveFileManager<T>
|
||||
public interface IMaSaveFileManager
|
||||
{
|
||||
Task Save(T gameData);
|
||||
Task Save<T>(T gameData);
|
||||
|
||||
Task<T?> Load();
|
||||
Task<T?> Load<T>();
|
||||
}
|
||||
|
||||
public sealed class MaSaveFileManager<T> : IMaSaveFileManager<T>
|
||||
public sealed class MaSaveFileManager : IMaSaveFileManager
|
||||
{
|
||||
private readonly ISaveFileManager<T> _saveFileManager;
|
||||
private readonly ISaveFileManager _saveFileManager;
|
||||
private ImmutableList<IJsonTypeInfoResolver> _converters;
|
||||
|
||||
public MaSaveFileManager(ISaveFileManager<T> saveFileManager)
|
||||
public MaSaveFileManager(ISaveFileManager saveFileManager)
|
||||
{
|
||||
_saveFileManager = saveFileManager;
|
||||
_converters = [WeaponTagEnumContext.Default, ItemTagEnumContext.Default, ElementTypeEnumContext.Default, AccessoryTagEnumContext.Default, ThrowableItemTagEnumContext.Default, UsableItemTagEnumContext.Default, BoxItemTagEnumContext.Default];
|
||||
}
|
||||
|
||||
public async Task Save(T gameData)
|
||||
public async Task Save<T>(T gameData)
|
||||
{
|
||||
await _saveFileManager.WriteToFile(gameData, [.. _converters]);
|
||||
}
|
||||
|
||||
public async Task<T?> Load() => await _saveFileManager.ReadFromFile([.. _converters]);
|
||||
public async Task<T?> Load<T>() => await _saveFileManager.ReadFromFile<T>([.. _converters]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.LogicBlocks;
|
||||
using Zennysoft.Game.Implementation;
|
||||
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Zennysoft.Game.Ma;
|
||||
using Zennysoft.Game.Implementation;
|
||||
using Zennysoft.Game.Ma;
|
||||
|
||||
namespace Zennysoft.Ma.Adapter;
|
||||
public partial class InGameUILogic
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Game\state\states\**" />
|
||||
<Compile Remove="Map\**" />
|
||||
<EmbeddedResource Remove="Game\state\states\**" />
|
||||
<EmbeddedResource Remove="Map\**" />
|
||||
<None Remove="Game\state\states\**" />
|
||||
<None Remove="Map\**" />
|
||||
<Compile Remove="Game\state\states\**" />
|
||||
<EmbeddedResource Remove="Game\state\states\**" />
|
||||
<None Remove="Game\state\states\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -31,4 +28,8 @@
|
||||
<ProjectReference Include="..\Zennysoft.Game.Godot.Implementation\Zennysoft.Game.Implementation.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Actions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.4.1">
|
||||
<Project Sdk="Godot.NET.Sdk/4.4.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
|
||||
38
Zennysoft.Game.Ma/Ma.csproj.old.1
Normal file
38
Zennysoft.Game.Ma/Ma.csproj.old.1
Normal file
@@ -0,0 +1,38 @@
|
||||
<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
Zennysoft.Game.Ma/Ma.csproj.old.2
Normal file
38
Zennysoft.Game.Ma/Ma.csproj.old.2
Normal file
@@ -0,0 +1,38 @@
|
||||
<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
Zennysoft.Game.Ma/Ma.csproj.old.3
Normal file
38
Zennysoft.Game.Ma/Ma.csproj.old.3
Normal file
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
38
Zennysoft.Game.Ma/Ma.csproj.old.4
Normal file
38
Zennysoft.Game.Ma/Ma.csproj.old.4
Normal file
@@ -0,0 +1,38 @@
|
||||
<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
Zennysoft.Game.Ma/Ma.csproj.old.5
Normal file
38
Zennysoft.Game.Ma/Ma.csproj.old.5
Normal file
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
@@ -1,68 +0,0 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using System.Linq;
|
||||
using Zennysoft.Game.Ma;
|
||||
|
||||
[Tool]
|
||||
public partial class DungeonFloorLayout : LayoutType
|
||||
{
|
||||
[Export]
|
||||
public DungeonFloorSetType SetType
|
||||
{
|
||||
get => _setType;
|
||||
set
|
||||
{
|
||||
_setType = value;
|
||||
LayoutWithSpawnRate = [];
|
||||
NotifyPropertyListChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[ExportToolButton("Populate Map Data")]
|
||||
public Callable PopulateMapList => Callable.From(() => PopulateDictionary(SetType));
|
||||
|
||||
[Export]
|
||||
public Dictionary<string, float> LayoutWithSpawnRate { get; private set; }
|
||||
|
||||
[Export]
|
||||
public Dictionary<EnemyType, float> EnemySpawnRates { get; set; } = default!;
|
||||
|
||||
private string _floorPath = "res://src/map/dungeon/floors/";
|
||||
private DungeonFloorSetType _setType;
|
||||
|
||||
private void PopulateDictionary(DungeonFloorSetType setType)
|
||||
{
|
||||
var floorPath = _floorPath;
|
||||
var floorType = string.Empty;
|
||||
if (setType == DungeonFloorSetType.SetA)
|
||||
floorType = "SetAFloors";
|
||||
else if (setType == DungeonFloorSetType.SetB)
|
||||
floorType = "SetBFloors";
|
||||
|
||||
var pathToScenes = $"{floorPath}/{floorType}";
|
||||
|
||||
var files = DirAccess.GetFilesAt(pathToScenes).Where(x => x.EndsWith(".tscn"));
|
||||
|
||||
var newMaps = new Dictionary<string, float>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (LayoutWithSpawnRate.ContainsKey($"{floorType}/{file}"))
|
||||
{
|
||||
var spawnRate = LayoutWithSpawnRate.TryGetValue($"{floorType}/{file}", out var currentSpawnRate);
|
||||
newMaps.Add($"{floorType}/{file}", currentSpawnRate);
|
||||
}
|
||||
else
|
||||
newMaps.Add($"{floorType}/{file}", 1.0f);
|
||||
}
|
||||
|
||||
LayoutWithSpawnRate = newMaps;
|
||||
|
||||
NotifyPropertyListChanged();
|
||||
}
|
||||
|
||||
public enum DungeonFloorSetType
|
||||
{
|
||||
SetA,
|
||||
SetB
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://ci7o3nn4mdo8o
|
||||
@@ -1,22 +0,0 @@
|
||||
#if TOOLS
|
||||
using Godot;
|
||||
using Zennysoft.Game.Ma;
|
||||
|
||||
[Tool]
|
||||
public partial class DungeonFloorLayoutNode : EditorPlugin
|
||||
{
|
||||
public override void _EnterTree()
|
||||
{
|
||||
// Initialization of the plugin goes here.
|
||||
var script = GD.Load<Script>("res://addons/dungeon_floor_layout/DungeonFloorLayout.cs");
|
||||
var texture = GD.Load<Texture2D>("res://addons/dungeon_floor_layout/icon.png");
|
||||
AddCustomType(nameof(DungeonFloorLayout), nameof(LayoutType), script, texture);
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
// Clean-up of the plugin goes here.
|
||||
RemoveCustomType(nameof(DungeonFloorLayout));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1 +0,0 @@
|
||||
uid://f87ejxatyy2n
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 161 B |
@@ -1,34 +0,0 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cgd2d4fusp4pg"
|
||||
path="res://.godot/imported/icon.png-f075641bee242eff6dcbc69a1dabede8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dungeon_floor_layout/icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-f075641bee242eff6dcbc69a1dabede8.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=0
|
||||
@@ -1,7 +0,0 @@
|
||||
[plugin]
|
||||
|
||||
name="Dungeon Floor Layout"
|
||||
description=""
|
||||
author="Zenny"
|
||||
version=""
|
||||
script="DungeonFloorLayoutNode.cs"
|
||||
287
Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs
Normal file
287
Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace NathanHoad
|
||||
{
|
||||
public static class InputHelper
|
||||
{
|
||||
public delegate void DeviceChangedEventHandler(string device, int deviceIndex);
|
||||
public delegate void KeyboardInputChangedEventHandler(string action, InputEvent input);
|
||||
public delegate void JoypadInputChangedEventHandler(string action, InputEvent input);
|
||||
public delegate void JoypadChangedEventHandler(int deviceIndex, bool isConnected);
|
||||
|
||||
public static DeviceChangedEventHandler? DeviceChanged;
|
||||
public static KeyboardInputChangedEventHandler? KeyboardInputChanged;
|
||||
public static JoypadInputChangedEventHandler? JoypadInputChanged;
|
||||
public static JoypadChangedEventHandler? JoypadChanged;
|
||||
|
||||
|
||||
public const string DEVICE_KEYBOARD = "keyboard";
|
||||
public const string DEVICE_XBOX_CONTROLLER = "xbox";
|
||||
public const string DEVICE_SWITCH_CONTROLLER = "switch";
|
||||
public const string DEVICE_PLAYSTATION_CONTROLLER = "playstation";
|
||||
public const string DEVICE_STEAMDECK_CONTROLLER = "steamdeck";
|
||||
public const string DEVICE_GENERIC = "generic";
|
||||
|
||||
public const string SUB_DEVICE_XBOX_ONE_CONTROLLER = "xbox_one";
|
||||
public const string SUB_DEVICE_XBOX_SERIES_CONTROLLER = "xbox_series";
|
||||
public const string SUB_DEVICE_PLAYSTATION3_CONTROLLER = "playstation3";
|
||||
public const string SUB_DEVICE_PLAYSTATION4_CONTROLLER = "playstation4";
|
||||
public const string SUB_DEVICE_PLAYSTATION5_CONTROLLER = "playstation5";
|
||||
public const string SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "switch_left_joycon";
|
||||
public const string SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "switch_right_joycon";
|
||||
|
||||
|
||||
private static Node instance;
|
||||
public static Node Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (Node)Engine.GetSingleton("InputHelper");
|
||||
instance.Connect("device_changed", Callable.From((string device, int deviceIndex) => DeviceChanged?.Invoke(device, deviceIndex)));
|
||||
instance.Connect("keyboard_input_changed", Callable.From((string action, InputEvent input) => KeyboardInputChanged?.Invoke(action, input)));
|
||||
instance.Connect("joypad_input_changed", Callable.From((string action, InputEvent input) => JoypadInputChanged?.Invoke(action, input)));
|
||||
instance.Connect("joypad_changed", Callable.From((int deviceIndex, bool isConnected) => JoypadChanged?.Invoke(deviceIndex, isConnected)));
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string Device
|
||||
{
|
||||
get => (string)Instance.Get("device");
|
||||
}
|
||||
|
||||
|
||||
public static int DeviceIndex
|
||||
{
|
||||
get => (int)Instance.Get("device_index");
|
||||
}
|
||||
|
||||
|
||||
public static string LastKnownJoypadDevice
|
||||
{
|
||||
get => (string)Instance.Get("last_known_joypad_device");
|
||||
}
|
||||
|
||||
|
||||
public static string LastKnownJoypadIndex
|
||||
{
|
||||
get => (string)Instance.Get("last_known_joypad_index");
|
||||
}
|
||||
|
||||
|
||||
public static float Deadzone
|
||||
{
|
||||
get => (float)Instance.Get("deadzone");
|
||||
set => Instance.Set("deadzone", value);
|
||||
}
|
||||
|
||||
|
||||
public static int MouseMotionThreshold
|
||||
{
|
||||
get => (int)Instance.Get("mouse_motion_threshold");
|
||||
set => Instance.Set("mouse_motion_threshold", value);
|
||||
}
|
||||
|
||||
|
||||
public static string GetSimplifiedDeviceName(string rawName)
|
||||
{
|
||||
return (string)Instance.Call("get_simplified_device_name", rawName);
|
||||
}
|
||||
|
||||
|
||||
public static string GetDeviceFromEvent(InputEvent @event)
|
||||
{
|
||||
return (string)Instance.Call("get_device_from_event", @event);
|
||||
}
|
||||
|
||||
|
||||
public static int GetDeviceIndexFromEvent(InputEvent @event)
|
||||
{
|
||||
return (int)Instance.Call("get_device_index_from_event", @event);
|
||||
}
|
||||
|
||||
|
||||
public static bool HasJoypad()
|
||||
{
|
||||
return (bool)Instance.Call("has_joypad");
|
||||
}
|
||||
|
||||
|
||||
public static string GuessDeviceName()
|
||||
{
|
||||
return (string)Instance.Call("guess_device_name");
|
||||
}
|
||||
|
||||
|
||||
public static void ResetAllActions()
|
||||
{
|
||||
Instance.Call("reset_all_actions");
|
||||
}
|
||||
|
||||
|
||||
public static void SetKeyboardOrJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("set_keyboard_or_joypad_input_for_action", action, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static InputEvent GetKeyboardOrJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
return (InputEvent)Instance.Call("get_keyboard_or_joypad_input_for_action", action, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static Array<InputEvent> GetKeyboardOrJoypadInputsForAction(string action)
|
||||
{
|
||||
return (Array<InputEvent>)Instance.Call("get_keyboard_or_joypad_inputs_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static string GetLabelForInput(InputEvent input)
|
||||
{
|
||||
return (string)Instance.Call("get_label_for_input", input);
|
||||
}
|
||||
|
||||
|
||||
public static string SerializeInputsForAction(string action)
|
||||
{
|
||||
return (string)Instance.Call("serialize_inputs_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static string SerializeInputsForActions(Array<string> actions = null)
|
||||
{
|
||||
if (actions == null)
|
||||
{
|
||||
actions = new Array<string>();
|
||||
}
|
||||
return (string)Instance.Call("serialize_inputs_for_actions", actions);
|
||||
}
|
||||
|
||||
|
||||
public static void DeserializeInputsForAction(string action, string serializedInputs)
|
||||
{
|
||||
Instance.Call("desserialize_inputs_for_action", action, serializedInputs);
|
||||
}
|
||||
|
||||
public static void DeserializeInputsForActions(string serializedInputs)
|
||||
{
|
||||
Instance.Call("deserialize_inputs_for_actions", serializedInputs);
|
||||
}
|
||||
|
||||
|
||||
#region Keyboard/Mouse
|
||||
|
||||
public static Array<InputEvent> GetKeyboardInputsForAction(string action)
|
||||
{
|
||||
return (Array<InputEvent>)Instance.Call("get_keyboard_inputs_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static InputEvent GetKeyboardInputForAction(string action)
|
||||
{
|
||||
return (InputEvent)Instance.Call("get_keyboard_input_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static void SetKeyboardInputForAction(string action, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("set_keyboard_input_for_action", action, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static void ReplaceKeyboardInputForAction(string action, InputEvent currentInput, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("replace_keyboard_input_for_action", action, currentInput, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static void ReplaceKeyboardInputAtIndex(string action, int index, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("replace_keyboard_input_at_index", action, index, input, swapIfTaken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Joypad
|
||||
|
||||
public static Array<InputEvent> GetJoypadInputsForAction(string action)
|
||||
{
|
||||
return (Array<InputEvent>)Instance.Call("get_joypad_inputs_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static InputEvent GetJoypadInputForAction(string action)
|
||||
{
|
||||
return (InputEvent)Instance.Call("get_joypad_input_for_action", action);
|
||||
}
|
||||
|
||||
|
||||
public static void SetJoypadInputForAction(string action, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("set_joypad_input_for_action", action, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static void ReplaceJoypadInputForAction(string action, InputEvent currentInput, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("replace_joypad_input_for_action", action, currentInput, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static void ReplaceJoypadInputAtIndex(string action, int index, InputEvent input, bool swapIfTaken = true)
|
||||
{
|
||||
Instance.Call("replace_joypad_input_at_index", action, index, input, swapIfTaken);
|
||||
}
|
||||
|
||||
|
||||
public static void RumbleSmall(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("rumble_small", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void RumbleMedium(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("rumble_medium", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void RumbleLarge(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("rumble_large", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void StartRumbleSmall(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("start_rumble_small", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void StartRumbleMedium(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("start_rumble_medium", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void StartRumbleLarge(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("start_rumble_large", targetDevice);
|
||||
}
|
||||
|
||||
|
||||
public static void StopRumble(int targetDevice = 0)
|
||||
{
|
||||
Instance.Call("stop_rumble", targetDevice);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1
Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs.uid
Normal file
1
Zennysoft.Game.Ma/addons/input_helper/InputHelper.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfs4dgwrfvb11
|
||||
21
Zennysoft.Game.Ma/addons/input_helper/LICENSE
Normal file
21
Zennysoft.Game.Ma/addons/input_helper/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-present Nathan Hoad
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
230
Zennysoft.Game.Ma/addons/input_helper/assets/update.svg
Normal file
230
Zennysoft.Game.Ma/addons/input_helper/assets/update.svg
Normal file
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="300"
|
||||
height="80"
|
||||
viewBox="0 0 79.374999 21.166667"
|
||||
version="1.1"
|
||||
id="svg291"
|
||||
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||
sodipodi:docname="update.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview293"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
width="1920px"
|
||||
units="px"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="121.625"
|
||||
inkscape:cy="43.25"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="2552"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showguides="true">
|
||||
<sodipodi:guide
|
||||
position="-15.575132,19.553027"
|
||||
orientation="0,-1"
|
||||
id="guide2089"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs288" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g2335"
|
||||
transform="matrix(0.93072355,0,0,0.92874157,17.062816,18.409989)"
|
||||
style="stroke-width:0.85374062;stroke-dasharray:none;stroke:none">
|
||||
<path
|
||||
id="rect2095"
|
||||
style="fill:#349684;fill-opacity:1;stroke:#152f2c;stroke-width:0.56916041;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:normal"
|
||||
d="m 24.364404,-16.012976 a 1.7811118,1.7811118 0 0 0 -1.568379,0.938961 1.7811118,1.7811118 0 0 0 -1.557528,-0.918291 1.7811118,1.7811118 0 0 0 -1.383895,0.660942 c -1.816891,-1.243302 -4.115395,-0.49791 -5.162476,1.678967 l -2.844787,5.914368 c -1.050638,2.1842718 -0.431729,4.957652 1.388029,6.2187419 1.819758,1.26108987 4.130958,0.5177068 5.181596,-1.666565 l 1.240234,-2.5786539 h 6.335531 l 1.240234,2.5786539 c 1.050638,2.1842718 3.361322,2.92765487 5.181079,1.666565 1.819758,-1.2610899 2.439184,-4.0344701 1.388546,-6.2187419 L 30.9578,-13.652397 c -1.050637,-2.184271 -3.361838,-2.927654 -5.181596,-1.666565 -1.97e-4,1.37e-4 -3.19e-4,3.8e-4 -5.16e-4,5.17e-4 a 1.7811118,1.7811118 0 0 0 -1.411284,-0.694531 z" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect2101"
|
||||
width="1.403165"
|
||||
height="4.2796535"
|
||||
x="17.394386"
|
||||
y="-13.427068"
|
||||
ry="0.75541198"
|
||||
rx="0.75380331" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect2103"
|
||||
width="1.403165"
|
||||
height="4.2796535"
|
||||
x="-11.988823"
|
||||
y="-20.235798"
|
||||
ry="0.75380331"
|
||||
rx="0.75541198"
|
||||
transform="rotate(90)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse2105"
|
||||
cx="28.192503"
|
||||
cy="2.7587082"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse2107"
|
||||
cx="28.216581"
|
||||
cy="4.7943339"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse2109"
|
||||
cx="30.279696"
|
||||
cy="2.6987464"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse2111"
|
||||
cx="30.320311"
|
||||
cy="4.767447"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.85374062;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect2117"
|
||||
width="1.109892"
|
||||
height="2.1669323"
|
||||
x="22.090807"
|
||||
y="-10.531444"
|
||||
ry="0.52878839"
|
||||
rx="0.52766228" />
|
||||
</g>
|
||||
<path
|
||||
id="rect1625"
|
||||
style="fill:#50fa7b;fill-opacity:1;stroke:#0f451d;stroke-width:0.593381;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m 50.52517,5.3570989 c -0.643062,0 -1.160653,0.5900216 -1.160653,1.3229166 v 1.0764201 h -1.07642 c -0.732895,0 -1.322916,0.5175908 -1.322916,1.1606533 0,0.643061 0.590021,1.1611691 1.322916,1.1611691 h 1.07642 v 1.076421 c 0,0.732895 0.517591,1.322917 1.160653,1.322917 0.643062,0 1.160653,-0.590022 1.160653,-1.322917 v -1.076421 h 1.076937 c 0.732895,0 1.322916,-0.5181081 1.322916,-1.1611691 0,-0.6430625 -0.590021,-1.1606533 -1.322916,-1.1606533 H 51.685823 V 6.6800155 c 0,-0.732895 -0.517591,-1.3229166 -1.160653,-1.3229166 z" />
|
||||
<path
|
||||
id="path2678"
|
||||
style="fill:#50fa7b;fill-opacity:1;stroke:#0f451d;stroke-width:0.593381;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m 57.791691,5.357099 c -0.643062,0 -1.160653,0.5900216 -1.160653,1.3229166 v 1.0764201 h -1.07642 c -0.732895,0 -1.322916,0.5175908 -1.322916,1.1606522 0,0.643062 0.590021,1.1611701 1.322916,1.1611701 h 1.07642 v 1.076421 c 0,0.732895 0.517591,1.322917 1.160653,1.322917 0.643062,0 1.160653,-0.590022 1.160653,-1.322917 v -1.076421 h 1.076937 c 0.732895,0 1.322916,-0.5181081 1.322916,-1.1611701 0,-0.6430614 -0.590021,-1.1606522 -1.322916,-1.1606522 H 58.952344 V 6.6800156 c 0,-0.732895 -0.517591,-1.3229166 -1.160653,-1.3229166 z" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.72829;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
|
||||
id="rect413"
|
||||
width="10.200269"
|
||||
height="8.7847834"
|
||||
x="-21.316095"
|
||||
y="3.0372066" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.622475;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
|
||||
id="rect415"
|
||||
width="7.9928446"
|
||||
height="15.312933"
|
||||
x="-20.546837"
|
||||
y="12.977094"
|
||||
ry="4.375123"
|
||||
transform="matrix(0.82192626,0.56959391,-0.43346431,0.90117073,0,0)"
|
||||
rx="4.375123" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.622475;stroke-linecap:round;stroke-linejoin:round;stroke-dashoffset:0.3;paint-order:markers stroke fill"
|
||||
id="rect471"
|
||||
width="7.9928446"
|
||||
height="15.312933"
|
||||
x="9.1578636"
|
||||
y="-5.7980561"
|
||||
ry="4.375123"
|
||||
transform="matrix(-0.82192626,0.56959391,0.43346431,0.90117073,0,0)"
|
||||
rx="4.375123" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect1137"
|
||||
width="1.403165"
|
||||
height="4.2796535"
|
||||
x="-22.240166"
|
||||
y="3.9990206"
|
||||
ry="0.70158249"
|
||||
rx="0.70158249" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect1295"
|
||||
width="1.403165"
|
||||
height="4.2796535"
|
||||
x="5.4372649"
|
||||
y="19.398754"
|
||||
ry="0.70158249"
|
||||
rx="0.70158249"
|
||||
transform="rotate(90)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse1297"
|
||||
cx="-13.849753"
|
||||
cy="-1.3102485"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse1299"
|
||||
cx="-13.825675"
|
||||
cy="0.72537726"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse1301"
|
||||
cx="-11.762562"
|
||||
cy="-1.3702103"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<ellipse
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="ellipse1303"
|
||||
cx="-11.721946"
|
||||
cy="0.69849032"
|
||||
rx="0.82257289"
|
||||
ry="0.79261416"
|
||||
transform="rotate(-30)" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="path2085"
|
||||
cx="-17.863199"
|
||||
cy="3.3752983"
|
||||
r="1.7811118" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="circle2087"
|
||||
cx="-14.737003"
|
||||
cy="3.3542202"
|
||||
r="1.7811118" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0.3;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
id="rect2091"
|
||||
width="0.98221546"
|
||||
height="1.917659"
|
||||
x="-16.946886"
|
||||
y="7.1795278"
|
||||
ry="0.49110773"
|
||||
rx="0.49110773" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -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"]
|
||||
601
Zennysoft.Game.Ma/addons/input_helper/input_helper.gd
Normal file
601
Zennysoft.Game.Ma/addons/input_helper/input_helper.gd
Normal file
@@ -0,0 +1,601 @@
|
||||
extends Node
|
||||
|
||||
|
||||
signal device_changed(device: String, device_index: int)
|
||||
signal keyboard_input_changed(action: String, input: InputEvent)
|
||||
signal joypad_input_changed(action: String, input: InputEvent)
|
||||
signal joypad_changed(device_index: int, is_connected: bool)
|
||||
|
||||
|
||||
const DEVICE_KEYBOARD = "keyboard"
|
||||
const DEVICE_XBOX_CONTROLLER = "xbox"
|
||||
const DEVICE_SWITCH_CONTROLLER = "switch"
|
||||
const DEVICE_PLAYSTATION_CONTROLLER = "playstation"
|
||||
const DEVICE_STEAMDECK_CONTROLLER = "steamdeck"
|
||||
const DEVICE_GENERIC = "generic"
|
||||
|
||||
const SUB_DEVICE_XBOX_ONE_CONTROLLER = "xbox_one"
|
||||
const SUB_DEVICE_XBOX_SERIES_CONTROLLER = "xbox_series"
|
||||
|
||||
const SUB_DEVICE_PLAYSTATION3_CONTROLLER = "playstation3"
|
||||
const SUB_DEVICE_PLAYSTATION4_CONTROLLER = "playstation4"
|
||||
const SUB_DEVICE_PLAYSTATION5_CONTROLLER = "playstation5"
|
||||
|
||||
const SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "switch_left_joycon"
|
||||
const SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "switch_right_joycon"
|
||||
|
||||
const XBOX_BUTTON_LABELS = ["A", "B", "X", "Y", "Back", "Guide", "Start", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"]
|
||||
const XBOX_ONE_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "Guide", "Menu", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"]
|
||||
const XBOX_SERIES_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "Guide", "Menu", "Left Stick", "Right Stick", "LB", "RB", "Up", "Down", "Left", "Right", "Share", "Paddle 1", "Paddle 2", "Paddle 3", "Paddle 4"]
|
||||
const STEAMDECK_BUTTON_LABELS = ["A", "B", "X", "Y", "View", "?", "Options", "Left Stick", "Right Stick", "L1", "R1", "Up", "Down", "Left", "Right", "", "", "", "", ""]
|
||||
# Note: share and home buttons are not recognized
|
||||
const SWITCH_BUTTON_LABELS = ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick", "Right Stick", "LS", "RS", "Up", "Down", "Left", "Right", "Capture"]
|
||||
# Mapping for left and right joypad connected together (extended gamepad)
|
||||
# Left Stick is Axis 0 and 1
|
||||
# Right Stick is Axis 2 and 3
|
||||
# ZL and ZR are Axis 4 and 5
|
||||
const SWITCH_EXTENDED_GAMEPAD_BUTTON_LABELS = ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick", "Right Stick", "L", "R", "Up", "Down", "Left", "Right", "Capture"]
|
||||
const PLAYSTATION_3_4_BUTTON_LABELS = ["Cross", "Circle", "Square", "Triangle", "Share", "PS", "Options", "L3", "R3", "L1", "R1", "Up", "Down", "Left", "Right", "Microphone", "", "", "", "", "Touchpad"]
|
||||
# Note: Microphone does not work on PC / touchpad is not recognized
|
||||
const PLAYSTATION_5_BUTTON_LABELS = ["Cross", "Circle", "Square", "Triangle", "Create", "PS", "Options", "L3", "R3", "L1", "R1", "Up", "Down", "Left", "Right", "Microphone", "", "", "", "", "Touchpad"]
|
||||
|
||||
const SERIAL_VERSION = 1
|
||||
|
||||
## The deadzone to ignore for joypad motion
|
||||
var deadzone: float = 0.5
|
||||
## The mouse distance to ignore before movement is assumed
|
||||
var mouse_motion_threshold: int = 100
|
||||
## The last known joypad device name (or "" if no joypad detected)
|
||||
var last_known_joypad_device: String = get_simplified_device_name(Input.get_joy_name(0))
|
||||
## The last known joypad index
|
||||
var last_known_joypad_index: int = 0 if Input.get_connected_joypads().size() > 0 else -1
|
||||
|
||||
## Used internally
|
||||
var device_last_changed_at: int = 0
|
||||
var _last_known_granular_joypad_device: String = get_simplified_device_name(Input.get_joy_name(0), true)
|
||||
|
||||
@onready var device: String = guess_device_name()
|
||||
@onready var device_index: int = 0 if has_joypad() else -1
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
|
||||
if not Engine.has_singleton("InputHelper"):
|
||||
Engine.register_singleton("InputHelper", self)
|
||||
|
||||
Input.joy_connection_changed.connect(func(device_index, is_connected): joypad_changed.emit(device_index, is_connected))
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
var next_device: String = device
|
||||
var next_device_index: int = device_index
|
||||
|
||||
# Did we just press a key on the keyboard or move the mouse?
|
||||
if event is InputEventKey \
|
||||
or event is InputEventMouseButton \
|
||||
or (event is InputEventMouseMotion and (event as InputEventMouseMotion).relative.length_squared() > mouse_motion_threshold):
|
||||
next_device = DEVICE_KEYBOARD
|
||||
next_device_index = -1
|
||||
|
||||
# Did we just use a joypad?
|
||||
elif event is InputEventJoypadButton \
|
||||
or (event is InputEventJoypadMotion and abs(event.axis_value) > deadzone):
|
||||
next_device = get_simplified_device_name(get_joy_name(event.device))
|
||||
last_known_joypad_device = next_device
|
||||
next_device_index = event.device
|
||||
last_known_joypad_index = next_device_index
|
||||
|
||||
_last_known_granular_joypad_device = get_simplified_device_name(get_joy_name(event.device), true)
|
||||
|
||||
# Debounce changes for 1 second because some joypads register twice in Windows for some reason
|
||||
var not_changed_in_last_second = Engine.get_frames_drawn() - device_last_changed_at > Engine.get_frames_per_second()
|
||||
if (next_device != device or next_device_index != device_index) and not_changed_in_last_second:
|
||||
device_last_changed_at = Engine.get_frames_drawn()
|
||||
|
||||
device = next_device
|
||||
device_index = next_device_index
|
||||
device_changed.emit(device, device_index)
|
||||
|
||||
|
||||
## Get the name of a joypad
|
||||
func get_joy_name(at_device_index: int) -> String:
|
||||
var joy_name: String = Input.get_joy_name(at_device_index)
|
||||
if joy_name == "" and Input.get_joy_info(at_device_index).size() > 0 and "xinput" in Input.get_joy_info(at_device_index).keys()[0]:
|
||||
joy_name = "XInput"
|
||||
return joy_name
|
||||
|
||||
|
||||
## Get the device name for an event
|
||||
func get_device_from_event(event: InputEvent) -> String:
|
||||
if event is InputEventKey or event is InputEventMouseButton or event is InputEventMouseMotion:
|
||||
return DEVICE_KEYBOARD
|
||||
elif event is InputEventJoypadButton or event is InputEventJoypadMotion:
|
||||
return get_simplified_device_name(get_joy_name(event.device))
|
||||
else:
|
||||
return DEVICE_GENERIC
|
||||
|
||||
|
||||
## Get the device name for an event
|
||||
func get_device_index_from_event(event: InputEvent) -> int:
|
||||
if event is InputEventJoypadButton or event is InputEventJoypadMotion:
|
||||
return event.device
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
## Convert a Godot device identifier to a simplified string
|
||||
func get_simplified_device_name(raw_name: String, force_granular_identifier: bool = false) -> String:
|
||||
var use_granular_identifier: bool = force_granular_identifier or InputHelperSettings.get_setting(InputHelperSettings.USE_GRANULAR_DEVICE_IDENTIFIERS, false)
|
||||
|
||||
var keywords: Dictionary = {
|
||||
SUB_DEVICE_XBOX_ONE_CONTROLLER: ["Xbox One Controller"],
|
||||
SUB_DEVICE_XBOX_SERIES_CONTROLLER: ["Xbox Series Controller", "Xbox Wireless Controller"],
|
||||
DEVICE_XBOX_CONTROLLER: ["XInput", "XBox"],
|
||||
SUB_DEVICE_PLAYSTATION3_CONTROLLER: ["PS3"],
|
||||
SUB_DEVICE_PLAYSTATION4_CONTROLLER:["Nacon Revolution Unlimited Pro Controller", "PS4", "DUALSHOCK 4"],
|
||||
SUB_DEVICE_PLAYSTATION5_CONTROLLER:["Sony DualSense", "PS5", "DualSense Wireless Controller"],
|
||||
DEVICE_STEAMDECK_CONTROLLER: ["Steam"],
|
||||
DEVICE_SWITCH_CONTROLLER: ["Switch", "Joy-Con (L/R)", "PowerA Core Controller"],
|
||||
SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER: ["Joy-Con (L)"],
|
||||
SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER: ["joy-Con (R)"],
|
||||
} if use_granular_identifier else {
|
||||
DEVICE_XBOX_CONTROLLER: ["XBox", "XInput"],
|
||||
DEVICE_PLAYSTATION_CONTROLLER: ["Sony", "PS3", "PS5", "PS4", "DUALSHOCK 4", "DualSense", "Nacon Revolution Unlimited Pro Controller"],
|
||||
DEVICE_STEAMDECK_CONTROLLER: ["Steam"],
|
||||
DEVICE_SWITCH_CONTROLLER: ["Switch", "Joy-Con", "PowerA Core Controller"],
|
||||
}
|
||||
|
||||
for device_key in keywords:
|
||||
for keyword in keywords[device_key]:
|
||||
if keyword.to_lower() in raw_name.to_lower():
|
||||
return device_key
|
||||
|
||||
return DEVICE_GENERIC
|
||||
|
||||
|
||||
## Check if there is a connected joypad
|
||||
func has_joypad() -> bool:
|
||||
return Input.get_connected_joypads().size() > 0
|
||||
|
||||
|
||||
## Guess the initial input device
|
||||
func guess_device_name() -> String:
|
||||
if has_joypad():
|
||||
return get_simplified_device_name(get_joy_name(0))
|
||||
else:
|
||||
return DEVICE_KEYBOARD
|
||||
|
||||
|
||||
#region Mapping
|
||||
|
||||
|
||||
func reset_all_actions() -> void:
|
||||
InputMap.load_from_project_settings()
|
||||
for action in InputMap.get_actions():
|
||||
var input: InputEvent = get_joypad_input_for_action(action)
|
||||
if input != null:
|
||||
joypad_input_changed.emit(action, input)
|
||||
|
||||
input = get_keyboard_input_for_action(action)
|
||||
if input != null:
|
||||
keyboard_input_changed.emit(action, input)
|
||||
|
||||
|
||||
## Set the key or button for an action
|
||||
func set_keyboard_or_joypad_input_for_action(action: String, event: InputEvent, swap_if_taken: bool = true) -> void:
|
||||
if event is InputEventKey or event is InputEventMouse:
|
||||
set_keyboard_input_for_action(action, event, swap_if_taken)
|
||||
elif event is InputEventJoypadButton:
|
||||
set_joypad_input_for_action(action, event, swap_if_taken)
|
||||
|
||||
|
||||
## Get the key or button for a given action depending on the current device
|
||||
func get_keyboard_or_joypad_input_for_action(action: String) -> InputEvent:
|
||||
if device == DEVICE_KEYBOARD:
|
||||
return get_keyboard_input_for_action(action)
|
||||
else:
|
||||
return get_joypad_input_for_action(action)
|
||||
|
||||
|
||||
## Get the key or button for a given action depending on the current device
|
||||
func get_keyboard_or_joypad_inputs_for_action(action: String) -> Array[InputEvent]:
|
||||
if device == DEVICE_KEYBOARD:
|
||||
return get_keyboard_inputs_for_action(action)
|
||||
else:
|
||||
return get_joypad_inputs_for_action(action)
|
||||
|
||||
|
||||
## Get a text label for a given input
|
||||
func get_label_for_input(input: InputEvent) -> String:
|
||||
if input == null: return ""
|
||||
|
||||
if input is InputEventKey:
|
||||
if input.physical_keycode > 0 :
|
||||
var keycode: Key = DisplayServer.keyboard_get_keycode_from_physical(input.physical_keycode) if DisplayServer.keyboard_get_current_layout() > -1 else input.physical_keycode
|
||||
return OS.get_keycode_string(keycode)
|
||||
elif input.keycode > 0:
|
||||
return OS.get_keycode_string(input.keycode)
|
||||
else:
|
||||
return input.as_text()
|
||||
|
||||
elif input is InputEventMouseButton:
|
||||
match input.button_index:
|
||||
MOUSE_BUTTON_LEFT:
|
||||
return "Mouse Left Button"
|
||||
MOUSE_BUTTON_MIDDLE:
|
||||
return "Mouse Middle Button"
|
||||
MOUSE_BUTTON_RIGHT:
|
||||
return "Mouse Right Button"
|
||||
return "Mouse Button %d" % input.button_index
|
||||
|
||||
elif input is InputEventJoypadButton:
|
||||
var labels = []
|
||||
match _last_known_granular_joypad_device:
|
||||
DEVICE_XBOX_CONTROLLER, DEVICE_GENERIC:
|
||||
labels = XBOX_BUTTON_LABELS
|
||||
SUB_DEVICE_XBOX_ONE_CONTROLLER:
|
||||
labels = XBOX_ONE_BUTTON_LABELS
|
||||
SUB_DEVICE_XBOX_SERIES_CONTROLLER:
|
||||
labels = XBOX_SERIES_BUTTON_LABELS
|
||||
SUB_DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER, SUB_DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER:
|
||||
labels = SWITCH_BUTTON_LABELS
|
||||
DEVICE_SWITCH_CONTROLLER:
|
||||
labels = SWITCH_EXTENDED_GAMEPAD_BUTTON_LABELS
|
||||
SUB_DEVICE_PLAYSTATION3_CONTROLLER, SUB_DEVICE_PLAYSTATION4_CONTROLLER:
|
||||
labels = PLAYSTATION_3_4_BUTTON_LABELS
|
||||
DEVICE_PLAYSTATION_CONTROLLER, SUB_DEVICE_PLAYSTATION5_CONTROLLER:
|
||||
labels = PLAYSTATION_5_BUTTON_LABELS
|
||||
DEVICE_STEAMDECK_CONTROLLER:
|
||||
labels = STEAMDECK_BUTTON_LABELS
|
||||
if input.button_index < labels.size():
|
||||
return "%s Button" % labels[input.button_index]
|
||||
else:
|
||||
return "Button %d" % input.button_index
|
||||
|
||||
elif input is InputEventJoypadMotion:
|
||||
var motion: InputEventJoypadMotion = input as InputEventJoypadMotion
|
||||
match motion.axis:
|
||||
JOY_AXIS_LEFT_X:
|
||||
return "Left Stick %s" % ("Left" if motion.axis_value < 0 else "Right")
|
||||
JOY_AXIS_LEFT_Y:
|
||||
return "Left Stick %s" % ("Up" if motion.axis_value < 0 else "Down")
|
||||
JOY_AXIS_RIGHT_X:
|
||||
return "Right Stick %s" % ("Left" if motion.axis_value < 0 else "Right")
|
||||
JOY_AXIS_RIGHT_Y:
|
||||
return "Right Stick %s" % ("Up" if motion.axis_value < 0 else "Down")
|
||||
JOY_AXIS_TRIGGER_LEFT:
|
||||
return "Left Trigger"
|
||||
JOY_AXIS_TRIGGER_RIGHT:
|
||||
return "Right Trigger"
|
||||
|
||||
return input.as_text()
|
||||
|
||||
|
||||
## Serialize a single action's inputs.
|
||||
func serialize_inputs_for_action(action: StringName) -> String:
|
||||
var action_inputs: PackedStringArray = []
|
||||
var inputs: Array[InputEvent] = InputMap.action_get_events(action)
|
||||
for input in inputs:
|
||||
if input is InputEventKey:
|
||||
var s: String = get_label_for_input(input)
|
||||
var modifiers: Array[String] = []
|
||||
if input.alt_pressed:
|
||||
modifiers.append("alt")
|
||||
if input.shift_pressed:
|
||||
modifiers.append("shift")
|
||||
if input.ctrl_pressed:
|
||||
modifiers.append("ctrl")
|
||||
if input.meta_pressed:
|
||||
modifiers.append("meta")
|
||||
if not modifiers.is_empty():
|
||||
s += "|" + ",".join(modifiers)
|
||||
action_inputs.append("key:%s" % s)
|
||||
elif input is InputEventMouseButton:
|
||||
action_inputs.append("mouse:%d" % input.button_index)
|
||||
elif input is InputEventJoypadButton:
|
||||
action_inputs.append("joypad:%d" % input.button_index)
|
||||
elif input is InputEventJoypadMotion:
|
||||
action_inputs.append("joypad:%d|%f" % [input.axis, input.axis_value])
|
||||
|
||||
return ";".join(action_inputs)
|
||||
|
||||
|
||||
## Serialize a list of action inputs to string. If actions is empty then it will serialize
|
||||
## all actions.
|
||||
func serialize_inputs_for_actions(actions: PackedStringArray = []) -> String:
|
||||
if actions == null or actions.is_empty():
|
||||
actions = InputMap.get_actions()
|
||||
|
||||
var map: Dictionary = {}
|
||||
for action in actions:
|
||||
map[action] = serialize_inputs_for_action(action)
|
||||
|
||||
return JSON.stringify({
|
||||
version = SERIAL_VERSION,
|
||||
map = map
|
||||
})
|
||||
|
||||
|
||||
## Deserialize a single action's inputs.
|
||||
func deserialize_inputs_for_action(action: String, string: String) -> void:
|
||||
InputMap.action_erase_events(action)
|
||||
var action_inputs: PackedStringArray = string.split(";")
|
||||
for action_input in action_inputs:
|
||||
var bits: PackedStringArray = action_input.split(":")
|
||||
|
||||
# Ignore any empty actions
|
||||
if bits.size() < 2: continue
|
||||
|
||||
var input_type: String = bits[0]
|
||||
var input_details: String = bits[1]
|
||||
|
||||
match input_type:
|
||||
"key":
|
||||
var keyboard_input = InputEventKey.new()
|
||||
if "|" in input_details:
|
||||
var detail_bits = input_details.split("|")
|
||||
keyboard_input.keycode = OS.find_keycode_from_string(detail_bits[0])
|
||||
detail_bits = detail_bits[1].split(",")
|
||||
if detail_bits.has("alt"):
|
||||
keyboard_input.alt_pressed = true
|
||||
if detail_bits.has("shift"):
|
||||
keyboard_input.shift_pressed = true
|
||||
if detail_bits.has("ctrl"):
|
||||
keyboard_input.ctrl_pressed = true
|
||||
if detail_bits.has("meta"):
|
||||
keyboard_input.meta_pressed = true
|
||||
else:
|
||||
keyboard_input.keycode = OS.find_keycode_from_string(input_details)
|
||||
InputMap.action_add_event(action, keyboard_input)
|
||||
keyboard_input_changed.emit(action, keyboard_input)
|
||||
|
||||
"mouse":
|
||||
var mouse_input = InputEventMouseButton.new()
|
||||
mouse_input.button_index = int(input_details)
|
||||
InputMap.action_add_event(action, mouse_input)
|
||||
keyboard_input_changed.emit(action, mouse_input)
|
||||
|
||||
"joypad":
|
||||
if "|" in str(input_details):
|
||||
var joypad_motion_input = InputEventJoypadMotion.new()
|
||||
var joypad_bits = input_details.split("|")
|
||||
joypad_motion_input.axis = int(joypad_bits[0])
|
||||
joypad_motion_input.axis_value = float(joypad_bits[1])
|
||||
InputMap.action_add_event(action, joypad_motion_input)
|
||||
joypad_input_changed.emit(action, joypad_motion_input)
|
||||
else:
|
||||
var joypad_input = InputEventJoypadButton.new()
|
||||
joypad_input.button_index = int(input_details)
|
||||
InputMap.action_add_event(action, joypad_input)
|
||||
joypad_input_changed.emit(action, joypad_input)
|
||||
|
||||
|
||||
## Deserialise a list of actions' inputs.
|
||||
func deserialize_inputs_for_actions(string: String) -> void:
|
||||
var data: Dictionary = JSON.parse_string(string)
|
||||
|
||||
# Use legacy deserialization
|
||||
if not data.has("version"):
|
||||
_deprecated_deserialize_inputs_for_actions(string)
|
||||
return
|
||||
|
||||
# Version 1
|
||||
for action in data.map.keys():
|
||||
deserialize_inputs_for_action(action, data.map[action])
|
||||
|
||||
|
||||
# Load inputs from a serialized string. [deprecated]
|
||||
func _deprecated_deserialize_inputs_for_actions(string: String) -> void:
|
||||
var map: Dictionary = JSON.parse_string(string)
|
||||
for action in map.keys():
|
||||
InputMap.action_erase_events(action)
|
||||
|
||||
for key in map[action]["keyboard"]:
|
||||
var keyboard_input = InputEventKey.new()
|
||||
if "|" in key:
|
||||
var bits = key.split("|")
|
||||
keyboard_input.keycode = OS.find_keycode_from_string(bits[0])
|
||||
bits = bits[1].split(",")
|
||||
if bits.has("alt"):
|
||||
keyboard_input.alt_pressed = true
|
||||
if bits.has("shift"):
|
||||
keyboard_input.shift_pressed = true
|
||||
if bits.has("ctrl"):
|
||||
keyboard_input.ctrl_pressed = true
|
||||
if bits.has("meta"):
|
||||
keyboard_input.meta_pressed = true
|
||||
else:
|
||||
keyboard_input.keycode = OS.find_keycode_from_string(key)
|
||||
InputMap.action_add_event(action, keyboard_input)
|
||||
|
||||
for button_index in map[action]["mouse"]:
|
||||
var mouse_input = InputEventMouseButton.new()
|
||||
mouse_input.button_index = int(button_index)
|
||||
InputMap.action_add_event(action, mouse_input)
|
||||
|
||||
for button_index_or_motion in map[action]["joypad"]:
|
||||
if "|" in str(button_index_or_motion):
|
||||
var joypad_motion_input = InputEventJoypadMotion.new()
|
||||
var bits = button_index_or_motion.split("|")
|
||||
joypad_motion_input.axis = int(bits[0])
|
||||
joypad_motion_input.axis_value = float(bits[1])
|
||||
InputMap.action_add_event(action, joypad_motion_input)
|
||||
else:
|
||||
var joypad_input = InputEventJoypadButton.new()
|
||||
joypad_input.button_index = int(button_index_or_motion)
|
||||
InputMap.action_add_event(action, joypad_input)
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyboard/mouse input
|
||||
|
||||
|
||||
## Get all of the keys/mouse buttons used for an action.
|
||||
func get_keyboard_inputs_for_action(action: String) -> Array[InputEvent]:
|
||||
return InputMap.action_get_events(action).filter(func(event):
|
||||
return event is InputEventKey or event is InputEventMouseButton
|
||||
)
|
||||
|
||||
|
||||
## Get the first key for an action
|
||||
func get_keyboard_input_for_action(action: String) -> InputEvent:
|
||||
var inputs: Array[InputEvent] = get_keyboard_inputs_for_action(action)
|
||||
return null if inputs.is_empty() else inputs[0]
|
||||
|
||||
|
||||
## Set the key used for an action
|
||||
func set_keyboard_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true) -> Error:
|
||||
return _update_keyboard_input_for_action(action, input, swap_if_taken, null)
|
||||
|
||||
|
||||
## Replace a specific key with another key
|
||||
func replace_keyboard_input_for_action(action: String, current_input: InputEvent, input: InputEvent, swap_if_taken: bool = true) -> Error:
|
||||
return _update_keyboard_input_for_action(action, input, swap_if_taken, current_input)
|
||||
|
||||
|
||||
## Replace a specific key, given its index
|
||||
func replace_keyboard_input_at_index(action: String, index: int, input: InputEvent, swap_if_taken: bool = true) -> Error:
|
||||
var inputs: Array[InputEvent] = get_keyboard_inputs_for_action(action)
|
||||
var replacing_input = InputEventKey.new() if (inputs.is_empty() or inputs.size() <= index) else inputs[index]
|
||||
return _update_keyboard_input_for_action(action, input, swap_if_taken, replacing_input)
|
||||
|
||||
|
||||
func _update_keyboard_input_for_action(action: String, input: InputEvent, swap_if_taken: bool, replacing_input: InputEvent = null) -> Error:
|
||||
if not (input is InputEventKey or input is InputEventMouseButton): return ERR_INVALID_DATA
|
||||
|
||||
var is_valid_keyboard_event = func(event):
|
||||
return event is InputEventKey or event is InputEventMouseButton
|
||||
|
||||
return _update_input_for_action(action, input, swap_if_taken, replacing_input, is_valid_keyboard_event, keyboard_input_changed)
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Joypad input
|
||||
|
||||
|
||||
## Get all buttons used for an action
|
||||
func get_joypad_inputs_for_action(action: String) -> Array[InputEvent]:
|
||||
return InputMap.action_get_events(action).filter(func(event):
|
||||
return event is InputEventJoypadButton or event is InputEventJoypadMotion
|
||||
)
|
||||
|
||||
|
||||
## Get the first button for an action
|
||||
func get_joypad_input_for_action(action: String) -> InputEvent:
|
||||
var buttons: Array[InputEvent] = get_joypad_inputs_for_action(action)
|
||||
return null if buttons.is_empty() else buttons[0]
|
||||
|
||||
|
||||
## Set the button for an action
|
||||
func set_joypad_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true) -> Error:
|
||||
return _update_joypad_input_for_action(action, input, swap_if_taken, null)
|
||||
|
||||
|
||||
## Replace a specific button for an action
|
||||
func replace_joypad_input_for_action(action: String, current_input: InputEvent, input: InputEventJoypadButton, swap_if_taken: bool = true) -> Error:
|
||||
return _update_joypad_input_for_action(action, input, swap_if_taken, current_input)
|
||||
|
||||
|
||||
## Replace a button, given its index
|
||||
func replace_joypad_input_at_index(action: String, index: int, input: InputEvent, swap_if_taken: bool = true) -> Error:
|
||||
var inputs: Array[InputEvent] = get_joypad_inputs_for_action(action)
|
||||
var replacing_input
|
||||
if inputs.is_empty() or inputs.size() <= index:
|
||||
replacing_input = InputEventJoypadButton.new()
|
||||
replacing_input.button_index = JOY_BUTTON_INVALID
|
||||
else:
|
||||
replacing_input = inputs[index]
|
||||
return _update_joypad_input_for_action(action, input, swap_if_taken, replacing_input)
|
||||
|
||||
|
||||
## Set the action used for a button
|
||||
func _update_joypad_input_for_action(action: String, input: InputEvent, swap_if_taken: bool = true, replacing_input: InputEvent = null) -> Error:
|
||||
var is_valid_keyboard_event = func(event):
|
||||
return event is InputEventJoypadButton or event is InputEventJoypadMotion
|
||||
|
||||
return _update_input_for_action(action, input, swap_if_taken, replacing_input, is_valid_keyboard_event, joypad_input_changed)
|
||||
|
||||
|
||||
func _update_input_for_action(action: String, input: InputEvent, swap_if_taken: bool, replacing_input: InputEvent, check_is_valid: Callable, did_change_signal: Signal) -> Error:
|
||||
# Find any action that is already mapped to this input
|
||||
var clashing_action = ""
|
||||
var clashing_event
|
||||
if swap_if_taken:
|
||||
for other_action in InputMap.get_actions():
|
||||
if other_action == action: continue
|
||||
|
||||
for event in InputMap.action_get_events(other_action):
|
||||
if event.is_match(input):
|
||||
clashing_action = other_action
|
||||
clashing_event = event
|
||||
|
||||
# Find the key based event for the target action
|
||||
var action_events: Array[InputEvent] = InputMap.action_get_events(action)
|
||||
var is_replacing: bool = false
|
||||
for i in range(0, action_events.size()):
|
||||
var event: InputEvent = action_events[i]
|
||||
if check_is_valid.call(event):
|
||||
if replacing_input != null and not event.is_match(replacing_input):
|
||||
continue
|
||||
|
||||
# Remap the other event if there is a clashing one
|
||||
if clashing_action:
|
||||
_update_input_for_action(clashing_action, event, false, clashing_event, check_is_valid, did_change_signal)
|
||||
|
||||
# Replace the event
|
||||
action_events[i] = input
|
||||
is_replacing = true
|
||||
break
|
||||
|
||||
# If we were trying to replace something but didn't find it then just add it to the end
|
||||
if not is_replacing:
|
||||
action_events.append(input)
|
||||
|
||||
# Apply the changes
|
||||
InputMap.action_erase_events(action)
|
||||
for event in action_events:
|
||||
if event != null:
|
||||
InputMap.action_add_event(action, event)
|
||||
|
||||
did_change_signal.emit(action, input)
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rumbling
|
||||
|
||||
|
||||
func rumble_small(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0.4, 0, 0.1)
|
||||
|
||||
|
||||
func rumble_medium(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0, 0.7, 0.1)
|
||||
|
||||
|
||||
func rumble_large(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0, 1, 0.1)
|
||||
|
||||
|
||||
func start_rumble_small(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0.4, 0, 0)
|
||||
|
||||
|
||||
func start_rumble_medium(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0, 0.7, 0)
|
||||
|
||||
|
||||
func start_rumble_large(target_device: int = 0) -> void:
|
||||
Input.start_joy_vibration(target_device, 0, 1, 0)
|
||||
|
||||
|
||||
func stop_rumble(target_device: int = 0) -> void:
|
||||
Input.stop_joy_vibration(target_device)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://cholww48njaeh
|
||||
7
Zennysoft.Game.Ma/addons/input_helper/plugin.cfg
Normal file
7
Zennysoft.Game.Ma/addons/input_helper/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Input Helper"
|
||||
description="Detect which input device the player is using and manage input actions"
|
||||
author="Nathan Hoad"
|
||||
version="4.7.0"
|
||||
script="plugin.gd"
|
||||
112
Zennysoft.Game.Ma/addons/input_helper/plugin.gd
Normal file
112
Zennysoft.Game.Ma/addons/input_helper/plugin.gd
Normal file
@@ -0,0 +1,112 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_input_helper/releases"
|
||||
const LOCAL_CONFIG_PATH = "res://addons/input_helper/plugin.cfg"
|
||||
|
||||
const DownloadDialogScene = preload("res://addons/input_helper/views/download_dialog.tscn")
|
||||
|
||||
|
||||
var http_request: HTTPRequest = HTTPRequest.new()
|
||||
var next_version: String = ""
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
add_autoload_singleton("InputHelper", "res://addons/input_helper/input_helper.gd")
|
||||
|
||||
# Configure settings
|
||||
InputHelperSettings.prepare()
|
||||
|
||||
# Check for updates on GitHub
|
||||
get_editor_interface().get_base_control().add_child(http_request)
|
||||
http_request.request_completed.connect(_on_http_request_request_completed)
|
||||
http_request.request(REMOTE_RELEASES_URL)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_autoload_singleton("InputHelper")
|
||||
|
||||
if next_version != "":
|
||||
remove_tool_menu_item("Update Input Helper to v%s" % next_version)
|
||||
|
||||
|
||||
# Get the current version
|
||||
func get_version() -> String:
|
||||
var config: ConfigFile = ConfigFile.new()
|
||||
config.load(LOCAL_CONFIG_PATH)
|
||||
return config.get_value("plugin", "version")
|
||||
|
||||
|
||||
# Convert a version number to an actually comparable number
|
||||
func version_to_number(version: String) -> int:
|
||||
var bits = version.split(".")
|
||||
return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int()
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
|
||||
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
http_request.queue_free()
|
||||
|
||||
if result != HTTPRequest.RESULT_SUCCESS: return
|
||||
|
||||
var current_version: String = get_version()
|
||||
|
||||
# Work out the next version from the releases information on GitHub
|
||||
var response = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(response) != TYPE_ARRAY: return
|
||||
|
||||
# GitHub releases are in order of creation, not order of version
|
||||
var versions = (response as Array).filter(func(release):
|
||||
var version: String = release.tag_name.substr(1)
|
||||
return version_to_number(version) > version_to_number(current_version)
|
||||
)
|
||||
if versions.size() > 0:
|
||||
next_version = versions[0].tag_name.substr(1)
|
||||
add_tool_menu_item("Update Input Helper to v%s" % next_version, _update_input_helper)
|
||||
|
||||
|
||||
func _update_input_helper() -> void:
|
||||
var download_dialog := DownloadDialogScene.instantiate()
|
||||
download_dialog.next_version = next_version
|
||||
|
||||
var scale: float = get_editor_interface().get_editor_scale()
|
||||
download_dialog.min_size = Vector2(300, 250) * scale
|
||||
|
||||
download_dialog.update_finished.connect(_on_download_dialog_update_finished)
|
||||
download_dialog.update_failed.connect(_on_download_dialog_update_failed)
|
||||
|
||||
get_editor_interface().get_base_control().add_child(download_dialog)
|
||||
download_dialog.show()
|
||||
|
||||
|
||||
func _on_download_dialog_update_finished() -> void:
|
||||
remove_tool_menu_item("Update Input Helper to v%s" % next_version)
|
||||
|
||||
get_editor_interface().get_resource_filesystem().scan()
|
||||
|
||||
print_rich("\n[b]Updated Input Helper to v%s[/b]\n" % next_version)
|
||||
|
||||
var finished_dialog: AcceptDialog = AcceptDialog.new()
|
||||
finished_dialog.dialog_text = "Your Input Helper is now up to date."
|
||||
|
||||
var restart_addon = func():
|
||||
finished_dialog.queue_free()
|
||||
get_editor_interface().call_deferred("set_plugin_enabled", "input_helper", true)
|
||||
get_editor_interface().set_plugin_enabled("input_helper", false)
|
||||
|
||||
finished_dialog.canceled.connect(restart_addon)
|
||||
finished_dialog.confirmed.connect(restart_addon)
|
||||
get_editor_interface().get_base_control().add_child(finished_dialog)
|
||||
finished_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_download_dialog_update_failed() -> void:
|
||||
var failed_dialog: AcceptDialog = AcceptDialog.new()
|
||||
failed_dialog.dialog_text = "There was a problem downloading the update."
|
||||
failed_dialog.canceled.connect(func(): failed_dialog.queue_free())
|
||||
failed_dialog.confirmed.connect(func(): failed_dialog.queue_free())
|
||||
get_editor_interface().get_base_control().add_child(failed_dialog)
|
||||
failed_dialog.popup_centered()
|
||||
1
Zennysoft.Game.Ma/addons/input_helper/plugin.gd.uid
Normal file
1
Zennysoft.Game.Ma/addons/input_helper/plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cul6evy00vr55
|
||||
42
Zennysoft.Game.Ma/addons/input_helper/settings.gd
Normal file
42
Zennysoft.Game.Ma/addons/input_helper/settings.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
class_name InputHelperSettings extends Node
|
||||
|
||||
const USE_GRANULAR_DEVICE_IDENTIFIERS = "devices/use_granular_device_identifiers"
|
||||
|
||||
const SETTINGS_CONFIGURATION = {
|
||||
USE_GRANULAR_DEVICE_IDENTIFIERS: {
|
||||
value = false,
|
||||
type = TYPE_BOOL,
|
||||
is_advanced = true
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
static func prepare() -> void:
|
||||
for key: String in SETTINGS_CONFIGURATION:
|
||||
var setting_config: Dictionary = SETTINGS_CONFIGURATION[key]
|
||||
var setting_name: String = "input_helper/%s" % key
|
||||
if not ProjectSettings.has_setting(setting_name):
|
||||
ProjectSettings.set_setting(setting_name, setting_config.value)
|
||||
ProjectSettings.set_initial_value(setting_name, setting_config.value)
|
||||
ProjectSettings.add_property_info({
|
||||
"name" = setting_name,
|
||||
"type" = setting_config.type,
|
||||
"hint" = setting_config.get("hint", PROPERTY_HINT_NONE),
|
||||
"hint_string" = setting_config.get("hint_string", "")
|
||||
})
|
||||
ProjectSettings.set_as_basic(setting_name, not setting_config.has("is_advanced"))
|
||||
ProjectSettings.set_as_internal(setting_name, setting_config.has("is_hidden"))
|
||||
|
||||
|
||||
static func set_setting(key: String, value) -> void:
|
||||
if get_setting(key, value) != value:
|
||||
ProjectSettings.set_setting("input_helper/%s" % key, value)
|
||||
ProjectSettings.set_initial_value("input_helper/%s" % key, SETTINGS_CONFIGURATION[key].value)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
static func get_setting(key: String, default):
|
||||
if ProjectSettings.has_setting("input_helper/%s" % key):
|
||||
return ProjectSettings.get_setting("input_helper/%s" % key)
|
||||
else:
|
||||
return default
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user