Compare commits
9 Commits
phase-a-pr
...
afca70e6c6
| Author | SHA1 | Date | |
|---|---|---|---|
| afca70e6c6 | |||
| 6c733d3159 | |||
| 6e71c321f7 | |||
| 4c15f50f6e | |||
| cea3956fbb | |||
| ad53ef9715 | |||
| ca0d21e40a | |||
| 1d4948e5b4 | |||
| 9d0f502cc2 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,4 +1,2 @@
|
|||||||
# Normalize EOL for all files that Git considers text files.
|
# Normalize EOL for all files that Git considers text files.
|
||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
addons/* linguist-vendored
|
|
||||||
*.gd linguist-vendored
|
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -2,12 +2,3 @@
|
|||||||
.godot/
|
.godot/
|
||||||
/android/
|
/android/
|
||||||
builds/
|
builds/
|
||||||
.worktrees/
|
|
||||||
|
|
||||||
# IDE / local tooling
|
|
||||||
.idea/
|
|
||||||
.superpowers/
|
|
||||||
|
|
||||||
# .NET build output
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Manages achievements using GameStateStore.
|
|
||||||
/// </summary>
|
|
||||||
public partial class AchievementManager : Node
|
|
||||||
{
|
|
||||||
public static AchievementManager Instance { get; private set; }
|
|
||||||
|
|
||||||
[Export] private string AchievementsFolderPath = "res://achievements/";
|
|
||||||
[Export] private PackedScene AchievementPopupScene { get; set; }
|
|
||||||
|
|
||||||
private System.Collections.Generic.Dictionary<string, AchievementResource> _achievements = new();
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
LoadAchievementsFromFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadAchievementsFromFolder()
|
|
||||||
{
|
|
||||||
using var dir = DirAccess.Open(AchievementsFolderPath);
|
|
||||||
if (dir == null)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"AchievementManager: Could not open achievements folder at '{AchievementsFolderPath}'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dir.ListDirBegin();
|
|
||||||
var fileName = dir.GetNext();
|
|
||||||
while (fileName != "")
|
|
||||||
{
|
|
||||||
if (!dir.CurrentIsDir() && fileName.EndsWith(".tres"))
|
|
||||||
{
|
|
||||||
var achievement = GD.Load<AchievementResource>(AchievementsFolderPath + fileName);
|
|
||||||
if (achievement != null)
|
|
||||||
{
|
|
||||||
_achievements.TryAdd(achievement.Id, achievement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileName = dir.GetNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the list of unlocked achievement IDs from the store.
|
|
||||||
/// </summary>
|
|
||||||
private System.Collections.Generic.List<string> GetUnlockedIds()
|
|
||||||
{
|
|
||||||
return GameStateStore.Instance?.Player.UnlockedAchievements ?? new System.Collections.Generic.List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnlockAchievement(string achievementId)
|
|
||||||
{
|
|
||||||
if (!_achievements.TryGetValue(achievementId, out var achievement))
|
|
||||||
{
|
|
||||||
GD.PrintErr($"Attempted to unlock non-existent achievement: '{achievementId}'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unlockedIds = GetUnlockedIds();
|
|
||||||
if (unlockedIds.Contains(achievementId))
|
|
||||||
{
|
|
||||||
return; // Already unlocked
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Mark as unlocked
|
|
||||||
unlockedIds.Add(achievementId);
|
|
||||||
GD.Print($"Achievement Unlocked: {achievement.DisplayName}");
|
|
||||||
EventBus.EmitAchievementUnlocked(achievementId);
|
|
||||||
|
|
||||||
// 2. Show the UI popup
|
|
||||||
if (AchievementPopupScene != null)
|
|
||||||
{
|
|
||||||
var popup = AchievementPopupScene.Instantiate<scripts.UI.AchievementPopup>();
|
|
||||||
GetTree().Root.AddChild(popup);
|
|
||||||
_ = popup.ShowAchievement(achievement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Call SteamManager if it's available
|
|
||||||
if (SteamManager.IsSteamInitialized)
|
|
||||||
{
|
|
||||||
SteamManager.UnlockAchievement(achievement.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LockAchievement(string achievementId)
|
|
||||||
{
|
|
||||||
var unlockedIds = GetUnlockedIds();
|
|
||||||
if (unlockedIds.Contains(achievementId))
|
|
||||||
{
|
|
||||||
unlockedIds.Remove(achievementId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsAchievementUnlocked(string achievementId)
|
|
||||||
{
|
|
||||||
return GetUnlockedIds().Contains(achievementId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://c4vvuqnx5y33u
|
|
||||||
@@ -4,8 +4,6 @@ namespace Mr.BrickAdventures.Autoloads;
|
|||||||
|
|
||||||
public partial class ConfigFileHandler : Node
|
public partial class ConfigFileHandler : Node
|
||||||
{
|
{
|
||||||
public static ConfigFileHandler Instance { get; private set; }
|
|
||||||
|
|
||||||
private ConfigFile _settingsConfig = new();
|
private ConfigFile _settingsConfig = new();
|
||||||
public const string SettingsPath = "user://settings.ini";
|
public const string SettingsPath = "user://settings.ini";
|
||||||
|
|
||||||
@@ -13,7 +11,6 @@ public partial class ConfigFileHandler : Node
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
Instance = this;
|
|
||||||
if (!FileAccess.FileExists(SettingsPath))
|
if (!FileAccess.FileExists(SettingsPath))
|
||||||
{
|
{
|
||||||
var err = _settingsConfig.Save(SettingsPath);
|
var err = _settingsConfig.Save(SettingsPath);
|
||||||
@@ -27,9 +24,4 @@ public partial class ConfigFileHandler : Node
|
|||||||
GD.PushError($"Failed to load settings file at {SettingsPath}: {err}");
|
GD.PushError($"Failed to load settings file at {SettingsPath}: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,120 +1,158 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
using Limbo.Console.Sharp;
|
||||||
|
using Mr.BrickAdventures.scripts;
|
||||||
using Mr.BrickAdventures.scripts.components;
|
using Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
public partial class ConsoleManager : Node
|
public partial class ConsoleManager : Node
|
||||||
{
|
{
|
||||||
private GameStateStore Store => GameStateStore.Instance;
|
private GameManager _gameManager;
|
||||||
private GameManager GameManager => GameManager.Instance;
|
|
||||||
private AchievementManager AchievementManager => AchievementManager.Instance;
|
|
||||||
private SkillManager _skillManager;
|
private SkillManager _skillManager;
|
||||||
private SkillUnlockerComponent _skillUnlockerComponent;
|
private SkillUnlockerComponent _skillUnlockerComponent;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_skillManager = SkillManager.Instance;
|
_gameManager = GetNode<GameManager>("/root/GameManager");
|
||||||
|
|
||||||
|
RegisterConsoleCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void _ExitTree()
|
||||||
|
{
|
||||||
|
UnregisterConsoleCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("add_coins", "Adds a specified amount of coins to the player's total.")]
|
||||||
private void AddCoinsCommand(int amount)
|
private void AddCoinsCommand(int amount)
|
||||||
{
|
{
|
||||||
if (Store == null) return;
|
_gameManager.AddCoins(amount);
|
||||||
Store.Player.Coins += amount;
|
LimboConsole.Info($"Increased coins by {amount}. Total coins: {_gameManager.GetCoins()}");
|
||||||
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("set_coins", "Sets the player's total coins to a specified amount.")]
|
||||||
private void SetCoinsCommand(int amount)
|
private void SetCoinsCommand(int amount)
|
||||||
{
|
{
|
||||||
if (Store == null) return;
|
_gameManager.SetCoins(amount);
|
||||||
Store.Player.Coins = Mathf.Max(0, amount);
|
LimboConsole.Info($"Set coins to {amount}. Total coins: {_gameManager.GetCoins()}");
|
||||||
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("set_lives", "Sets the player's total lives to a specified amount.")]
|
||||||
private void SetLivesCommand(int amount)
|
private void SetLivesCommand(int amount)
|
||||||
{
|
{
|
||||||
if (Store == null) return;
|
_gameManager.SetLives(amount);
|
||||||
Store.Player.Lives = amount;
|
LimboConsole.Info($"Set lives to {amount}.");
|
||||||
EventBus.EmitLivesChanged(amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("add_lives", "Adds a specified amount of lives to the player's total.")]
|
||||||
private void AddLivesCommand(int amount)
|
private void AddLivesCommand(int amount)
|
||||||
{
|
{
|
||||||
Store?.AddLives(amount);
|
_gameManager.AddLives(amount);
|
||||||
|
LimboConsole.Info($"Increased lives by {amount}. Total lives: {_gameManager.GetLives()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("set_health", "Sets the player's health to a specified amount.")]
|
||||||
private void SetHealthCommand(float amount)
|
private void SetHealthCommand(float amount)
|
||||||
{
|
{
|
||||||
var playerHealthComponent = GameManager?.Player?.GetNode<HealthComponent>("HealthComponent");
|
var playerHealthComponent = _gameManager.Player.GetNode<HealthComponent>("HealthComponent");
|
||||||
if (playerHealthComponent != null)
|
if (playerHealthComponent != null)
|
||||||
playerHealthComponent.Health = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetSessionCommand()
|
|
||||||
{
|
|
||||||
Store?.ResetSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnlockSkillCommand(string skillName)
|
|
||||||
{
|
|
||||||
if (!EnsureSkillManagement()) return;
|
|
||||||
|
|
||||||
var skill = _skillManager.GetSkillByName(skillName);
|
|
||||||
if (skill == null) return;
|
|
||||||
|
|
||||||
Store?.UnlockSkillPermanently(skill);
|
|
||||||
_skillManager.ActivateSkill(skill);
|
|
||||||
_skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnlockAllSkillsCommand()
|
|
||||||
{
|
|
||||||
if (!EnsureSkillManagement()) return;
|
|
||||||
_skillUnlockerComponent.UnlockAllSkills();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveSkillCommand(string skillName)
|
|
||||||
{
|
|
||||||
if (!EnsureSkillManagement()) return;
|
|
||||||
|
|
||||||
var skill = _skillManager.GetSkillByName(skillName);
|
|
||||||
if (skill == null) return;
|
|
||||||
|
|
||||||
Store?.RemoveUnlockedSkill(skill.Name);
|
|
||||||
_skillManager.DeactivateSkill(skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveAllSkillsCommand()
|
|
||||||
{
|
|
||||||
if (!EnsureSkillManagement()) return;
|
|
||||||
|
|
||||||
foreach (var skill in _skillManager.AvailableSkills)
|
|
||||||
{
|
{
|
||||||
Store?.RemoveUnlockedSkill(skill.Name);
|
playerHealthComponent.Health = amount;
|
||||||
_skillManager.DeactivateSkill(skill);
|
LimboConsole.Info($"Set player health to {amount}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LimboConsole.Warn("Player HealthComponent not found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("reset_session", "Resets the current session state.")]
|
||||||
|
private void ResetSessionCommand()
|
||||||
|
{
|
||||||
|
_gameManager.ResetCurrentSessionState();
|
||||||
|
LimboConsole.Info("Current session state has been reset.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("unlock_skill", "Unlocks and activates a skill by its name.")]
|
||||||
|
private void UnlockSkillCommand(string skillName)
|
||||||
|
{
|
||||||
|
if (!GetSkillManagement()) return;
|
||||||
|
|
||||||
|
var skill = _skillManager.GetSkillByName(skillName);
|
||||||
|
if (skill == null)
|
||||||
|
{
|
||||||
|
LimboConsole.Warn($"Skill '{skillName}' not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameManager.UnlockSkill(skill);
|
||||||
|
_skillManager.ActivateSkill(skill);
|
||||||
|
_skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill);
|
||||||
|
LimboConsole.Info($"Skill '{skillName}' has been unlocked and activated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetSkillManagement()
|
||||||
|
{
|
||||||
|
var player = _gameManager.Player;
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
LimboConsole.Warn("Player node not found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_skillManager ??= player.GetNode<SkillManager>("SkillManager");
|
||||||
|
_skillUnlockerComponent ??= player.GetNode<SkillUnlockerComponent>("SkillUnlockerComponent");
|
||||||
|
|
||||||
|
if (_skillManager != null && _skillUnlockerComponent != null) return true;
|
||||||
|
|
||||||
|
LimboConsole.Warn("SkillManager or SkillUnlockerComponent not found on the player.");
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("unlock_all_skills", "Unlocks and activates all available skills.")]
|
||||||
|
private void UnlockAllSkillsCommand()
|
||||||
|
{
|
||||||
|
if (!GetSkillManagement()) return;
|
||||||
|
|
||||||
|
_skillUnlockerComponent.UnlockAllSkills();
|
||||||
|
LimboConsole.Info("All skills have been unlocked and activated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("remove_skill", "Deactivates and removes a skill by its name.")]
|
||||||
|
private void RemoveSkillCommand(string skillName)
|
||||||
|
{
|
||||||
|
if (!GetSkillManagement()) return;
|
||||||
|
|
||||||
|
var skill = _skillManager.GetSkillByName(skillName);
|
||||||
|
if (skill == null)
|
||||||
|
{
|
||||||
|
LimboConsole.Warn($"Skill '{skillName}' not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameManager.RemoveSkill(skill.Name);
|
||||||
|
_skillManager.DeactivateSkill(skill);
|
||||||
|
LimboConsole.Info($"Skill '{skillName}' has been deactivated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("remove_all_skills", "Deactivates and removes all skills.")]
|
||||||
|
private void RemoveAllSkillsCommand()
|
||||||
|
{
|
||||||
|
if (!GetSkillManagement()) return;
|
||||||
|
|
||||||
|
foreach (var skill in _skillManager.AvailableSkills)
|
||||||
|
{
|
||||||
|
_gameManager.RemoveSkill(skill.Name);
|
||||||
|
_skillManager.DeactivateSkill(skill);
|
||||||
|
}
|
||||||
|
LimboConsole.Info("All skills have been deactivated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("next_level", "Advances the game to the next level.")]
|
||||||
private void GoToNextLevelCommand()
|
private void GoToNextLevelCommand()
|
||||||
{
|
{
|
||||||
GameManager?.OnLevelComplete();
|
_gameManager.OnLevelComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnlockAchievementCommand(string achievementId)
|
|
||||||
{
|
|
||||||
AchievementManager?.UnlockAchievement(achievementId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetAchievementCommand(string achievementId)
|
|
||||||
{
|
|
||||||
AchievementManager?.LockAchievement(achievementId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool EnsureSkillManagement()
|
|
||||||
{
|
|
||||||
var player = GameManager?.Player;
|
|
||||||
if (player == null || !IsInstanceValid(player)) return false;
|
|
||||||
|
|
||||||
_skillUnlockerComponent ??= player.GetNode<SkillUnlockerComponent>("SkillUnlockerComponent");
|
|
||||||
return _skillManager != null && _skillUnlockerComponent != null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.scripts.components;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Global event bus for decoupled communication between game systems.
|
|
||||||
/// Use the static Instance property for easy access from anywhere.
|
|
||||||
/// </summary>
|
|
||||||
public partial class EventBus : Node
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Singleton instance. Available after the autoload is initialized.
|
|
||||||
/// </summary>
|
|
||||||
public static EventBus Instance { get; private set; }
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this)
|
|
||||||
Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Level Events
|
|
||||||
|
|
||||||
[Signal] public delegate void LevelStartedEventHandler(int levelIndex, Node currentScene);
|
|
||||||
[Signal] public delegate void LevelCompletedEventHandler(int levelIndex, Node currentScene, double completionTime);
|
|
||||||
[Signal] public delegate void LevelRestartedEventHandler(int levelIndex);
|
|
||||||
|
|
||||||
public static void EmitLevelStarted(int levelIndex, Node currentScene)
|
|
||||||
=> Instance?.EmitSignal(SignalName.LevelStarted, levelIndex, currentScene);
|
|
||||||
|
|
||||||
public static void EmitLevelCompleted(int levelIndex, Node currentScene, double completionTime)
|
|
||||||
=> Instance?.EmitSignal(SignalName.LevelCompleted, levelIndex, currentScene, completionTime);
|
|
||||||
|
|
||||||
public static void EmitLevelRestarted(int levelIndex)
|
|
||||||
=> Instance?.EmitSignal(SignalName.LevelRestarted, levelIndex);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Player Events
|
|
||||||
|
|
||||||
[Signal] public delegate void PlayerSpawnedEventHandler(PlayerController player);
|
|
||||||
[Signal] public delegate void PlayerDiedEventHandler(Vector2 position);
|
|
||||||
[Signal] public delegate void PlayerDamagedEventHandler(float damage, float remainingHealth, Vector2 position);
|
|
||||||
[Signal] public delegate void PlayerHealedEventHandler(float amount, float newHealth, Vector2 position);
|
|
||||||
|
|
||||||
public static void EmitPlayerSpawned(PlayerController player)
|
|
||||||
=> Instance?.EmitSignal(SignalName.PlayerSpawned, player);
|
|
||||||
|
|
||||||
public static void EmitPlayerDied(Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.PlayerDied, position);
|
|
||||||
|
|
||||||
public static void EmitPlayerDamaged(float damage, float remainingHealth, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.PlayerDamaged, damage, remainingHealth, position);
|
|
||||||
|
|
||||||
public static void EmitPlayerHealed(float amount, float newHealth, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.PlayerHealed, amount, newHealth, position);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Combat Events
|
|
||||||
|
|
||||||
[Signal] public delegate void EnemyDefeatedEventHandler(Node enemy, Vector2 position);
|
|
||||||
[Signal] public delegate void EnemyDamagedEventHandler(Node enemy, float damage, Vector2 position);
|
|
||||||
|
|
||||||
public static void EmitEnemyDefeated(Node enemy, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.EnemyDefeated, enemy, position);
|
|
||||||
|
|
||||||
public static void EmitEnemyDamaged(Node enemy, float damage, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.EnemyDamaged, enemy, damage, position);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Collection Events
|
|
||||||
|
|
||||||
[Signal] public delegate void CoinCollectedEventHandler(int amount, Vector2 position);
|
|
||||||
[Signal] public delegate void ItemCollectedEventHandler(CollectableType itemType, float amount, Vector2 position);
|
|
||||||
[Signal] public delegate void ChildRescuedEventHandler(Vector2 position);
|
|
||||||
[Signal] public delegate void SkillCollectedEventHandler(SkillData skill, Vector2 position);
|
|
||||||
|
|
||||||
public static void EmitCoinCollected(int amount, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.CoinCollected, amount, position);
|
|
||||||
|
|
||||||
public static void EmitItemCollected(CollectableType itemType, float amount, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.ItemCollected, (int)itemType, amount, position);
|
|
||||||
|
|
||||||
public static void EmitChildRescued(Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.ChildRescued, position);
|
|
||||||
|
|
||||||
public static void EmitSkillCollected(SkillData skill, Vector2 position)
|
|
||||||
=> Instance?.EmitSignal(SignalName.SkillCollected, skill, position);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Skill Events
|
|
||||||
|
|
||||||
[Signal] public delegate void SkillUnlockedEventHandler(string skillName, int level);
|
|
||||||
[Signal] public delegate void SkillActivatedEventHandler(string skillName);
|
|
||||||
[Signal] public delegate void SkillDeactivatedEventHandler(string skillName);
|
|
||||||
|
|
||||||
public static void EmitSkillUnlocked(string skillName, int level = 1)
|
|
||||||
=> Instance?.EmitSignal(SignalName.SkillUnlocked, skillName, level);
|
|
||||||
|
|
||||||
public static void EmitSkillActivated(string skillName)
|
|
||||||
=> Instance?.EmitSignal(SignalName.SkillActivated, skillName);
|
|
||||||
|
|
||||||
public static void EmitSkillDeactivated(string skillName)
|
|
||||||
=> Instance?.EmitSignal(SignalName.SkillDeactivated, skillName);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Game State Events
|
|
||||||
|
|
||||||
[Signal] public delegate void GamePausedEventHandler();
|
|
||||||
[Signal] public delegate void GameResumedEventHandler();
|
|
||||||
[Signal] public delegate void GameSavedEventHandler();
|
|
||||||
[Signal] public delegate void GameStartedEventHandler();
|
|
||||||
[Signal] public delegate void GameContinuedEventHandler();
|
|
||||||
|
|
||||||
public static void EmitGamePaused()
|
|
||||||
=> Instance?.EmitSignal(SignalName.GamePaused);
|
|
||||||
|
|
||||||
public static void EmitGameResumed()
|
|
||||||
=> Instance?.EmitSignal(SignalName.GameResumed);
|
|
||||||
|
|
||||||
public static void EmitGameSaved()
|
|
||||||
=> Instance?.EmitSignal(SignalName.GameSaved);
|
|
||||||
|
|
||||||
public static void EmitGameStarted()
|
|
||||||
=> Instance?.EmitSignal(SignalName.GameStarted);
|
|
||||||
|
|
||||||
public static void EmitGameContinued()
|
|
||||||
=> Instance?.EmitSignal(SignalName.GameContinued);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Achievement Events
|
|
||||||
|
|
||||||
[Signal] public delegate void AchievementUnlockedEventHandler(string achievementId);
|
|
||||||
|
|
||||||
public static void EmitAchievementUnlocked(string achievementId)
|
|
||||||
=> Instance?.EmitSignal(SignalName.AchievementUnlocked, achievementId);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region State Change Events
|
|
||||||
|
|
||||||
[Signal] public delegate void CoinsChangedEventHandler(int totalCoins);
|
|
||||||
[Signal] public delegate void LivesChangedEventHandler(int lives);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The Playthrough has no lives left. Raised as part of the same operation that spends the last
|
|
||||||
/// life, so a subscriber can never observe lives that have not been decremented yet — which is
|
|
||||||
/// the race that lets the death screen sit there forever on the last life.
|
|
||||||
/// </summary>
|
|
||||||
[Signal] public delegate void RunEndedEventHandler();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A death has been settled. The life is already spent and the attempt already discarded, so
|
|
||||||
/// <paramref name="remainingLives"/> is safe to display directly — unlike reading Lives from a
|
|
||||||
/// handler racing the one that writes it.
|
|
||||||
/// </summary>
|
|
||||||
[Signal] public delegate void AttemptFailedEventHandler(int remainingLives, bool runOver);
|
|
||||||
|
|
||||||
public static void EmitAttemptFailed(int remainingLives, bool runOver)
|
|
||||||
=> Instance?.EmitSignal(SignalName.AttemptFailed, remainingLives, runOver);
|
|
||||||
|
|
||||||
public static void EmitRunEnded()
|
|
||||||
=> Instance?.EmitSignal(SignalName.RunEnded);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The set of owned skills, or the level of one of them, changed. SkillManager reconciles its live
|
|
||||||
/// components against this instead of every caller remembering to update it separately — the
|
|
||||||
/// non-atomic pair of calls that could leave a skill owned but inert.
|
|
||||||
/// </summary>
|
|
||||||
[Signal] public delegate void SkillSetChangedEventHandler();
|
|
||||||
|
|
||||||
public static void EmitSkillSetChanged()
|
|
||||||
=> Instance?.EmitSignal(SignalName.SkillSetChanged);
|
|
||||||
|
|
||||||
public static void EmitCoinsChanged(int totalCoins)
|
|
||||||
=> Instance?.EmitSignal(SignalName.CoinsChanged, totalCoins);
|
|
||||||
|
|
||||||
public static void EmitLivesChanged(int lives)
|
|
||||||
=> Instance?.EmitSignal(SignalName.LivesChanged, lives);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bb2yq5wggdw3w
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
using Mr.BrickAdventures.scripts.UI;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class FloatingTextManager : Node
|
|
||||||
{
|
|
||||||
[Export] public PackedScene FloatingTextScene { get; set; }
|
|
||||||
|
|
||||||
[ExportGroup("Colors")]
|
|
||||||
[Export] public Color DamageColor { get; set; } = new Color("#b21030"); // Red
|
|
||||||
[Export] public Color HealColor { get; set; } = new Color("#71f341"); // Green
|
|
||||||
[Export] public Color CoinColor { get; set; } = new Color("#ebd320"); // Gold
|
|
||||||
[Export] public Color MessageColor { get; set; } = new Color("#ffffff"); // White
|
|
||||||
|
|
||||||
public static FloatingTextManager Instance { get; private set; }
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowDamage(float amount, Vector2 position)
|
|
||||||
{
|
|
||||||
var text = Mathf.Round(amount * 100f).ToString(CultureInfo.InvariantCulture);
|
|
||||||
CreateFloatingText(text, position, DamageColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowHeal(float amount, Vector2 position)
|
|
||||||
{
|
|
||||||
var text = $"+{Mathf.Round(amount * 100f).ToString(CultureInfo.InvariantCulture)}";
|
|
||||||
CreateFloatingText(text, position, HealColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowCoin(int amount, Vector2 position)
|
|
||||||
{
|
|
||||||
var text = $"+{amount}";
|
|
||||||
CreateFloatingText(text, position, CoinColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowMessage(string message, Vector2 position)
|
|
||||||
{
|
|
||||||
CreateFloatingText(message, position, MessageColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateFloatingText(string text, Vector2 position, Color color)
|
|
||||||
{
|
|
||||||
if (FloatingTextScene == null)
|
|
||||||
{
|
|
||||||
GD.PushError("FloatingTextManager: FloatingTextScene is not set!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var popup = FloatingTextScene.Instantiate<FloatingText>();
|
|
||||||
GetTree().CurrentScene.AddChild(popup);
|
|
||||||
popup.Show(text, position, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cobgfsr3gw7cn
|
|
||||||
@@ -1,191 +1,247 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Godot;
|
using Godot;
|
||||||
using Mr.BrickAdventures.Progression;
|
using Godot.Collections;
|
||||||
using Mr.BrickAdventures.scripts.components;
|
using Mr.BrickAdventures.scripts.components;
|
||||||
|
using Mr.BrickAdventures.scripts.Resources;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Game orchestrator: scene transitions and game flow.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// Which Level the player is on now belongs to <see cref="ProgressionRoot"/>, addressed by a stable
|
|
||||||
/// <see cref="LevelId"/> and resolved to a scene by the level catalogue. The old
|
|
||||||
/// <c>Array<PackedScene> LevelScenes</c> is gone: it made a save's meaning depend on the order
|
|
||||||
/// of an inspector array, so reordering levels silently invalidated every save.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Progression is persisted by <see cref="ProgressionRoot"/>'s stores, which save themselves at the
|
|
||||||
/// points that matter — a First Clear and a death — so there is no separate "save the game" step to
|
|
||||||
/// forget.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public partial class GameManager : Node
|
public partial class GameManager : Node
|
||||||
{
|
{
|
||||||
public PlayerController Player
|
[Export] public Array<PackedScene> LevelScenes { get; set; } = [];
|
||||||
{
|
|
||||||
|
public PlayerController Player {
|
||||||
get => GetPlayer();
|
get => GetPlayer();
|
||||||
private set => _player = value;
|
private set => _player = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Node> _sceneNodes = [];
|
||||||
private PlayerController _player;
|
private PlayerController _player;
|
||||||
private SpeedRunManager SpeedRunManager => SpeedRunManager.Instance;
|
|
||||||
private ProgressionRoot Progression => ProgressionRoot.Instance;
|
|
||||||
|
|
||||||
public static GameManager Instance { get; private set; }
|
[Export]
|
||||||
|
public Dictionary PlayerState { get; set; } = new()
|
||||||
public override void _Ready()
|
|
||||||
{
|
{
|
||||||
Instance = this;
|
{ "coins", 0 },
|
||||||
EventBus.Instance.PlayerSpawned += OnPlayerSpawned;
|
{ "lives", 3 },
|
||||||
|
{ "current_level", 0 },
|
||||||
|
{ "completed_levels", new Array<int>() },
|
||||||
|
{ "unlocked_levels", new Array<int>() {0}},
|
||||||
|
{ "unlocked_skills", new Array<SkillData>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public Dictionary CurrentSessionState { get; private set; } = new()
|
||||||
|
{
|
||||||
|
{ "coins_collected", 0 },
|
||||||
|
{ "skills_unlocked", new Array<SkillData>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void _EnterTree()
|
||||||
|
{
|
||||||
|
GetTree().NodeAdded += OnNodeAdded;
|
||||||
|
GetTree().NodeRemoved += OnNodeRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree()
|
||||||
{
|
{
|
||||||
if (Instance == this) Instance = null;
|
GetTree().NodeAdded -= OnNodeAdded;
|
||||||
if (EventBus.Instance != null)
|
GetTree().NodeRemoved -= OnNodeRemoved;
|
||||||
EventBus.Instance.PlayerSpawned -= OnPlayerSpawned;
|
_sceneNodes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerSpawned(PlayerController player)
|
private void OnNodeAdded(Node node)
|
||||||
{
|
{
|
||||||
_player = player;
|
_sceneNodes.Add(node);
|
||||||
player.TreeExiting += () => { if (_player == player) _player = null; };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Game Flow
|
private void OnNodeRemoved(Node node)
|
||||||
|
|
||||||
public void StartNewGame()
|
|
||||||
{
|
{
|
||||||
if (Progression == null) return;
|
_sceneNodes.Remove(node);
|
||||||
|
|
||||||
Progression.StartPlaythrough(ProgressionRoot.SoleSlot);
|
|
||||||
SpeedRunManager?.StartTimer();
|
|
||||||
EventBus.EmitGameStarted();
|
|
||||||
|
|
||||||
GoTo(Progression.Campaign.First);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ContinueGame()
|
public void AddCoins(int amount)
|
||||||
{
|
{
|
||||||
if (Progression == null) return;
|
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] + amount);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Progression.LoadPlaythrough(ProgressionRoot.SoleSlot))
|
public void SetCoins(int amount) => PlayerState["coins"] = Mathf.Max(0, amount);
|
||||||
|
|
||||||
|
public int GetCoins() => (int)PlayerState["coins"] + (int)CurrentSessionState["coins_collected"];
|
||||||
|
|
||||||
|
public void RemoveCoins(int amount)
|
||||||
|
{
|
||||||
|
var sessionCoins = (int)CurrentSessionState["coins_collected"];
|
||||||
|
if (amount <= sessionCoins)
|
||||||
{
|
{
|
||||||
var why = Progression.PeekPlaythrough(ProgressionRoot.SoleSlot);
|
CurrentSessionState["coins_collected"] = sessionCoins - amount;
|
||||||
if (why == LoadOutcome.Corrupt)
|
|
||||||
{
|
|
||||||
// Deliberately not overwritten: the file is left where it is so it can be recovered.
|
|
||||||
GD.PushError("Saved progress could not be read. It has been left untouched.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartNewGame();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var remaining = amount - sessionCoins;
|
||||||
|
CurrentSessionState["coins_collected"] = 0;
|
||||||
|
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"] - remaining);
|
||||||
|
}
|
||||||
|
PlayerState["coins"] = Mathf.Max(0, (int)PlayerState["coins"]);
|
||||||
|
}
|
||||||
|
|
||||||
SpeedRunManager?.StartTimer();
|
public void AddLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] + amount;
|
||||||
EventBus.EmitGameContinued();
|
public void RemoveLives(int amount) => PlayerState["lives"] = (int)PlayerState["lives"] - amount;
|
||||||
|
public void SetLives(int amount) => PlayerState["lives"] = amount;
|
||||||
|
public int GetLives() => (int)PlayerState["lives"];
|
||||||
|
|
||||||
GoTo(Progression.CurrentLevel ?? Progression.Campaign.First);
|
public bool IsSkillUnlocked(SkillData skill)
|
||||||
|
{
|
||||||
|
return ((Array)PlayerState["unlocked_skills"]).Contains(skill)
|
||||||
|
|| ((Array)CurrentSessionState["skills_unlocked"]).Contains(skill);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnlockSkill(SkillData skill)
|
||||||
|
{
|
||||||
|
if (!IsSkillUnlocked(skill))
|
||||||
|
((Array)PlayerState["unlocked_skills"]).Add(skill);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSkill(string skillName)
|
||||||
|
{
|
||||||
|
var arr = (Array)PlayerState["unlocked_skills"];
|
||||||
|
foreach (SkillData s in arr)
|
||||||
|
{
|
||||||
|
if (s.Name != skillName) continue;
|
||||||
|
|
||||||
|
arr.Remove(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnlockSkills(Array<SkillData> skills)
|
||||||
|
{
|
||||||
|
foreach (var s in skills)
|
||||||
|
UnlockSkill(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetPlayerState()
|
||||||
|
{
|
||||||
|
PlayerState = new Dictionary
|
||||||
|
{
|
||||||
|
{ "coins", 0 },
|
||||||
|
{ "lives", 3 },
|
||||||
|
{ "current_level", 0 },
|
||||||
|
{ "completed_levels", new Array<int>() },
|
||||||
|
{ "unlocked_levels", new Array<int>() {0}},
|
||||||
|
{ "unlocked_skills", new Array<SkillData>() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnlockLevel(int levelIndex)
|
||||||
|
{
|
||||||
|
var unlocked = (Array)PlayerState["unlocked_levels"];
|
||||||
|
if (!unlocked.Contains(levelIndex)) unlocked.Add(levelIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TryToGoToNextLevel()
|
||||||
|
{
|
||||||
|
var next = (int)PlayerState["current_level"] + 1;
|
||||||
|
var unlocked = (Array)PlayerState["unlocked_levels"];
|
||||||
|
if (next < LevelScenes.Count && unlocked.Contains(next))
|
||||||
|
{
|
||||||
|
PlayerState["current_level"] = next;
|
||||||
|
GetTree().ChangeSceneToPacked(LevelScenes[next]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkLevelComplete(int levelIndex)
|
||||||
|
{
|
||||||
|
UnlockLevel(levelIndex + 1);
|
||||||
|
var completed = (Array)PlayerState["completed_levels"];
|
||||||
|
if (!completed.Contains(levelIndex)) completed.Add(levelIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetCurrentSessionState()
|
||||||
|
{
|
||||||
|
CurrentSessionState = new Dictionary
|
||||||
|
{
|
||||||
|
{ "coins_collected", 0 },
|
||||||
|
{ "skills_unlocked", new Array<SkillData>() }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartGame()
|
public void RestartGame()
|
||||||
{
|
{
|
||||||
if (Progression == null) return;
|
ResetPlayerState();
|
||||||
|
ResetCurrentSessionState();
|
||||||
Progression.StartPlaythrough(ProgressionRoot.SoleSlot);
|
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
||||||
SpeedRunManager?.StartTimer();
|
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||||
|
|
||||||
GoTo(Progression.Campaign.First);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnLevelComplete()
|
|
||||||
{
|
|
||||||
if (Progression?.CurrentLevel is not { } finished) return;
|
|
||||||
|
|
||||||
var seconds = SpeedRunManager?.GetCurrentLevelTime() ?? 0.0;
|
|
||||||
LevelTime? time = seconds > 0 ? LevelTime.Of(seconds) : null;
|
|
||||||
|
|
||||||
EventBus.EmitLevelCompleted(IndexOf(finished), GetTree().CurrentScene, seconds);
|
|
||||||
|
|
||||||
// Banks the attempt's coins and skills, marks the level cleared, and advances campaign
|
|
||||||
// position if this was a First Clear.
|
|
||||||
Progression.FinishLevel(time);
|
|
||||||
|
|
||||||
if (Progression.CurrentLevel is { } next && !next.Equals(finished))
|
|
||||||
GoTo(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads a Level's scene, then announces the entry once the new scene is actually current.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The announcement is deliberately after a frame. <c>ChangeSceneToPacked</c> is deferred, so at
|
|
||||||
/// the moment it returns <c>CurrentScene</c> is still the outgoing scene — which is why the old
|
|
||||||
/// code handed <c>GhostEventHandler</c> the scene being torn down and ghosts were spawned into it.
|
|
||||||
/// </remarks>
|
|
||||||
private async void GoTo(LevelId level)
|
|
||||||
{
|
|
||||||
if (!Progression.CanEnter(level))
|
|
||||||
{
|
|
||||||
GD.PushError($"Level '{level}' is not reachable in this playthrough.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scene = Progression.Catalogue.SceneFor(level);
|
|
||||||
if (scene == null)
|
|
||||||
{
|
|
||||||
GD.PushError($"Level '{level}' has no loadable scene.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetTree().ChangeSceneToPacked(scene);
|
|
||||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
|
||||||
|
|
||||||
Progression.EnterLevel(level);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Position in the catalogue, for the signals that are still index-based because GhostManager
|
|
||||||
/// names its files <c>ghost_level_{index}.json</c>.
|
|
||||||
/// </summary>
|
|
||||||
private int IndexOf(LevelId level)
|
|
||||||
{
|
|
||||||
var levels = Progression?.Catalogue?.Levels;
|
|
||||||
if (levels == null) return -1;
|
|
||||||
|
|
||||||
for (var i = 0; i < levels.Count; i++)
|
|
||||||
if (levels[i]?.Id == level.Value)
|
|
||||||
return i;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PauseGame()
|
|
||||||
{
|
|
||||||
Engine.TimeScale = 0;
|
|
||||||
EventBus.EmitGamePaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResumeGame()
|
|
||||||
{
|
|
||||||
Engine.TimeScale = 1;
|
|
||||||
EventBus.EmitGameResumed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QuitGame() => GetTree().Quit();
|
public void QuitGame() => GetTree().Quit();
|
||||||
|
|
||||||
#endregion
|
public void PauseGame() => Engine.TimeScale = 0;
|
||||||
|
public void ResumeGame() => Engine.TimeScale = 1;
|
||||||
|
|
||||||
#region Player Lookup
|
public void StartNewGame()
|
||||||
|
{
|
||||||
|
ResetPlayerState();
|
||||||
|
ResetCurrentSessionState();
|
||||||
|
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
||||||
|
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContinueGame()
|
||||||
|
{
|
||||||
|
var save = GetNode<SaveSystem>("/root/SaveSystem");
|
||||||
|
if (!save.LoadGame())
|
||||||
|
{
|
||||||
|
GD.PrintErr("Failed to load game. Starting a new game instead.");
|
||||||
|
StartNewGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = (int)PlayerState["current_level"];
|
||||||
|
if (idx < LevelScenes.Count)
|
||||||
|
GetTree().ChangeSceneToPacked(LevelScenes[idx]);
|
||||||
|
else
|
||||||
|
GD.PrintErr("No levels unlocked to continue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnLevelComplete()
|
||||||
|
{
|
||||||
|
var levelIndex = (int)PlayerState["current_level"];
|
||||||
|
MarkLevelComplete(levelIndex);
|
||||||
|
AddCoins((int)CurrentSessionState["coins_collected"]);
|
||||||
|
foreach (var s in (Array)CurrentSessionState["skills_unlocked"])
|
||||||
|
UnlockSkill((SkillData)s);
|
||||||
|
|
||||||
|
ResetCurrentSessionState();
|
||||||
|
TryToGoToNextLevel();
|
||||||
|
GetNode<SaveSystem>("/root/SaveSystem").SaveGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Array<SkillData> GetUnlockedSkills()
|
||||||
|
{
|
||||||
|
var unlocked = (Array<SkillData>)PlayerState["unlocked_skills"];
|
||||||
|
var session = (Array<SkillData>)CurrentSessionState["skills_unlocked"];
|
||||||
|
if (session!.Count == 0) return unlocked;
|
||||||
|
if (unlocked!.Count == 0) return session;
|
||||||
|
var joined = new Array<SkillData>();
|
||||||
|
joined.AddRange(unlocked);
|
||||||
|
joined.AddRange(session);
|
||||||
|
return joined;
|
||||||
|
}
|
||||||
|
|
||||||
public PlayerController GetPlayer()
|
public PlayerController GetPlayer()
|
||||||
{
|
{
|
||||||
if (_player != null && IsInstanceValid(_player)) return _player;
|
if (_player != null) return _player;
|
||||||
_player = null;
|
|
||||||
|
foreach (var node in _sceneNodes)
|
||||||
|
{
|
||||||
|
if (node is not PlayerController player) continue;
|
||||||
|
|
||||||
|
_player = player;
|
||||||
|
return _player;
|
||||||
|
}
|
||||||
|
|
||||||
|
GD.PrintErr("PlayerController not found in the scene tree.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Central store for game state - single source of truth.
|
|
||||||
/// Use the static Instance property for easy access.
|
|
||||||
/// </summary>
|
|
||||||
public partial class GameStateStore : Node
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Singleton instance.
|
|
||||||
/// </summary>
|
|
||||||
public static GameStateStore Instance { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Persistent player state (saved to disk).
|
|
||||||
/// </summary>
|
|
||||||
public PlayerState Player { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current session state (transient, reset on death/level complete).
|
|
||||||
/// </summary>
|
|
||||||
public SessionState Session { get; set; } = new();
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this)
|
|
||||||
Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Coin Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets total coins (saved + session).
|
|
||||||
/// </summary>
|
|
||||||
public int GetTotalCoins() => Player.Coins + Session.CoinsCollected;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds coins to the session (not saved until level complete).
|
|
||||||
/// </summary>
|
|
||||||
public void AddSessionCoins(int amount)
|
|
||||||
{
|
|
||||||
Session.CoinsCollected += amount;
|
|
||||||
EventBus.EmitCoinsChanged(GetTotalCoins());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Commits session coins to player state.
|
|
||||||
/// </summary>
|
|
||||||
public void CommitSessionCoins()
|
|
||||||
{
|
|
||||||
Player.Coins += Session.CoinsCollected;
|
|
||||||
Session.CoinsCollected = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes coins, first from session then from saved.
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveCoins(int amount)
|
|
||||||
{
|
|
||||||
if (amount <= Session.CoinsCollected)
|
|
||||||
{
|
|
||||||
Session.CoinsCollected -= amount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var remaining = amount - Session.CoinsCollected;
|
|
||||||
Session.CoinsCollected = 0;
|
|
||||||
Player.Coins = Mathf.Max(0, Player.Coins - remaining);
|
|
||||||
}
|
|
||||||
EventBus.EmitCoinsChanged(GetTotalCoins());
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Lives Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Decrements lives by 1.
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveLife()
|
|
||||||
{
|
|
||||||
Player.Lives = Mathf.Max(0, Player.Lives - 1);
|
|
||||||
EventBus.EmitLivesChanged(Player.Lives);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds lives.
|
|
||||||
/// </summary>
|
|
||||||
public void AddLives(int amount)
|
|
||||||
{
|
|
||||||
Player.Lives += amount;
|
|
||||||
EventBus.EmitLivesChanged(Player.Lives);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Level Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unlocks a level for access.
|
|
||||||
/// </summary>
|
|
||||||
public void UnlockLevel(int levelIndex)
|
|
||||||
{
|
|
||||||
if (!Player.UnlockedLevels.Contains(levelIndex))
|
|
||||||
Player.UnlockedLevels.Add(levelIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks a level as completed and unlocks the next.
|
|
||||||
/// </summary>
|
|
||||||
public void MarkLevelComplete(int levelIndex)
|
|
||||||
{
|
|
||||||
if (!Player.CompletedLevels.Contains(levelIndex))
|
|
||||||
Player.CompletedLevels.Add(levelIndex);
|
|
||||||
UnlockLevel(levelIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a level is unlocked.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsLevelUnlocked(int levelIndex) => Player.UnlockedLevels.Contains(levelIndex);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Skill Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a skill is unlocked (saved or session).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSkillUnlocked(SkillData skill)
|
|
||||||
{
|
|
||||||
return Player.UnlockedSkills.Contains(skill) || Session.SkillsUnlocked.Contains(skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unlocks a skill in the session (lost on death, committed on level complete).
|
|
||||||
/// </summary>
|
|
||||||
public void UnlockSkillInSession(SkillData skill)
|
|
||||||
{
|
|
||||||
if (!IsSkillUnlocked(skill))
|
|
||||||
Session.SkillsUnlocked.Add(skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Permanently unlocks a skill directly in player state (bypasses session, e.g. console commands).
|
|
||||||
/// </summary>
|
|
||||||
public void UnlockSkillPermanently(SkillData skill)
|
|
||||||
{
|
|
||||||
if (!Player.UnlockedSkills.Contains(skill))
|
|
||||||
Player.UnlockedSkills.Add(skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a permanently unlocked skill from player state by name.
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveUnlockedSkill(string skillName)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < Player.UnlockedSkills.Count; i++)
|
|
||||||
{
|
|
||||||
if (Player.UnlockedSkills[i].Name == skillName)
|
|
||||||
{
|
|
||||||
Player.UnlockedSkills.RemoveAt(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Commits session skills to player state.
|
|
||||||
/// </summary>
|
|
||||||
public void CommitSessionSkills()
|
|
||||||
{
|
|
||||||
foreach (var skill in Session.SkillsUnlocked)
|
|
||||||
{
|
|
||||||
if (!Player.UnlockedSkills.Contains(skill))
|
|
||||||
Player.UnlockedSkills.Add(skill);
|
|
||||||
}
|
|
||||||
Session.SkillsUnlocked.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all unlocked skills from player persistence and current session.
|
|
||||||
/// </summary>
|
|
||||||
public List<SkillData> GetAllUnlockedSkills()
|
|
||||||
{
|
|
||||||
var result = new List<SkillData>(Player.UnlockedSkills);
|
|
||||||
foreach (var skill in Session.SkillsUnlocked)
|
|
||||||
{
|
|
||||||
if (!result.Contains(skill)) result.Add(skill);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Statistics Operations
|
|
||||||
|
|
||||||
public void IncrementStat(string name, int amount = 1)
|
|
||||||
{
|
|
||||||
if (Player.Statistics.TryGetValue(name, out var current))
|
|
||||||
Player.Statistics[name] = current + amount;
|
|
||||||
else
|
|
||||||
Player.Statistics[name] = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStat(string name, int value) => Player.Statistics[name] = value;
|
|
||||||
|
|
||||||
public int GetStat(string name) =>
|
|
||||||
Player.Statistics.TryGetValue(name, out var value) ? value : 0;
|
|
||||||
|
|
||||||
public System.Collections.Generic.Dictionary<string, int> GetAllStats() =>
|
|
||||||
new(Player.Statistics);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Reset Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets only the session state.
|
|
||||||
/// </summary>
|
|
||||||
public void ResetSession() => Session.Reset();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resets everything to defaults.
|
|
||||||
/// </summary>
|
|
||||||
public void ResetAll()
|
|
||||||
{
|
|
||||||
Player.Reset();
|
|
||||||
Session.ResetAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://bwrhkipwecytk
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.scripts;
|
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class GhostManager : Node
|
|
||||||
{
|
|
||||||
public static GhostManager Instance { get; private set; }
|
|
||||||
|
|
||||||
[Export] private PackedScene GhostPlayerScene { get; set; }
|
|
||||||
|
|
||||||
public override void _Ready() => Instance = this;
|
|
||||||
public override void _ExitTree() { if (Instance == this) Instance = null; }
|
|
||||||
|
|
||||||
public bool IsRecording { get; private set; } = false;
|
|
||||||
public bool IsPlaybackEnabled { get; private set; } = true;
|
|
||||||
|
|
||||||
private List<GhostFrame> _currentRecording = [];
|
|
||||||
private double _startTime = 0.0;
|
|
||||||
private int _currentLevelIndex = -1;
|
|
||||||
|
|
||||||
public void StartRecording(int levelIndex)
|
|
||||||
{
|
|
||||||
if (!IsPlaybackEnabled) return;
|
|
||||||
|
|
||||||
_currentLevelIndex = levelIndex;
|
|
||||||
_currentRecording.Clear();
|
|
||||||
_startTime = Time.GetTicksMsec() / 1000.0;
|
|
||||||
IsRecording = true;
|
|
||||||
GD.Print("Ghost recording started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopRecording(bool levelCompleted, double finalTime)
|
|
||||||
{
|
|
||||||
if (!IsRecording) return;
|
|
||||||
IsRecording = false;
|
|
||||||
|
|
||||||
if (levelCompleted)
|
|
||||||
{
|
|
||||||
var bestTime = LoadBestTime(_currentLevelIndex);
|
|
||||||
if (finalTime < bestTime)
|
|
||||||
{
|
|
||||||
SaveGhostData(_currentLevelIndex, finalTime);
|
|
||||||
GD.Print($"New best ghost saved for level {_currentLevelIndex}. Time: {finalTime}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_currentRecording.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecordFrame(Vector2 position)
|
|
||||||
{
|
|
||||||
if (!IsRecording) return;
|
|
||||||
|
|
||||||
_currentRecording.Add(new GhostFrame
|
|
||||||
{
|
|
||||||
Timestamp = (Time.GetTicksMsec() / 1000.0) - _startTime,
|
|
||||||
Position = position
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SpawnGhostPlayer(int levelIndex, Node parent)
|
|
||||||
{
|
|
||||||
if (!IsPlaybackEnabled || GhostPlayerScene == null) return;
|
|
||||||
|
|
||||||
var ghostData = LoadGhostData(levelIndex);
|
|
||||||
if (ghostData.Count > 0)
|
|
||||||
{
|
|
||||||
var ghostPlayer = GhostPlayerScene.Instantiate<GhostPlayer>();
|
|
||||||
parent.AddChild(ghostPlayer);
|
|
||||||
ghostPlayer.StartPlayback(ghostData);
|
|
||||||
GD.Print($"Ghost player spawned for level {levelIndex}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveGhostData(int levelIndex, double time)
|
|
||||||
{
|
|
||||||
var path = $"user://ghost_level_{levelIndex}.json";
|
|
||||||
var saveData = new GhostSaveData { BestTime = time };
|
|
||||||
foreach (var frame in _currentRecording)
|
|
||||||
saveData.Frames.Add(new GhostFrameDto { Timestamp = frame.Timestamp, X = frame.Position.X, Y = frame.Position.Y });
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(saveData, SaveSystem.JsonOptions);
|
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
|
||||||
file.StoreString(json);
|
|
||||||
}
|
|
||||||
catch (System.Exception e)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"GhostManager: Failed to save ghost data: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<GhostFrame> LoadGhostData(int levelIndex)
|
|
||||||
{
|
|
||||||
var path = $"user://ghost_level_{levelIndex}.json";
|
|
||||||
if (!FileAccess.FileExists(path)) return [];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
|
||||||
var saveData = JsonSerializer.Deserialize<GhostSaveData>(file.GetAsText(), SaveSystem.JsonOptions);
|
|
||||||
if (saveData == null) return [];
|
|
||||||
|
|
||||||
var frames = new List<GhostFrame>();
|
|
||||||
foreach (var dto in saveData.Frames)
|
|
||||||
frames.Add(new GhostFrame { Timestamp = dto.Timestamp, Position = new Vector2(dto.X, dto.Y) });
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
catch (System.Exception e)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"GhostManager: Failed to load ghost data: {e.Message}");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private double LoadBestTime(int levelIndex)
|
|
||||||
{
|
|
||||||
var path = $"user://ghost_level_{levelIndex}.json";
|
|
||||||
if (!FileAccess.FileExists(path)) return double.MaxValue;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
|
||||||
var saveData = JsonSerializer.Deserialize<GhostSaveData>(file.GetAsText(), SaveSystem.JsonOptions);
|
|
||||||
return saveData?.BestTime ?? double.MaxValue;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return double.MaxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cgmuod4p2hg5h
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class InputDeviceManager : Node
|
|
||||||
{
|
|
||||||
public enum InputDevice { Mouse, Keyboard, Gamepad }
|
|
||||||
public static InputDeviceManager Instance { get; private set; }
|
|
||||||
|
|
||||||
public InputDevice CurrentDevice { get; private set; } = InputDevice.Mouse;
|
|
||||||
|
|
||||||
public bool IsPointerless => CurrentDevice is InputDevice.Keyboard or InputDevice.Gamepad;
|
|
||||||
|
|
||||||
[Signal] public delegate void DeviceChangedEventHandler(int device);
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
Input.MouseMode = Input.MouseModeEnum.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Input(InputEvent @event)
|
|
||||||
{
|
|
||||||
InputDevice detected;
|
|
||||||
|
|
||||||
if (@event is InputEventMouseMotion or InputEventMouseButton)
|
|
||||||
detected = InputDevice.Mouse;
|
|
||||||
else if (@event is InputEventKey)
|
|
||||||
detected = InputDevice.Keyboard;
|
|
||||||
else if (@event is InputEventJoypadButton)
|
|
||||||
detected = InputDevice.Gamepad;
|
|
||||||
else if (@event is InputEventJoypadMotion joyEvent)
|
|
||||||
{
|
|
||||||
if (Mathf.Abs(joyEvent.AxisValue) <= 0.15f) return;
|
|
||||||
detected = InputDevice.Gamepad;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (detected == CurrentDevice) return;
|
|
||||||
|
|
||||||
CurrentDevice = detected;
|
|
||||||
Input.MouseMode = CurrentDevice == InputDevice.Mouse
|
|
||||||
? Input.MouseModeEnum.Visible
|
|
||||||
: Input.MouseModeEnum.Hidden;
|
|
||||||
|
|
||||||
EmitSignal(SignalName.DeviceChanged, (int)CurrentDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b81610g8fd6p1
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.Progression;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Translates domain events into Godot signals on the <see cref="EventBus"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// The adapter half of ADR-0004. Aggregates raise plain records with no engine dependency; this is the
|
|
||||||
/// single place that turns them into signals, which is what makes "state changed ⇒ signal fired"
|
|
||||||
/// un-bypassable. Existing subscribers and scene wiring keep working unchanged.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Not a Node — it has no lifecycle of its own. The composition root owns one and hands it every
|
|
||||||
/// batch of drained events.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public sealed class ProgressionEventRepublisher
|
|
||||||
{
|
|
||||||
private readonly LevelCatalogue _catalogue;
|
|
||||||
private readonly SceneTree _tree;
|
|
||||||
|
|
||||||
public ProgressionEventRepublisher(LevelCatalogue catalogue, SceneTree tree)
|
|
||||||
{
|
|
||||||
_catalogue = catalogue;
|
|
||||||
_tree = tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Publish(IReadOnlyList<ProgressionEvent> events)
|
|
||||||
{
|
|
||||||
foreach (var @event in events)
|
|
||||||
Publish(@event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Publish(ProgressionEvent @event)
|
|
||||||
{
|
|
||||||
switch (@event)
|
|
||||||
{
|
|
||||||
case LevelStarted started:
|
|
||||||
// The existing signal is keyed by array index because GhostManager names its replay
|
|
||||||
// files ghost_level_{index}.json. Transitional: once ghosts key on LevelId this
|
|
||||||
// becomes a straight pass-through and the index lookup goes away.
|
|
||||||
EventBus.EmitLevelStarted(IndexOf(started.Level), _tree.CurrentScene);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RunEnded:
|
|
||||||
EventBus.EmitRunEnded();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AttemptFailed failed:
|
|
||||||
EventBus.EmitAttemptFailed(failed.Remaining.Value, failed.RunOver);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AchievementUnlocked unlocked:
|
|
||||||
EventBus.EmitAchievementUnlocked(unlocked.Achievement.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LivesChanged lives:
|
|
||||||
EventBus.EmitLivesChanged(lives.Remaining.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TotalCoinsChanged coins:
|
|
||||||
EventBus.EmitCoinsChanged(coins.Total.Value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SkillSetChanged:
|
|
||||||
EventBus.EmitSkillSetChanged();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
GD.PushWarning($"No signal is published for domain event {@event.GetType().Name}.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int IndexOf(LevelId level)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < _catalogue.Levels.Count; i++)
|
|
||||||
if (_catalogue.Levels[i]?.Id == level.Value)
|
|
||||||
return i;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cjm0g50la7uie
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.Progression;
|
|
||||||
using Mr.BrickAdventures.Progression.Adapters;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Composition root for player progression: builds the stores, loads the Profile, and owns the
|
|
||||||
/// <see cref="ProgressionService"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// The only Node in this area. The aggregates are plain objects, so their lifetime is no longer tied
|
|
||||||
/// to the scene tree — which is why the operations below need no null-guarding of the store itself.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Every mutating operation is wrapped so that draining and republishing cannot be forgotten. That is
|
|
||||||
/// the one risk of the DrainEvents design, and wrapping collapses it to this file.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public partial class ProgressionRoot : Node
|
|
||||||
{
|
|
||||||
public static ProgressionRoot Instance { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The one slot the game currently uses. Phase B replaces this with a picker over several
|
|
||||||
/// Playthroughs; until then a fixed id keeps the file name stable across runs.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly PlaythroughId SoleSlot =
|
|
||||||
PlaythroughId.Of(new Guid("a17e0000-0000-4000-8000-000000000001"));
|
|
||||||
|
|
||||||
[Export] public LevelCatalogue Catalogue { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Lives a new Playthrough starts with. A tuning value, so it is authored here rather than baked into the domain.</summary>
|
|
||||||
[Export] public int StartingLives { get; set; } = 3;
|
|
||||||
|
|
||||||
private ProgressionService _service;
|
|
||||||
private ProgressionEventRepublisher _republisher;
|
|
||||||
|
|
||||||
/// <summary>Levels in campaign order, as the domain sees them.</summary>
|
|
||||||
public Campaign Campaign { get; private set; }
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
|
|
||||||
if (Catalogue == null)
|
|
||||||
{
|
|
||||||
GD.PushError("ProgressionRoot has no LevelCatalogue assigned; progression is unavailable.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var problem in Catalogue.Validate())
|
|
||||||
GD.PushError($"LevelCatalogue: {problem}");
|
|
||||||
|
|
||||||
Campaign = Catalogue.ToCampaign();
|
|
||||||
|
|
||||||
var directory = ProjectSettings.GlobalizePath("user://");
|
|
||||||
var playthroughs = new JsonPlaythroughStore(directory);
|
|
||||||
var profiles = new JsonProfileStore(directory);
|
|
||||||
|
|
||||||
_service = new ProgressionService(
|
|
||||||
LoadProfile(profiles), playthroughs, profiles,
|
|
||||||
Campaign, BuildSkillCatalogue(), Lives.Of(StartingLives));
|
|
||||||
|
|
||||||
_republisher = new ProgressionEventRepublisher(Catalogue, GetTree());
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A missing Profile is a first run. A corrupt one is reported loudly and replaced with an empty
|
|
||||||
/// Profile so the game still starts — but the file is left alone, so it can still be recovered.
|
|
||||||
/// </summary>
|
|
||||||
private static Profile LoadProfile(IProfileStore profiles)
|
|
||||||
{
|
|
||||||
var result = profiles.Load();
|
|
||||||
|
|
||||||
if (result.Outcome == LoadOutcome.Corrupt)
|
|
||||||
GD.PushError($"Profile could not be read and has been left untouched: {result.Problem}");
|
|
||||||
|
|
||||||
return result.Outcome == LoadOutcome.Loaded ? result.Value : Profile.Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads the authored upgrade tables into the domain's view of them. A Skill with no upgrade
|
|
||||||
/// entries yields an empty cost list, which the domain reports as not purchasable rather than
|
|
||||||
/// throwing on <c>Upgrades[0]</c>.
|
|
||||||
/// </summary>
|
|
||||||
private static SkillCatalogue BuildSkillCatalogue()
|
|
||||||
{
|
|
||||||
var available = SkillManager.Instance?.AvailableSkills;
|
|
||||||
if (available == null) return SkillCatalogue.Empty();
|
|
||||||
|
|
||||||
var costs = new System.Collections.Generic.Dictionary<SkillId, System.Collections.Generic.IReadOnlyList<Coins>>();
|
|
||||||
|
|
||||||
foreach (var skill in available)
|
|
||||||
{
|
|
||||||
if (skill == null || string.IsNullOrWhiteSpace(skill.Name)) continue;
|
|
||||||
|
|
||||||
var levels = new System.Collections.Generic.List<Coins>();
|
|
||||||
foreach (var upgrade in skill.Upgrades)
|
|
||||||
levels.Add(Coins.Of(Mathf.Max(0, upgrade.Cost)));
|
|
||||||
|
|
||||||
costs[SkillId.Of(skill.Name)] = levels;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkillCatalogue.Of(costs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Operations — each drains and republishes, so callers cannot forget to
|
|
||||||
|
|
||||||
public void StartPlaythrough(PlaythroughId id) => Run(() => _service.StartPlaythrough(id));
|
|
||||||
|
|
||||||
public bool LoadPlaythrough(PlaythroughId id)
|
|
||||||
{
|
|
||||||
if (_service == null) return false;
|
|
||||||
|
|
||||||
var loaded = _service.LoadPlaythrough(id);
|
|
||||||
Publish();
|
|
||||||
return loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoadOutcome PeekPlaythrough(PlaythroughId id) =>
|
|
||||||
_service?.PeekPlaythrough(id) ?? LoadOutcome.Absent;
|
|
||||||
|
|
||||||
public void RecordStat(StatName stat, int amount = 1) => _service?.RecordStat(stat, amount);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Reads
|
|
||||||
|
|
||||||
public bool IsPlaying => _service?.IsPlaying ?? false;
|
|
||||||
public bool CanEnter(LevelId level) => _service?.CanEnter(level) ?? false;
|
|
||||||
public LevelId? CurrentLevel => _service?.CurrentLevel;
|
|
||||||
public Coins TotalCoins => _service?.TotalCoins ?? Coins.Zero;
|
|
||||||
public Lives Lives => _service?.Lives ?? Lives.Of(0);
|
|
||||||
public bool IsRunOver => _service?.IsRunOver ?? false;
|
|
||||||
public SkillLevel? LevelOf(SkillId skill) => _service?.LevelOf(skill);
|
|
||||||
public bool IsOwned(SkillId skill) => _service?.IsOwned(skill) ?? false;
|
|
||||||
public bool CanAfford(SkillId skill) => _service?.CanAfford(skill) ?? false;
|
|
||||||
public bool HasCleared(LevelId level) => _service?.HasCleared(level) ?? false;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Operations, continued
|
|
||||||
|
|
||||||
public bool EnterLevel(LevelId level)
|
|
||||||
{
|
|
||||||
if (_service == null) return false;
|
|
||||||
|
|
||||||
var entered = _service.EnterLevel(level);
|
|
||||||
Publish();
|
|
||||||
return entered;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RecordCoinsFound(Coins amount) => Run(() => _service.RecordCoinsFound(amount));
|
|
||||||
|
|
||||||
public void HandlePlayerDeath() => Run(() => _service.HandlePlayerDeath());
|
|
||||||
|
|
||||||
public void FinishLevel(LevelTime? time) => Run(() => _service.FinishLevel(time));
|
|
||||||
|
|
||||||
public PurchaseOutcome PurchaseSkill(SkillId skill)
|
|
||||||
{
|
|
||||||
if (_service == null) return PurchaseOutcome.NotPlaying;
|
|
||||||
|
|
||||||
var outcome = _service.PurchaseSkill(skill);
|
|
||||||
Publish();
|
|
||||||
return outcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Free unlock, as a pickup gives. Returns false if it was already owned.</summary>
|
|
||||||
public bool GrantSkill(SkillId skill)
|
|
||||||
{
|
|
||||||
if (_service == null) return false;
|
|
||||||
|
|
||||||
var granted = _service.GrantSkill(skill);
|
|
||||||
Publish();
|
|
||||||
return granted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Takes a Skill away — for Skills that consume themselves.</summary>
|
|
||||||
public bool RevokeSkill(SkillId skill)
|
|
||||||
{
|
|
||||||
if (_service == null) return false;
|
|
||||||
|
|
||||||
var revoked = _service.RevokeSkill(skill);
|
|
||||||
Publish();
|
|
||||||
return revoked;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Discards the current Attempt without charging a life — leaving a Level, not dying.</summary>
|
|
||||||
public void AbandonAttempt() => Run(() => _service.AbandonAttempt());
|
|
||||||
|
|
||||||
/// <summary>Console only — see <see cref="ProgressionService.ApplyCheat"/>.</summary>
|
|
||||||
public void ApplyCheat(Func<PlaythroughSnapshot, PlaythroughSnapshot> change) =>
|
|
||||||
Run(() => _service.ApplyCheat(change));
|
|
||||||
|
|
||||||
/// <summary>Whether there is a resumable Playthrough, for enabling the Continue button.</summary>
|
|
||||||
public bool HasSavedPlaythrough =>
|
|
||||||
PeekPlaythrough(SoleSlot) == LoadOutcome.Loaded;
|
|
||||||
|
|
||||||
private void Run(System.Action operation)
|
|
||||||
{
|
|
||||||
if (_service == null) return;
|
|
||||||
|
|
||||||
operation();
|
|
||||||
Publish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Publish() => _republisher?.Publish(_service.DrainEvents());
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b6amqp58yjfc4
|
|
||||||
@@ -1,176 +1,61 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
using Mr.BrickAdventures.scripts.Resources;
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Save system that serializes state to JSON using DTOs.
|
|
||||||
/// </summary>
|
|
||||||
public partial class SaveSystem : Node
|
public partial class SaveSystem : Node
|
||||||
{
|
{
|
||||||
[Export] public string SavePath { get; set; } = "user://savegame.json";
|
[Export] public string SavePath { get; set; } = "user://savegame.save";
|
||||||
[Export] public int Version { get; set; } = 2;
|
[Export] public int Version { get; set; } = 1;
|
||||||
|
|
||||||
public static SaveSystem Instance { get; private set; }
|
private GameManager _gameManager;
|
||||||
|
|
||||||
internal static readonly JsonSerializerOptions JsonOptions = new()
|
|
||||||
{
|
|
||||||
WriteIndented = true,
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
Instance = this;
|
_gameManager = GetNode<GameManager>("/root/GameManager");
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveGame()
|
public void SaveGame()
|
||||||
{
|
{
|
||||||
var store = GameStateStore.Instance;
|
var saveData = new Dictionary
|
||||||
if (store == null)
|
|
||||||
{
|
{
|
||||||
GD.PrintErr("SaveSystem: GameStateStore not available.");
|
{ "player_state", _gameManager.PlayerState},
|
||||||
return;
|
{ "version", Version}
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to DTO (only serializable data)
|
|
||||||
var saveData = new SaveDataDto
|
|
||||||
{
|
|
||||||
Version = Version,
|
|
||||||
Coins = store.Player.Coins,
|
|
||||||
Lives = store.Player.Lives,
|
|
||||||
CurrentLevel = store.Player.CurrentLevel,
|
|
||||||
CompletedLevels = [.. store.Player.CompletedLevels],
|
|
||||||
UnlockedLevels = new List<int>(store.Player.UnlockedLevels),
|
|
||||||
UnlockedSkillNames = GetSkillNames(store.Player.UnlockedSkills),
|
|
||||||
UnlockedAchievements = new List<string>(store.Player.UnlockedAchievements),
|
|
||||||
Statistics = new Dictionary<string, int>(store.Player.Statistics)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write);
|
||||||
{
|
file.StoreVar(saveData);
|
||||||
var json = JsonSerializer.Serialize(saveData, JsonOptions);
|
GD.Print("Game state saved to: ", SavePath);
|
||||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write);
|
|
||||||
file.StoreString(json);
|
|
||||||
GD.Print("Game saved to: ", SavePath);
|
|
||||||
EventBus.EmitGameSaved();
|
|
||||||
}
|
|
||||||
catch (System.Exception e)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"SaveSystem: Failed to save game: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadGame()
|
public bool LoadGame()
|
||||||
{
|
{
|
||||||
if (!FileAccess.FileExists(SavePath))
|
if (!FileAccess.FileExists(SavePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
|
||||||
|
var saveDataObj = (Dictionary)file.GetVar();
|
||||||
|
|
||||||
|
if (saveDataObj.ContainsKey("version") && (int)saveDataObj["version"] != Version)
|
||||||
{
|
{
|
||||||
GD.Print("SaveSystem: No save file found.");
|
GD.Print($"Save file version mismatch. Expected: {Version}, Found: {saveDataObj["version"]}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
GD.Print("Game state loaded from: ", SavePath);
|
||||||
|
GD.Print("Player state: ", saveDataObj["player_state"]);
|
||||||
|
_gameManager.PlayerState = (Dictionary)saveDataObj["player_state"];
|
||||||
|
|
||||||
|
var skills = new Array<SkillData>();
|
||||||
|
foreach (var skill in (Array<SkillData>)_gameManager.PlayerState["unlocked_skills"])
|
||||||
{
|
{
|
||||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Read);
|
skills.Add(skill);
|
||||||
var json = file.GetAsText();
|
|
||||||
var saveData = JsonSerializer.Deserialize<SaveDataDto>(json, JsonOptions);
|
|
||||||
|
|
||||||
if (saveData == null)
|
|
||||||
{
|
|
||||||
GD.PrintErr("SaveSystem: Failed to deserialize save data.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveData.Version != Version)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"SaveSystem: Version mismatch. Expected {Version}, found {saveData.Version}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var store = GameStateStore.Instance;
|
|
||||||
if (store == null)
|
|
||||||
{
|
|
||||||
GD.PrintErr("SaveSystem: GameStateStore not available.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply loaded state
|
|
||||||
store.Player.Coins = saveData.Coins;
|
|
||||||
store.Player.Lives = saveData.Lives;
|
|
||||||
store.Player.CurrentLevel = saveData.CurrentLevel;
|
|
||||||
|
|
||||||
store.Player.CompletedLevels = saveData.CompletedLevels ?? new List<int>();
|
|
||||||
store.Player.UnlockedLevels = saveData.UnlockedLevels ?? new List<int> { 0 };
|
|
||||||
store.Player.UnlockedAchievements = saveData.UnlockedAchievements ?? new List<string>();
|
|
||||||
store.Player.Statistics = saveData.Statistics ?? new Dictionary<string, int>();
|
|
||||||
|
|
||||||
// Reload skills by name from SkillManager
|
|
||||||
store.Player.UnlockedSkills = LoadSkillsByName(saveData.UnlockedSkillNames);
|
|
||||||
|
|
||||||
GD.Print("Game loaded from: ", SavePath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (System.Exception e)
|
|
||||||
{
|
|
||||||
GD.PrintErr($"SaveSystem: Failed to load game: {e.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> GetSkillNames(List<SkillData> skills)
|
|
||||||
{
|
|
||||||
var names = new List<string>();
|
|
||||||
foreach (var skill in skills)
|
|
||||||
{
|
|
||||||
if (skill != null)
|
|
||||||
names.Add(skill.Name);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SkillData> LoadSkillsByName(List<string> skillNames)
|
|
||||||
{
|
|
||||||
var skills = new List<SkillData>();
|
|
||||||
if (skillNames == null) return skills;
|
|
||||||
|
|
||||||
var skillManager = SkillManager.Instance;
|
|
||||||
if (skillManager == null)
|
|
||||||
{
|
|
||||||
GD.PrintErr("SaveSystem: SkillManager not available to resolve skill names.");
|
|
||||||
return skills;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var name in skillNames)
|
_gameManager.UnlockSkills(skills);
|
||||||
{
|
return true;
|
||||||
var skill = skillManager.GetSkillByName(name);
|
|
||||||
if (skill != null)
|
|
||||||
{
|
|
||||||
skills.Add(skill);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GD.PrintErr($"SaveSystem: Skill '{name}' not found in SkillManager.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return skills;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckSaveExists() => FileAccess.FileExists(SavePath);
|
public bool CheckSaveExists() => FileAccess.FileExists(SavePath);
|
||||||
|
|
||||||
public void DeleteSave()
|
|
||||||
{
|
|
||||||
if (FileAccess.FileExists(SavePath))
|
|
||||||
{
|
|
||||||
DirAccess.RemoveAbsolute(ProjectSettings.GlobalizePath(SavePath));
|
|
||||||
GD.Print("Save file deleted.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class SettingsManager : Node
|
|
||||||
{
|
|
||||||
public static SettingsManager Instance { get; private set; }
|
|
||||||
|
|
||||||
public float GamepadDeadzone { get; set; } = 0.2f;
|
|
||||||
public float GamepadSensitivity { get; set; } = 1.0f;
|
|
||||||
public string WindowMode { get; set; } = "fullscreen";
|
|
||||||
public Vector2I Resolution { get; set; } = new Vector2I(1920, 1080);
|
|
||||||
|
|
||||||
private static readonly List<Vector2I> CuratedResolutions = new()
|
|
||||||
{
|
|
||||||
// 4:3
|
|
||||||
new Vector2I(640, 480),
|
|
||||||
new Vector2I(800, 600),
|
|
||||||
new Vector2I(1024, 768),
|
|
||||||
new Vector2I(1280, 960),
|
|
||||||
new Vector2I(1600, 1200),
|
|
||||||
// 16:9
|
|
||||||
new Vector2I(1280, 720),
|
|
||||||
new Vector2I(1366, 768),
|
|
||||||
new Vector2I(1600, 900),
|
|
||||||
new Vector2I(1920, 1080),
|
|
||||||
new Vector2I(2560, 1440),
|
|
||||||
new Vector2I(3840, 2160),
|
|
||||||
// 16:10
|
|
||||||
new Vector2I(1280, 800),
|
|
||||||
new Vector2I(1440, 900),
|
|
||||||
new Vector2I(1920, 1200),
|
|
||||||
new Vector2I(2560, 1600),
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly List<Vector2I> _customResolutions = new();
|
|
||||||
|
|
||||||
private static readonly string[] DeadzoneActions = { "left", "right", "up", "down" };
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
|
|
||||||
if (ConfigFileHandler.Instance == null)
|
|
||||||
{
|
|
||||||
GD.PushError("SettingsManager: ConfigFileHandler.Instance is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg = ConfigFileHandler.Instance.SettingsConfig;
|
|
||||||
|
|
||||||
// --- display_settings ---
|
|
||||||
WindowMode = cfg.GetValue("display_settings", "window_mode", Variant.From("fullscreen")).AsString();
|
|
||||||
var resStr = cfg.GetValue("display_settings", "resolution", Variant.From("1920x1080")).AsString();
|
|
||||||
Resolution = ParseResolution(resStr, new Vector2I(1920, 1080));
|
|
||||||
|
|
||||||
var customResStr = cfg.GetValue("display_settings", "custom_resolutions", Variant.From("")).AsString();
|
|
||||||
if (!string.IsNullOrWhiteSpace(customResStr))
|
|
||||||
{
|
|
||||||
foreach (var part in customResStr.Split(','))
|
|
||||||
{
|
|
||||||
var r = ParseResolution(part.Trim(), Vector2I.Zero);
|
|
||||||
if (r != Vector2I.Zero)
|
|
||||||
_customResolutions.Add(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyDisplaySettings();
|
|
||||||
|
|
||||||
// --- gameplay_settings ---
|
|
||||||
GamepadDeadzone = (float)cfg.GetValue("gameplay_settings", "gamepad_deadzone", Variant.From(0.2)).AsDouble();
|
|
||||||
GamepadSensitivity = (float)cfg.GetValue("gameplay_settings", "gamepad_sensitivity", Variant.From(1.0)).AsDouble();
|
|
||||||
|
|
||||||
ApplyGamepadDeadzone();
|
|
||||||
|
|
||||||
// --- input_settings ---
|
|
||||||
ApplyInputBindings(cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── public API ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public List<Vector2I> GetAllResolutions()
|
|
||||||
{
|
|
||||||
var all = new List<Vector2I>(CuratedResolutions);
|
|
||||||
all.AddRange(_customResolutions);
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveDisplaySettings()
|
|
||||||
{
|
|
||||||
var cfg = ConfigFileHandler.Instance.SettingsConfig;
|
|
||||||
cfg.SetValue("display_settings", "window_mode", WindowMode);
|
|
||||||
cfg.SetValue("display_settings", "resolution", $"{Resolution.X}x{Resolution.Y}");
|
|
||||||
cfg.SetValue("display_settings", "custom_resolutions", BuildCustomResolutionsString());
|
|
||||||
cfg.Save(ConfigFileHandler.SettingsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveGameplaySettings()
|
|
||||||
{
|
|
||||||
var cfg = ConfigFileHandler.Instance.SettingsConfig;
|
|
||||||
cfg.SetValue("gameplay_settings", "gamepad_deadzone", (double)GamepadDeadzone);
|
|
||||||
cfg.SetValue("gameplay_settings", "gamepad_sensitivity", (double)GamepadSensitivity);
|
|
||||||
cfg.Save(ConfigFileHandler.SettingsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveInputSettings()
|
|
||||||
{
|
|
||||||
var cfg = ConfigFileHandler.Instance.SettingsConfig;
|
|
||||||
foreach (var action in InputMap.GetActions())
|
|
||||||
{
|
|
||||||
if (action.ToString().StartsWith("ui_")) continue;
|
|
||||||
foreach (var ev in InputMap.ActionGetEvents(action))
|
|
||||||
{
|
|
||||||
if (ev is InputEventKey key)
|
|
||||||
{
|
|
||||||
cfg.SetValue("input_settings", action, (long)key.PhysicalKeycode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg.Save(ConfigFileHandler.SettingsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── apply helpers ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
public void ApplyDisplaySettings()
|
|
||||||
{
|
|
||||||
switch (WindowMode)
|
|
||||||
{
|
|
||||||
case "fullscreen":
|
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
|
||||||
break;
|
|
||||||
case "borderless":
|
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
|
||||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
|
|
||||||
DisplayServer.WindowSetSize(Resolution);
|
|
||||||
break;
|
|
||||||
default: // "windowed"
|
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
|
||||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
|
||||||
DisplayServer.WindowSetSize(Resolution);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyGamepadDeadzone()
|
|
||||||
{
|
|
||||||
foreach (var action in DeadzoneActions)
|
|
||||||
{
|
|
||||||
if (InputMap.HasAction(action))
|
|
||||||
InputMap.ActionSetDeadzone(action, GamepadDeadzone);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ApplyInputBindings(ConfigFile cfg)
|
|
||||||
{
|
|
||||||
if (!cfg.HasSection("input_settings")) return;
|
|
||||||
|
|
||||||
foreach (var actionName in cfg.GetSectionKeys("input_settings"))
|
|
||||||
{
|
|
||||||
if (actionName.StartsWith("ui_")) continue;
|
|
||||||
if (!InputMap.HasAction(actionName)) continue;
|
|
||||||
|
|
||||||
var scancode = (Key)(long)cfg.GetValue("input_settings", actionName);
|
|
||||||
|
|
||||||
// Remove existing keyboard events for this action
|
|
||||||
var events = InputMap.ActionGetEvents(actionName);
|
|
||||||
foreach (var ev in events)
|
|
||||||
{
|
|
||||||
if (ev is InputEventKey)
|
|
||||||
InputMap.ActionEraseEvent(actionName, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the saved key
|
|
||||||
var newKey = new InputEventKey { PhysicalKeycode = scancode };
|
|
||||||
InputMap.ActionAddEvent(actionName, newKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── util ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private static Vector2I ParseResolution(string s, Vector2I fallback)
|
|
||||||
{
|
|
||||||
var parts = s.Split('x');
|
|
||||||
if (parts.Length == 2 && int.TryParse(parts[0], out var w) && int.TryParse(parts[1], out var h))
|
|
||||||
return new Vector2I(w, h);
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string BuildCustomResolutionsString()
|
|
||||||
{
|
|
||||||
var parts = new List<string>();
|
|
||||||
foreach (var r in _customResolutions)
|
|
||||||
parts.Add($"{r.X}x{r.Y}");
|
|
||||||
return string.Join(",", parts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://d4jyxtm4jgtka
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Godot;
|
|
||||||
using Godot.Collections;
|
|
||||||
using Mr.BrickAdventures.Progression;
|
|
||||||
using Mr.BrickAdventures.scripts.components;
|
|
||||||
using Mr.BrickAdventures.scripts.interfaces;
|
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Owns the live skill components attached to the player, and keeps them matching what the Playthrough
|
|
||||||
/// says is owned.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// Reconciles from <see cref="EventBus.SkillSetChanged"/> rather than being told by each caller. The old
|
|
||||||
/// arrangement had four call sites doing "record it in the store, then tell SkillManager" as two
|
|
||||||
/// separate calls — anything between them, including <c>AddSkill</c>'s own early return when no player
|
|
||||||
/// is registered, left a skill owned but inert.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Tracks the level each component was built at, so an upgrade rebuilds it. Previously
|
|
||||||
/// <c>AddSkill</c> returned early for an already-active skill, and the upgrade was applied by the
|
|
||||||
/// purchase code reaching into the component instead.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public partial class SkillManager : Node
|
|
||||||
{
|
|
||||||
private PlayerController _player;
|
|
||||||
|
|
||||||
[Export] public Array<SkillData> AvailableSkills { get; set; } = [];
|
|
||||||
|
|
||||||
public Dictionary ActiveComponents { get; private set; } = new();
|
|
||||||
|
|
||||||
private readonly System.Collections.Generic.Dictionary<string, int> _activeLevels = new();
|
|
||||||
|
|
||||||
public static SkillManager Instance { get; private set; }
|
|
||||||
|
|
||||||
[Signal]
|
|
||||||
public delegate void ActiveThrowSkillChangedEventHandler(BrickThrowComponent throwComponent);
|
|
||||||
[Signal]
|
|
||||||
public delegate void SkillRemovedEventHandler(SkillData skillData);
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
EventBus.Instance.SkillSetChanged += Reconcile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (EventBus.Instance != null)
|
|
||||||
EventBus.Instance.SkillSetChanged -= Reconcile;
|
|
||||||
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called by the PlayerController from its _Ready method to register itself with the manager.
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterPlayer(PlayerController player)
|
|
||||||
{
|
|
||||||
if (_player == player) return;
|
|
||||||
|
|
||||||
if (_player != null && IsInstanceValid(_player))
|
|
||||||
UnregisterPlayer();
|
|
||||||
|
|
||||||
_player = player;
|
|
||||||
if (_player != null)
|
|
||||||
{
|
|
||||||
_player.TreeExiting += UnregisterPlayer;
|
|
||||||
Reconcile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnregisterPlayer()
|
|
||||||
{
|
|
||||||
if (_player != null && IsInstanceValid(_player))
|
|
||||||
{
|
|
||||||
_player.TreeExiting -= UnregisterPlayer;
|
|
||||||
RemoveAllActiveSkills();
|
|
||||||
}
|
|
||||||
_player = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Brings the live components in line with what is owned: adds what is missing, rebuilds anything
|
|
||||||
/// whose level changed, removes what is no longer owned.
|
|
||||||
/// </summary>
|
|
||||||
public void Reconcile()
|
|
||||||
{
|
|
||||||
if (_player == null || !IsInstanceValid(_player)) return;
|
|
||||||
|
|
||||||
var progression = ProgressionRoot.Instance;
|
|
||||||
if (progression == null) return;
|
|
||||||
|
|
||||||
foreach (var skill in AvailableSkills)
|
|
||||||
{
|
|
||||||
if (skill == null || string.IsNullOrWhiteSpace(skill.Name)) continue;
|
|
||||||
|
|
||||||
var owned = progression.LevelOf(SkillId.Of(skill.Name))?.Value;
|
|
||||||
var active = _activeLevels.TryGetValue(skill.Name, out var at) ? at : (int?)null;
|
|
||||||
|
|
||||||
if (owned == active) continue;
|
|
||||||
|
|
||||||
if (active is not null) RemoveSkill(skill.Name);
|
|
||||||
if (owned is not null) AddSkill(skill, owned.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Kept for the marketplace's activate/deactivate toggle, which is a display concern.</summary>
|
|
||||||
public void AddSkill(SkillData skillData) =>
|
|
||||||
AddSkill(skillData, ProgressionRoot.Instance?.LevelOf(SkillId.Of(skillData.Name))?.Value ?? 1);
|
|
||||||
|
|
||||||
private void AddSkill(SkillData skillData, int level)
|
|
||||||
{
|
|
||||||
if (_player == null || !IsInstanceValid(_player))
|
|
||||||
{
|
|
||||||
GD.Print("SkillManager: Player not available to add skill.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActiveComponents.ContainsKey(skillData.Name))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (skillData.Type == SkillType.Throw)
|
|
||||||
{
|
|
||||||
foreach (var sd in AvailableSkills)
|
|
||||||
{
|
|
||||||
if (sd != skillData && sd?.Type == SkillType.Throw)
|
|
||||||
RemoveSkill(sd.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var instance = skillData.Node.Instantiate();
|
|
||||||
if (instance is ISkill skill)
|
|
||||||
{
|
|
||||||
skill.Initialize(_player, skillData, level);
|
|
||||||
skill.Activate();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GD.PrintErr($"Skill scene for '{skillData.Name}' does not implement ISkill!");
|
|
||||||
instance.QueueFree();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_player.AddChild(instance);
|
|
||||||
ActiveComponents[skillData.Name] = instance;
|
|
||||||
_activeLevels[skillData.Name] = level;
|
|
||||||
|
|
||||||
if (instance is BrickThrowComponent btc)
|
|
||||||
EmitSignalActiveThrowSkillChanged(btc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSkill(string skillName)
|
|
||||||
{
|
|
||||||
_activeLevels.Remove(skillName);
|
|
||||||
|
|
||||||
if (!ActiveComponents.TryGetValue(skillName, out var component))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (component.AsGodotObject() is BrickThrowComponent)
|
|
||||||
EmitSignalActiveThrowSkillChanged(null);
|
|
||||||
|
|
||||||
var inst = (Node)component;
|
|
||||||
if (inst is ISkill skill)
|
|
||||||
skill.Deactivate();
|
|
||||||
|
|
||||||
if (IsInstanceValid(inst))
|
|
||||||
inst.QueueFree();
|
|
||||||
|
|
||||||
ActiveComponents.Remove(skillName);
|
|
||||||
var sd = GetSkillByName(skillName);
|
|
||||||
if (sd != null) EmitSignalSkillRemoved(sd);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveAllActiveSkills()
|
|
||||||
{
|
|
||||||
foreach (var skillName in ActiveComponents.Keys.Select(k => k.ToString()).ToList())
|
|
||||||
RemoveSkill(skillName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SkillData GetSkillByName(string skillName)
|
|
||||||
{
|
|
||||||
foreach (var sd in AvailableSkills)
|
|
||||||
if (sd?.Name == skillName) return sd;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSkillActive(SkillData skill) =>
|
|
||||||
skill != null && ActiveComponents.ContainsKey(skill.Name);
|
|
||||||
|
|
||||||
public void ActivateSkill(SkillData skill)
|
|
||||||
{
|
|
||||||
if (!ActiveComponents.ContainsKey(skill.Name))
|
|
||||||
AddSkill(skill);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeactivateSkill(SkillData skill)
|
|
||||||
{
|
|
||||||
if (ActiveComponents.ContainsKey(skill.Name))
|
|
||||||
RemoveSkill(skill.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleSkillActivation(SkillData skill)
|
|
||||||
{
|
|
||||||
if (skill == null) return;
|
|
||||||
|
|
||||||
if (ActiveComponents.ContainsKey(skill.Name))
|
|
||||||
DeactivateSkill(skill);
|
|
||||||
else
|
|
||||||
ActivateSkill(skill);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://dru77vj07e18s
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class SpeedRunManager : Node
|
|
||||||
{
|
|
||||||
public static SpeedRunManager Instance { get; private set; }
|
|
||||||
|
|
||||||
public override void _EnterTree() => Instance = this;
|
|
||||||
public override void _ExitTree() { if (Instance == this) Instance = null; }
|
|
||||||
|
|
||||||
public bool IsRunning { get; private set; } = false;
|
|
||||||
|
|
||||||
private double _startTime;
|
|
||||||
private double _levelStartTime;
|
|
||||||
private List<double> _splits = [];
|
|
||||||
|
|
||||||
[Signal] public delegate void TimeUpdatedEventHandler(double totalTime, double levelTime);
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
if (!IsRunning) return;
|
|
||||||
|
|
||||||
EmitSignalTimeUpdated(GetCurrentTotalTime(), GetCurrentLevelTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartTimer()
|
|
||||||
{
|
|
||||||
_startTime = Time.GetTicksMsec() / 1000.0;
|
|
||||||
_levelStartTime = _startTime;
|
|
||||||
_splits.Clear();
|
|
||||||
IsRunning = true;
|
|
||||||
GD.Print("Speedrun timer started.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopTimer()
|
|
||||||
{
|
|
||||||
if (!IsRunning) return;
|
|
||||||
IsRunning = false;
|
|
||||||
var finalTime = GetCurrentTotalTime();
|
|
||||||
GD.Print($"Speedrun finished. Final time: {FormatTime(finalTime)}");
|
|
||||||
|
|
||||||
// Save personal best if applicable
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Split()
|
|
||||||
{
|
|
||||||
if (!IsRunning) return;
|
|
||||||
|
|
||||||
var now = Time.GetTicksMsec() / 1000.0;
|
|
||||||
var splitTime = now - _levelStartTime;
|
|
||||||
_splits.Add(splitTime);
|
|
||||||
_levelStartTime = now;
|
|
||||||
GD.Print($"Split recorded: {FormatTime(splitTime)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public double GetCurrentTotalTime() => IsRunning ? (Time.GetTicksMsec() / 1000.0) - _startTime : 0;
|
|
||||||
public double GetCurrentLevelTime() => IsRunning ? (Time.GetTicksMsec() / 1000.0) - _levelStartTime : 0;
|
|
||||||
|
|
||||||
public static string FormatTime(double time)
|
|
||||||
{
|
|
||||||
var span = System.TimeSpan.FromSeconds(time);
|
|
||||||
return $"{(int)span.TotalMinutes:00}:{span.Seconds:00}.{span.Milliseconds:000}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://c6ohe36xw1h21
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Godot;
|
|
||||||
using Steamworks;
|
|
||||||
using Steamworks.Data;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
public partial class SteamManager : Node
|
|
||||||
{
|
|
||||||
private const uint AppId = 3575090;
|
|
||||||
|
|
||||||
public static string PlayerName { get; private set; } = "Player";
|
|
||||||
public static bool IsSteamInitialized { get; private set; } = false;
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SteamClient.Init(AppId);
|
|
||||||
IsSteamInitialized = true;
|
|
||||||
|
|
||||||
PlayerName = SteamClient.Name;
|
|
||||||
|
|
||||||
GD.Print($"Steam initialized successfully for user: {PlayerName}");
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
GD.PushError("Failed to initialize Steamworks. Is Steam running?");
|
|
||||||
GD.PushError(e.Message);
|
|
||||||
IsSteamInitialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
|
||||||
{
|
|
||||||
if (IsSteamInitialized) SteamClient.RunCallbacks();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Notification(int what)
|
|
||||||
{
|
|
||||||
if (what == NotificationWMCloseRequest)
|
|
||||||
{
|
|
||||||
if (IsSteamInitialized)
|
|
||||||
{
|
|
||||||
SteamClient.Shutdown();
|
|
||||||
}
|
|
||||||
GetTree().Quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UnlockAchievement(string achievementId)
|
|
||||||
{
|
|
||||||
if (!IsSteamInitialized)
|
|
||||||
{
|
|
||||||
GD.Print($"Steam not initialized. Cannot unlock achievement '{achievementId}'.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ach = new Achievement(achievementId);
|
|
||||||
|
|
||||||
if (ach.State)
|
|
||||||
{
|
|
||||||
GD.Print($"Achievement '{achievementId}' is already unlocked.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ach.Trigger())
|
|
||||||
{
|
|
||||||
SteamUserStats.StoreStats();
|
|
||||||
GD.Print($"Successfully triggered achievement: {ach.Name} ({ach.Identifier})");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GD.PrintErr($"Failed to trigger achievement: {achievementId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://b5abjlnbia63q
|
|
||||||
@@ -1,22 +1,11 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
using Godot.Collections;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
public partial class UIManager : Node
|
public partial class UIManager : Node
|
||||||
{
|
{
|
||||||
public static UIManager Instance { get; private set; }
|
[Export] public Array<Control> UiStack { get; set; } = new();
|
||||||
|
|
||||||
private readonly System.Collections.Generic.List<Control> UiStack = new();
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
Instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
if (Instance == this) Instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Signal] public delegate void ScreenPushedEventHandler(Control screen);
|
[Signal] public delegate void ScreenPushedEventHandler(Control screen);
|
||||||
[Signal] public delegate void ScreenPoppedEventHandler(Control screen);
|
[Signal] public delegate void ScreenPoppedEventHandler(Control screen);
|
||||||
@@ -45,19 +34,19 @@ public partial class UIManager : Node
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var top = UiStack[^1];
|
var top = (Control)UiStack[^1];
|
||||||
UiStack.RemoveAt(UiStack.Count - 1);
|
UiStack.RemoveAt(UiStack.Count - 1);
|
||||||
top.Hide();
|
top.Hide();
|
||||||
top.SetProcessInput(false);
|
top.SetProcessInput(false);
|
||||||
EmitSignalScreenPopped(top);
|
EmitSignalScreenPopped(top);
|
||||||
top.AcceptEvent();
|
top.AcceptEvent();
|
||||||
|
|
||||||
if (UiStack.Count > 0) UiStack[^1].GrabFocus();
|
if (UiStack.Count > 0) ((Control)UiStack[^1]).GrabFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Control TopScreen() => UiStack.Count > 0 ? UiStack[^1] : null;
|
public Control TopScreen() => UiStack.Count > 0 ? (Control)UiStack[^1] : null;
|
||||||
|
|
||||||
public bool IsScreenOnTop(Control screen) => UiStack.Count > 0 && UiStack[^1] == screen;
|
public bool IsScreenOnTop(Control screen) => UiStack.Count > 0 && (Control)UiStack[^1] == screen;
|
||||||
|
|
||||||
public bool IsVisibleOnStack(Control screen) => UiStack.Contains(screen) && screen.Visible;
|
public bool IsVisibleOnStack(Control screen) => UiStack.Contains(screen) && screen.Visible;
|
||||||
|
|
||||||
|
|||||||
103
CONTEXT.md
103
CONTEXT.md
@@ -1,103 +0,0 @@
|
|||||||
# Mr. Brick Adventures
|
|
||||||
|
|
||||||
A 2D platformer. This context covers **player state and progression** — what the player has, what they have done,
|
|
||||||
and at what point each of those becomes permanent. Gameplay behaviour (movement, combat, hazards) lives in the
|
|
||||||
composable components and is deliberately outside this context.
|
|
||||||
|
|
||||||
## Language
|
|
||||||
|
|
||||||
### Scopes
|
|
||||||
|
|
||||||
Three lifetimes. Every fact in this context belongs to exactly one.
|
|
||||||
|
|
||||||
**Attempt**:
|
|
||||||
One try at a level, from entering it to either completing it or losing a life. Holds a ledger of everything that
|
|
||||||
happened during the try — coins found, coins spent, skills bought. Discarded whole if the player dies.
|
|
||||||
_Avoid_: Session, run, life
|
|
||||||
|
|
||||||
**Playthrough**:
|
|
||||||
One game from New Game until game over, identified by a `PlaythroughId`. Holds lives, banked coins, owned skills
|
|
||||||
and their levels, campaign position, and which levels it has cleared. Several exist at once and the player picks
|
|
||||||
between them.
|
|
||||||
_Avoid_: Save, save slot, progress, game, run
|
|
||||||
|
|
||||||
**Profile**:
|
|
||||||
Everything shared across all Playthroughs, one per installation. Achievements, best time per Level, and lifetime
|
|
||||||
statistics. Never reset by starting a new Playthrough.
|
|
||||||
_Avoid_: Global state, meta, persistent data, account
|
|
||||||
|
|
||||||
### Operations
|
|
||||||
|
|
||||||
**Settle**:
|
|
||||||
Resolving an ended Attempt against the Playthrough — Commit it on a First Clear, Discard it otherwise. Settling is
|
|
||||||
the only way an Attempt affects a Playthrough, and it decides First Clear versus Replay itself rather than being
|
|
||||||
told which one happened.
|
|
||||||
_Avoid_: Apply, resolve, finalise
|
|
||||||
|
|
||||||
**Commit**:
|
|
||||||
Applying an Attempt's ledger to the Playthrough. Only a First Clear commits — nothing else, ever.
|
|
||||||
_Avoid_: Save, persist, flush, sync
|
|
||||||
|
|
||||||
**First Clear**:
|
|
||||||
Completing a Level this Playthrough has not cleared before. The only event that commits an Attempt and advances
|
|
||||||
campaign position.
|
|
||||||
_Avoid_: Level complete, finish (both ambiguous — they also describe a Replay)
|
|
||||||
|
|
||||||
**Replay**:
|
|
||||||
Completing a Level this Playthrough has already cleared. Records Best Time, achievements and statistics to the
|
|
||||||
Profile; commits nothing and never advances campaign position.
|
|
||||||
_Avoid_: Practice, retry, second run
|
|
||||||
|
|
||||||
**Bank**:
|
|
||||||
The coins a Playthrough holds, as opposed to coins found in the current Attempt. Coins are banked by committing,
|
|
||||||
never on pickup.
|
|
||||||
_Avoid_: Total coins, saved coins
|
|
||||||
|
|
||||||
**Discard**:
|
|
||||||
Throwing away an Attempt's ledger — on death, or at the end of a Replay. Purchases made during the Attempt are
|
|
||||||
undone by discarding, not refunded; under the ledger they never happened.
|
|
||||||
_Avoid_: Reset, rollback, revert
|
|
||||||
|
|
||||||
### Things
|
|
||||||
|
|
||||||
**Level**:
|
|
||||||
One playable stage, identified by a stable `LevelId` — never by position in a list. Knows its scene and display
|
|
||||||
name. Ids must survive inserting and reordering levels, because Playthroughs and Best Times both reference them.
|
|
||||||
_Avoid_: Stage, scene, level index
|
|
||||||
|
|
||||||
**Campaign**:
|
|
||||||
The authored ordering of Levels that defines progression — which Level is first, and what follows a given one. One
|
|
||||||
Campaign is shared by every Playthrough: it is authored data, not per-Playthrough state, and it carries no scene
|
|
||||||
or presentation detail.
|
|
||||||
_Avoid_: Level list, level order, world, chapter
|
|
||||||
|
|
||||||
**Cleared Levels**:
|
|
||||||
The Levels a Playthrough has completed at least once. Gates both campaign progression and level select; whether a
|
|
||||||
Level is reachable is computed from this, never stored alongside it.
|
|
||||||
_Avoid_: Unlocked levels, completed levels, available levels
|
|
||||||
|
|
||||||
**Skill**:
|
|
||||||
An ability the player can own and upgrade. `SkillData` is the authored asset describing one (name, icon, upgrade
|
|
||||||
table) and is immutable at runtime; the level the player has reached in it belongs to the Playthrough.
|
|
||||||
_Avoid_: Ability, power-up, upgrade (for the skill itself)
|
|
||||||
|
|
||||||
**Movement Ability**:
|
|
||||||
A composable behaviour attached to the player controller (jump, wall-jump, gravity). Unrelated to Skill —
|
|
||||||
authored per movement preset, not owned, not upgraded, not persisted.
|
|
||||||
_Avoid_: Skill, movement skill
|
|
||||||
|
|
||||||
**Best Time**:
|
|
||||||
The fastest recorded completion of a Level, from a First Clear or a Replay alike. Belongs to the Profile, so it
|
|
||||||
survives New Game and is shared by every Playthrough. Stored alongside, but distinct from, the ghost replay
|
|
||||||
frames that produced it.
|
|
||||||
_Avoid_: Personal best, high score, record
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- Reads may cross scopes freely — affordability spans Attempt and Playthrough. Writes must not.
|
|
||||||
- Derived facts are computed, never stored. Level reachability is derived from Cleared Levels; total coins from
|
|
||||||
Attempt plus Bank. Storing either alongside its source creates the second source of truth this context exists
|
|
||||||
to prevent.
|
|
||||||
- Achievements are mirrored to Steam, which is per-account and cannot be un-earned. That is why they are Profile
|
|
||||||
and not Playthrough: a per-Playthrough achievement list would contradict Steam permanently.
|
|
||||||
- Nothing in this context may reference Godot. Its correctness must be checkable without a scene tree.
|
|
||||||
@@ -1,25 +1,10 @@
|
|||||||
<Project Sdk="Godot.NET.Sdk/4.7.1">
|
<Project Sdk="Godot.NET.Sdk/4.4.1">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||||
<RootNamespace>Mr.BrickAdventures</RootNamespace>
|
<RootNamespace>Mr.BrickAdventures</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Facepunch.Steamworks" Version="2.3.3" />
|
<PackageReference Include="LimboConsole.Sharp" Version="0.0.1-beta-008" />
|
||||||
<PackageReference Include="Facepunch.Steamworks.Dlls" Version="2.3.2" />
|
|
||||||
<PackageReference Include="Facepunch.Steamworks.Library" Version="2.3.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!-- Godot.NET.Sdk globs **/*.cs, which would compile the progression projects into this
|
|
||||||
assembly as well as their own. They are separate projects; exclude them here. -->
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="progression\**" />
|
|
||||||
<Compile Remove="progression.tests\**" />
|
|
||||||
<Compile Remove="progression.adapters\**" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="progression\Mr.BrickAdventures.Progression.csproj" />
|
|
||||||
<ProjectReference Include="progression.adapters\Mr.BrickAdventures.Progression.Adapters.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,139 +1,19 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 2012
|
# Visual Studio 2012
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr. Brick Adventures", "Mr. Brick Adventures.csproj", "{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr. Brick Adventures", "Mr. Brick Adventures.csproj", "{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "progression", "progression", "{73CE8E92-AE0D-34EB-F3CD-A6392CC7A1AA}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr.BrickAdventures.Progression", "progression\Mr.BrickAdventures.Progression.csproj", "{4F115C37-24F6-4933-9086-ED5925EE5106}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "progression.tests", "progression.tests", "{B756B2BA-9E41-468E-DBBE-5BA8CC0A1E41}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr.BrickAdventures.Progression.Tests", "progression.tests\Mr.BrickAdventures.Progression.Tests.csproj", "{A4660883-E42A-4D04-99A3-770F36E9D861}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "progression.adapters", "progression.adapters", "{9D5C8041-6C54-0A01-82AE-FC7E0DE079DA}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mr.BrickAdventures.Progression.Adapters", "progression.adapters\Mr.BrickAdventures.Progression.Adapters.csproj", "{045D9BF4-8E63-4F14-84F4-F8C55073244C}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|x64 = Debug|x64
|
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||||
Debug|x86 = Debug|x86
|
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
|
||||||
ExportDebug|x64 = ExportDebug|x64
|
|
||||||
ExportDebug|x86 = ExportDebug|x86
|
|
||||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
|
||||||
ExportRelease|x64 = ExportRelease|x64
|
|
||||||
ExportRelease|x86 = ExportRelease|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|x64.ActiveCfg = ExportDebug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|x64.Build.0 = ExportDebug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|x86.ActiveCfg = ExportDebug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportDebug|x86.Build.0 = ExportDebug|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|x64.ActiveCfg = ExportRelease|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|x64.Build.0 = ExportRelease|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|x86.ActiveCfg = ExportRelease|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.ExportRelease|x86.Build.0 = ExportRelease|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{A1D482B9-207B-4D6C-A0A0-D9E6D1AE2356}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportDebug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|x64.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.ExportRelease|x86.Build.0 = Debug|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportDebug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|x64.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.ExportRelease|x86.Build.0 = Debug|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportDebug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|x64.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.ExportRelease|x86.Build.0 = Debug|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{4F115C37-24F6-4933-9086-ED5925EE5106} = {73CE8E92-AE0D-34EB-F3CD-A6392CC7A1AA}
|
|
||||||
{A4660883-E42A-4D04-99A3-770F36E9D861} = {B756B2BA-9E41-468E-DBBE-5BA8CC0A1E41}
|
|
||||||
{045D9BF4-8E63-4F14-84F4-F8C55073244C} = {9D5C8041-6C54-0A01-82AE-FC7E0DE079DA}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArea2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F116d8c5f8dae51522ba398e1d89e3d4722f4af7b6e7f071196b928be44af7_003FArea2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACamera2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fa2e12a1a67ad701a97608de6be85250e3e353951ecf8058a02c703490c753_003FCamera2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACanvasItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fef7b819b226fab796d1dfe66d415dd7510bcac87675020ddb8f03a828e763_003FCanvasItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACanvasItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fef7b819b226fab796d1dfe66d415dd7510bcac87675020ddb8f03a828e763_003FCanvasItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACecovym_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003Ftmp_003FJetBrainsPerUserTemp_002D1000_002D1_003FSandboxFiles_003FSadijuw_003FCecovym_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACharacterBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbba0bbd7a98ee58286e9484fbe86e01afff6232283f6efd3556eb7116453_003FCharacterBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACollisionShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ca9b7334678f5c97c7c2a9fbe4837be71cae11b6a30408dd4791b18f997e4a_003FCollisionShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACollisionShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F2ca9b7334678f5c97c7c2a9fbe4837be71cae11b6a30408dd4791b18f997e4a_003FCollisionShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACSharpInstanceBridge_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F14_003F4b4ade3f_003FCSharpInstanceBridge_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACSharpInstanceBridge_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F14_003F4b4ade3f_003FCSharpInstanceBridge_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADelegateUtils_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F08_003F45f75e10_003FDelegateUtils_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADelegateUtils_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F08_003F45f75e10_003FDelegateUtils_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F47_003Fda1d8d31_003FDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94701b444afa4c3d9a7a53ebcaa35fd1583c00_003F47_003Fda1d8d31_003FDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fe747192abb38e2df82cbdb37e721567726f559914a7b81f8b26ba537de632f4_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMr_002EBrickAdventures_002Escripts_002Ecomponents_002ECollectableComponent_005FScriptSignals_002Egenerated_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F80d9408eb7280c15eb4a12b61cdf8f7f1b0c5a2_003FMr_002EBrickAdventures_002Escripts_002Ecomponents_002ECollectableComponent_005FScriptSignals_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMr_002EBrickAdventures_002Escripts_002Ecomponents_002ECollectableComponent_005FScriptSignals_002Egenerated_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F80d9408eb7280c15eb4a12b61cdf8f7f1b0c5a2_003FMr_002EBrickAdventures_002Escripts_002Ecomponents_002ECollectableComponent_005FScriptSignals_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F86db9cd834346aad02d74c1b66dd9c64d6ef3147435dd9c9c9477b48f7_003FNode2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANode2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F86db9cd834346aad02d74c1b66dd9c64d6ef3147435dd9c9c9477b48f7_003FNode2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APhysicsBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F4fcd28ddaffa68e6fe53288161b788dea7d402b4a41b9d9f0f0f2c52f9af075_003FPhysicsBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARectangleShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fa1cc98873548652da0c14ecefa4737431426fcbb24a7f0641e3d9c266c3_003FRectangleShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARectangleShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fa1cc98873548652da0c14ecefa4737431426fcbb24a7f0641e3d9c266c3_003FRectangleShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARigidBody2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FSourcesCache_003F7bc3ccc1ac5bbc68933d64c7b7eb5ab4aecde2b73c686dd6495b68bdf08ba5b2_003FRigidBody2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3671dbbd9b17cdf2bf9075b468b6bd7e3ab13fc3be7a116484085d3b6cc9fe_003FShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShape2D_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F3671dbbd9b17cdf2bf9075b468b6bd7e3ab13fc3be7a116484085d3b6cc9fe_003FShape2D_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[gd_resource type="Resource" script_class="AchievementResource" load_steps=3 format=3 uid="uid://3odfkm1ig5"]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://cebeyr4wnibvk" path="res://sprites/achievement.png" id="1_usw25"]
|
|
||||||
[ext_resource type="Script" uid="uid://duib5phrmpro5" path="res://scripts/Resources/AchievementResource.cs" id="2_n7ktn"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
script = ExtResource("2_n7ktn")
|
|
||||||
Id = "level_complete_1"
|
|
||||||
DisplayName = "Complete level 1"
|
|
||||||
Description = ""
|
|
||||||
Icon = ExtResource("1_usw25")
|
|
||||||
IsSecret = false
|
|
||||||
metadata/_custom_type_script = "uid://duib5phrmpro5"
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
public static class CaGenerator
|
|
||||||
{
|
|
||||||
public static bool[,] Generate(CaGeneratorSettings s)
|
|
||||||
{
|
|
||||||
var rng = new Random(s.Seed);
|
|
||||||
return s.Mode switch
|
|
||||||
{
|
|
||||||
CaMode.Cave => GenerateCave(s, rng),
|
|
||||||
CaMode.Platform => GeneratePlatform(s, rng),
|
|
||||||
CaMode.Terrain => GenerateTerrain(s, rng),
|
|
||||||
_ => GenerateCave(s, rng),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool[,] Smooth(bool[,] grid) => SmoothPass(grid, 5);
|
|
||||||
|
|
||||||
// ── Cave ──────────────────────────────────────────────────────────────
|
|
||||||
private static bool[,] GenerateCave(CaGeneratorSettings s, Random rng)
|
|
||||||
{
|
|
||||||
var grid = RandomFill(s.Width, s.Height, s.FillDensity, rng);
|
|
||||||
if (s.BorderWalls) EnforceBorder(grid);
|
|
||||||
for (int i = 0; i < s.SmoothingPasses; i++)
|
|
||||||
{
|
|
||||||
grid = SmoothPass(grid, 5);
|
|
||||||
if (s.BorderWalls) EnforceBorder(grid);
|
|
||||||
}
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Platform ──────────────────────────────────────────────────────────
|
|
||||||
private static bool[,] GeneratePlatform(CaGeneratorSettings s, Random rng)
|
|
||||||
{
|
|
||||||
int w = s.Width, h = s.Height;
|
|
||||||
var grid = new bool[w, h];
|
|
||||||
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
{
|
|
||||||
grid[x, h - 1] = true;
|
|
||||||
grid[x, h - 2] = true;
|
|
||||||
|
|
||||||
for (int y = 0; y < h - 2; y++)
|
|
||||||
{
|
|
||||||
// Density linearly increases from 10% at top to 60% at bottom,
|
|
||||||
// scaled by user's FillDensity relative to default 0.48.
|
|
||||||
float t = (float)y / (h - 3);
|
|
||||||
float rowDensity = (0.1f + t * 0.5f) * (s.FillDensity / 0.48f);
|
|
||||||
grid[x, y] = rng.NextDouble() < rowDensity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < s.SmoothingPasses; i++)
|
|
||||||
{
|
|
||||||
// Threshold 4 (not 5): horizontal platform groups survive, isolated
|
|
||||||
// vertical pixels collapse into flat platforms.
|
|
||||||
grid = SmoothPass(grid, 4);
|
|
||||||
for (int x = 0; x < w; x++) { grid[x, h - 1] = true; grid[x, h - 2] = true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Terrain ───────────────────────────────────────────────────────────
|
|
||||||
private static bool[,] GenerateTerrain(CaGeneratorSettings s, Random rng)
|
|
||||||
{
|
|
||||||
int w = s.Width, h = s.Height;
|
|
||||||
var grid = new bool[w, h];
|
|
||||||
|
|
||||||
// Start terrain surface at ~60% from bottom (40% from top)
|
|
||||||
int surfaceRow = (int)(h * 0.4f);
|
|
||||||
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
{
|
|
||||||
int delta = rng.Next(-2, 3);
|
|
||||||
surfaceRow = Math.Clamp(surfaceRow + delta, h / 6, h - 2);
|
|
||||||
|
|
||||||
for (int y = surfaceRow; y < h; y++)
|
|
||||||
grid[x, y] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < s.SmoothingPasses; i++)
|
|
||||||
grid = SmoothPass(grid, 5);
|
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Shared helpers ────────────────────────────────────────────────
|
|
||||||
private static bool[,] RandomFill(int w, int h, float density, Random rng)
|
|
||||||
{
|
|
||||||
var grid = new bool[w, h];
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
for (int y = 0; y < h; y++)
|
|
||||||
grid[x, y] = rng.NextDouble() < density;
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool[,] SmoothPass(bool[,] grid, int threshold)
|
|
||||||
{
|
|
||||||
int w = grid.GetLength(0), h = grid.GetLength(1);
|
|
||||||
var next = new bool[w, h];
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
for (int y = 0; y < h; y++)
|
|
||||||
next[x, y] = CountSolidNeighbors(grid, x, y) >= threshold;
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CountSolidNeighbors(bool[,] grid, int cx, int cy)
|
|
||||||
{
|
|
||||||
int w = grid.GetLength(0), h = grid.GetLength(1), count = 0;
|
|
||||||
for (int dx = -1; dx <= 1; dx++)
|
|
||||||
for (int dy = -1; dy <= 1; dy++)
|
|
||||||
{
|
|
||||||
if (dx == 0 && dy == 0) continue;
|
|
||||||
int nx = cx + dx, ny = cy + dy;
|
|
||||||
// Out-of-bounds counts as solid (closes cave edges naturally)
|
|
||||||
if (nx < 0 || nx >= w || ny < 0 || ny >= h) count++;
|
|
||||||
else if (grid[nx, ny]) count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnforceBorder(bool[,] grid)
|
|
||||||
{
|
|
||||||
int w = grid.GetLength(0), h = grid.GetLength(1);
|
|
||||||
for (int x = 0; x < w; x++) { grid[x, 0] = true; grid[x, h - 1] = true; }
|
|
||||||
for (int y = 0; y < h; y++) { grid[0, y] = true; grid[w - 1, y] = true; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://exr10b7nmkgn
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
public enum CaMode { Cave, Platform, Terrain }
|
|
||||||
|
|
||||||
public class CaGeneratorSettings
|
|
||||||
{
|
|
||||||
public CaMode Mode { get; set; } = CaMode.Cave;
|
|
||||||
public int Width { get; set; } = 40;
|
|
||||||
public int Height { get; set; } = 22;
|
|
||||||
public Vector2I Offset { get; set; } = Vector2I.Zero;
|
|
||||||
public float FillDensity { get; set; } = 0.48f;
|
|
||||||
public int SmoothingPasses { get; set; } = 4;
|
|
||||||
public bool BorderWalls { get; set; } = true;
|
|
||||||
public int Seed { get; set; } = 12345;
|
|
||||||
// TerrainSet == -1 → raw tile mode
|
|
||||||
public int TerrainSet { get; set; } = 0;
|
|
||||||
public int Terrain { get; set; } = 0;
|
|
||||||
// Used only when TerrainSet == -1
|
|
||||||
public int SourceId { get; set; } = 0;
|
|
||||||
public Vector2I AtlasCoords { get; set; } = Vector2I.Zero;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://isnscgr1thvc
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#if TOOLS
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
[Tool]
|
|
||||||
public partial class CaGeneratorTestRunner : Node
|
|
||||||
{
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
int pass = 0, fail = 0;
|
|
||||||
|
|
||||||
void Assert(bool cond, string msg)
|
|
||||||
{
|
|
||||||
if (cond) { GD.Print($" PASS: {msg}"); pass++; }
|
|
||||||
else { GD.PrintErr($" FAIL: {msg}"); fail++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
GD.Print("=== CaGenerator Tests ===");
|
|
||||||
|
|
||||||
// Grid dimensions
|
|
||||||
var s = new CaGeneratorSettings { Width = 20, Height = 15, SmoothingPasses = 0, Seed = 42 };
|
|
||||||
var g = CaGenerator.Generate(s);
|
|
||||||
Assert(g.GetLength(0) == 20, "Width == 20");
|
|
||||||
Assert(g.GetLength(1) == 15, "Height == 15");
|
|
||||||
|
|
||||||
// Determinism
|
|
||||||
var g2 = CaGenerator.Generate(s);
|
|
||||||
bool det = true;
|
|
||||||
for (int x = 0; x < 20 && det; x++)
|
|
||||||
for (int y = 0; y < 15 && det; y++)
|
|
||||||
if (g[x, y] != g2[x, y]) det = false;
|
|
||||||
Assert(det, "Same seed → same output");
|
|
||||||
|
|
||||||
// Different seeds → different output
|
|
||||||
var s2 = new CaGeneratorSettings { Width = 20, Height = 15, SmoothingPasses = 0, Seed = 99 };
|
|
||||||
var g3 = CaGenerator.Generate(s2);
|
|
||||||
bool diff = false;
|
|
||||||
for (int x = 0; x < 20 && !diff; x++)
|
|
||||||
for (int y = 0; y < 15 && !diff; y++)
|
|
||||||
if (g[x, y] != g3[x, y]) diff = true;
|
|
||||||
Assert(diff, "Different seeds → different output");
|
|
||||||
|
|
||||||
// Cave border walls
|
|
||||||
var sb = new CaGeneratorSettings { Width = 20, Height = 15, BorderWalls = true, SmoothingPasses = 0, Seed = 42 };
|
|
||||||
var gb = CaGenerator.Generate(sb);
|
|
||||||
bool border = true;
|
|
||||||
for (int x = 0; x < 20; x++) if (!gb[x, 0] || !gb[x, 14]) { border = false; break; }
|
|
||||||
for (int y = 0; y < 15; y++) if (!gb[0, y] || !gb[19, y]) { border = false; break; }
|
|
||||||
Assert(border, "Cave: border cells all solid");
|
|
||||||
|
|
||||||
// Platform floor
|
|
||||||
var sp = new CaGeneratorSettings { Mode = CaMode.Platform, Width = 20, Height = 15, SmoothingPasses = 0, Seed = 42 };
|
|
||||||
var gp = CaGenerator.Generate(sp);
|
|
||||||
bool floor = true;
|
|
||||||
for (int x = 0; x < 20; x++) if (!gp[x, 14] || !gp[x, 13]) { floor = false; break; }
|
|
||||||
Assert(floor, "Platform: bottom 2 rows solid");
|
|
||||||
|
|
||||||
// Platform: top row not all solid (mostly empty at top)
|
|
||||||
int topSolid = 0;
|
|
||||||
for (int x = 0; x < 20; x++) if (gp[x, 0]) topSolid++;
|
|
||||||
Assert(topSolid < 15, "Platform: top row mostly empty");
|
|
||||||
|
|
||||||
// Terrain: bottom always solid
|
|
||||||
var st = new CaGeneratorSettings { Mode = CaMode.Terrain, Width = 20, Height = 15, SmoothingPasses = 0, Seed = 42 };
|
|
||||||
var gt = CaGenerator.Generate(st);
|
|
||||||
bool terrBottom = true;
|
|
||||||
for (int x = 0; x < 20; x++) if (!gt[x, 14]) { terrBottom = false; break; }
|
|
||||||
Assert(terrBottom, "Terrain: bottom row always solid");
|
|
||||||
|
|
||||||
// Smooth: output same dimensions
|
|
||||||
var smoothed = CaGenerator.Smooth(g);
|
|
||||||
Assert(smoothed.GetLength(0) == 20, "Smooth: width preserved");
|
|
||||||
Assert(smoothed.GetLength(1) == 15, "Smooth: height preserved");
|
|
||||||
|
|
||||||
GD.Print($"=== Results: {pass} passed, {fail} failed ===");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://6y4snwf31boh
|
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
#if TOOLS
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
[Tool]
|
|
||||||
public partial class CaLevelGeneratorDock : Control
|
|
||||||
{
|
|
||||||
public CaLevelGeneratorPlugin Plugin { get; set; }
|
|
||||||
|
|
||||||
// Controls referenced in Task 8 wiring
|
|
||||||
private OptionButton _layerDropdown;
|
|
||||||
private OptionButton _terrainDropdown;
|
|
||||||
private HBoxContainer _rawTileRow;
|
|
||||||
private SpinBox _sourceIdSpin;
|
|
||||||
private SpinBox _atlasXSpin;
|
|
||||||
private SpinBox _atlasYSpin;
|
|
||||||
private Button _caveBtn;
|
|
||||||
private Button _platformBtn;
|
|
||||||
private Button _terrainBtn;
|
|
||||||
private SpinBox _widthSpin;
|
|
||||||
private SpinBox _heightSpin;
|
|
||||||
private SpinBox _offsetXSpin;
|
|
||||||
private SpinBox _offsetYSpin;
|
|
||||||
private HSlider _densitySlider;
|
|
||||||
private Label _densityLabel;
|
|
||||||
private HSlider _smoothPassSlider;
|
|
||||||
private Label _smoothPassLabel;
|
|
||||||
private CheckBox _borderCheck;
|
|
||||||
private SpinBox _seedSpin;
|
|
||||||
private Button _genBtn;
|
|
||||||
private Button _smoothBtn;
|
|
||||||
private Button _clearBtn;
|
|
||||||
|
|
||||||
private CaMode _mode = CaMode.Cave;
|
|
||||||
|
|
||||||
private System.Collections.Generic.List<TileMapLayer> _layers = new();
|
|
||||||
private TileMapLayer _selectedLayer;
|
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
CustomMinimumSize = new Vector2(200, 0);
|
|
||||||
var scroll = new ScrollContainer();
|
|
||||||
scroll.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
|
|
||||||
scroll.SizeFlagsVertical = SizeFlags.ExpandFill;
|
|
||||||
AddChild(scroll);
|
|
||||||
|
|
||||||
var vbox = new VBoxContainer();
|
|
||||||
vbox.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
||||||
scroll.AddChild(vbox);
|
|
||||||
|
|
||||||
// Target Layer
|
|
||||||
vbox.AddChild(new Label { Text = "Target Layer" });
|
|
||||||
_layerDropdown = new OptionButton();
|
|
||||||
_layerDropdown.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
||||||
vbox.AddChild(_layerDropdown);
|
|
||||||
|
|
||||||
// Terrain
|
|
||||||
vbox.AddChild(new Label { Text = "Terrain" });
|
|
||||||
_terrainDropdown = new OptionButton();
|
|
||||||
_terrainDropdown.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
||||||
vbox.AddChild(_terrainDropdown);
|
|
||||||
|
|
||||||
// Raw tile row (hidden when terrain is available)
|
|
||||||
_rawTileRow = new HBoxContainer();
|
|
||||||
_rawTileRow.Visible = false;
|
|
||||||
_rawTileRow.AddChild(new Label { Text = "Src" });
|
|
||||||
_sourceIdSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
|
||||||
_rawTileRow.AddChild(_sourceIdSpin);
|
|
||||||
_rawTileRow.AddChild(new Label { Text = "X" });
|
|
||||||
_atlasXSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
|
||||||
_rawTileRow.AddChild(_atlasXSpin);
|
|
||||||
_rawTileRow.AddChild(new Label { Text = "Y" });
|
|
||||||
_atlasYSpin = new SpinBox { MinValue = 0, MaxValue = 99, Value = 0 };
|
|
||||||
_rawTileRow.AddChild(_atlasYSpin);
|
|
||||||
vbox.AddChild(_rawTileRow);
|
|
||||||
|
|
||||||
// Mode toggle
|
|
||||||
vbox.AddChild(new Label { Text = "Mode" });
|
|
||||||
var modeRow = new HBoxContainer();
|
|
||||||
modeRow.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
||||||
_caveBtn = MakeModeButton("Cave", () => SetMode(CaMode.Cave));
|
|
||||||
_platformBtn = MakeModeButton("Platform", () => SetMode(CaMode.Platform));
|
|
||||||
_terrainBtn = MakeModeButton("Terrain", () => SetMode(CaMode.Terrain));
|
|
||||||
_caveBtn.ButtonPressed = true;
|
|
||||||
modeRow.AddChild(_caveBtn);
|
|
||||||
modeRow.AddChild(_platformBtn);
|
|
||||||
modeRow.AddChild(_terrainBtn);
|
|
||||||
vbox.AddChild(modeRow);
|
|
||||||
|
|
||||||
vbox.AddChild(new HSeparator());
|
|
||||||
|
|
||||||
// Width / Height
|
|
||||||
var dimRow = new HBoxContainer();
|
|
||||||
dimRow.AddChild(MakeSpinColumn("Width", out _widthSpin, 5, 500, 40));
|
|
||||||
dimRow.AddChild(MakeSpinColumn("Height", out _heightSpin, 5, 500, 22));
|
|
||||||
vbox.AddChild(dimRow);
|
|
||||||
|
|
||||||
// Offset X / Y
|
|
||||||
var offRow = new HBoxContainer();
|
|
||||||
offRow.AddChild(MakeSpinColumn("Offset X", out _offsetXSpin, -9999, 9999, 0));
|
|
||||||
offRow.AddChild(MakeSpinColumn("Offset Y", out _offsetYSpin, -9999, 9999, 0));
|
|
||||||
vbox.AddChild(offRow);
|
|
||||||
|
|
||||||
// Fill Density
|
|
||||||
var densHeader = new HBoxContainer();
|
|
||||||
densHeader.AddChild(new Label { Text = "Fill Density", SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
|
||||||
_densityLabel = new Label { Text = "48%" };
|
|
||||||
densHeader.AddChild(_densityLabel);
|
|
||||||
vbox.AddChild(densHeader);
|
|
||||||
_densitySlider = new HSlider { MinValue = 0, MaxValue = 100, Value = 48, Step = 1 };
|
|
||||||
_densitySlider.ValueChanged += v => _densityLabel.Text = $"{(int)v}%";
|
|
||||||
vbox.AddChild(_densitySlider);
|
|
||||||
|
|
||||||
// Smoothing Passes
|
|
||||||
var smoothHeader = new HBoxContainer();
|
|
||||||
smoothHeader.AddChild(new Label { Text = "Smoothing Passes", SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
|
||||||
_smoothPassLabel = new Label { Text = "4" };
|
|
||||||
smoothHeader.AddChild(_smoothPassLabel);
|
|
||||||
vbox.AddChild(smoothHeader);
|
|
||||||
_smoothPassSlider = new HSlider { MinValue = 0, MaxValue = 10, Value = 4, Step = 1 };
|
|
||||||
_smoothPassSlider.ValueChanged += v => _smoothPassLabel.Text = $"{(int)v}";
|
|
||||||
vbox.AddChild(_smoothPassSlider);
|
|
||||||
|
|
||||||
// Border walls
|
|
||||||
_borderCheck = new CheckBox { Text = "Solid border walls", ButtonPressed = true };
|
|
||||||
vbox.AddChild(_borderCheck);
|
|
||||||
|
|
||||||
// Seed
|
|
||||||
vbox.AddChild(new Label { Text = "Seed" });
|
|
||||||
var seedRow = new HBoxContainer();
|
|
||||||
_seedSpin = new SpinBox { MinValue = 0, MaxValue = int.MaxValue, Value = 12345,
|
|
||||||
AllowGreater = true, SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
var randBtn = new Button { Text = "🎲" };
|
|
||||||
randBtn.Pressed += () => _seedSpin.Value = GD.Randi();
|
|
||||||
seedRow.AddChild(_seedSpin);
|
|
||||||
seedRow.AddChild(randBtn);
|
|
||||||
vbox.AddChild(seedRow);
|
|
||||||
|
|
||||||
vbox.AddChild(new HSeparator());
|
|
||||||
|
|
||||||
// Action buttons
|
|
||||||
_genBtn = new Button { Text = "▶ Generate", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
vbox.AddChild(_genBtn);
|
|
||||||
|
|
||||||
var actionRow = new HBoxContainer();
|
|
||||||
actionRow.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
|
||||||
_smoothBtn = new Button { Text = "✦ Smooth", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
_clearBtn = new Button { Text = "✕ Clear", SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
actionRow.AddChild(_smoothBtn);
|
|
||||||
actionRow.AddChild(_clearBtn);
|
|
||||||
vbox.AddChild(actionRow);
|
|
||||||
|
|
||||||
_layerDropdown.ItemSelected += OnLayerSelected;
|
|
||||||
_terrainDropdown.ItemSelected += _ => SaveSettings();
|
|
||||||
_genBtn.Pressed += OnGenerate;
|
|
||||||
_smoothBtn.Pressed += OnSmooth;
|
|
||||||
_clearBtn.Pressed += OnClear;
|
|
||||||
|
|
||||||
LoadSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnSceneChanged(Node sceneRoot)
|
|
||||||
{
|
|
||||||
ScanLayers(sceneRoot);
|
|
||||||
RestoreLastLayer(sceneRoot?.SceneFilePath ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetMode(CaMode mode)
|
|
||||||
{
|
|
||||||
_mode = mode;
|
|
||||||
_caveBtn.ButtonPressed = mode == CaMode.Cave;
|
|
||||||
_platformBtn.ButtonPressed = mode == CaMode.Platform;
|
|
||||||
_terrainBtn.ButtonPressed = mode == CaMode.Terrain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Button MakeModeButton(string text, System.Action onPressed)
|
|
||||||
{
|
|
||||||
var btn = new Button { Text = text, ToggleMode = true,
|
|
||||||
SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
btn.Pressed += onPressed;
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Control MakeSpinColumn(string label, out SpinBox spin,
|
|
||||||
double min, double max, double value)
|
|
||||||
{
|
|
||||||
var col = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
col.AddChild(new Label { Text = label });
|
|
||||||
spin = new SpinBox { MinValue = min, MaxValue = max, Value = value,
|
|
||||||
SizeFlagsHorizontal = SizeFlags.ExpandFill };
|
|
||||||
col.AddChild(spin);
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ScanLayers(Node root)
|
|
||||||
{
|
|
||||||
_layers.Clear();
|
|
||||||
_layerDropdown.Clear();
|
|
||||||
if (root == null) return;
|
|
||||||
CollectLayers(root);
|
|
||||||
foreach (var layer in _layers)
|
|
||||||
_layerDropdown.AddItem(layer.Name);
|
|
||||||
if (_layers.Count > 0) OnLayerSelected(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CollectLayers(Node node)
|
|
||||||
{
|
|
||||||
if (node is TileMapLayer layer) _layers.Add(layer);
|
|
||||||
foreach (Node child in node.GetChildren()) CollectLayers(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLayerSelected(long index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= _layers.Count) return;
|
|
||||||
_selectedLayer = _layers[(int)index];
|
|
||||||
PopulateTerrainDropdown(_selectedLayer);
|
|
||||||
|
|
||||||
var sceneRoot = EditorInterface.Singleton.GetEditedSceneRoot();
|
|
||||||
if (sceneRoot != null)
|
|
||||||
{
|
|
||||||
var key = $"ca_generator/last_layer_{sceneRoot.SceneFilePath}";
|
|
||||||
EditorInterface.Singleton.GetEditorSettings()
|
|
||||||
.SetSetting(key, _selectedLayer.GetPath().ToString());
|
|
||||||
}
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RestoreLastLayer(string sceneFilePath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(sceneFilePath) || _layers.Count == 0) return;
|
|
||||||
var key = $"ca_generator/last_layer_{sceneFilePath}";
|
|
||||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
|
||||||
if (!es.HasSetting(key)) return;
|
|
||||||
var path = es.GetSetting(key).AsString();
|
|
||||||
for (int i = 0; i < _layers.Count; i++)
|
|
||||||
{
|
|
||||||
if (_layers[i].GetPath().ToString() == path)
|
|
||||||
{
|
|
||||||
_layerDropdown.Select(i);
|
|
||||||
OnLayerSelected(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateTerrainDropdown(TileMapLayer layer)
|
|
||||||
{
|
|
||||||
_terrainDropdown.Clear();
|
|
||||||
var tileSet = layer.TileSet;
|
|
||||||
bool hasTerrains = false;
|
|
||||||
|
|
||||||
if (tileSet != null)
|
|
||||||
{
|
|
||||||
for (int ts = 0; ts < tileSet.GetTerrainSetsCount(); ts++)
|
|
||||||
{
|
|
||||||
for (int t = 0; t < tileSet.GetTerrainsCount(ts); t++)
|
|
||||||
{
|
|
||||||
_terrainDropdown.AddItem(tileSet.GetTerrainName(ts, t));
|
|
||||||
_terrainDropdown.SetItemMetadata(
|
|
||||||
_terrainDropdown.ItemCount - 1,
|
|
||||||
Variant.From((ts << 16) | t));
|
|
||||||
hasTerrains = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_terrainDropdown.Visible = hasTerrains;
|
|
||||||
_rawTileRow.Visible = !hasTerrains;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CaGeneratorSettings BuildSettings()
|
|
||||||
{
|
|
||||||
int terrainSet = -1, terrain = 0;
|
|
||||||
if (_terrainDropdown.Visible && _terrainDropdown.ItemCount > 0)
|
|
||||||
{
|
|
||||||
int packed = _terrainDropdown.GetSelectedMetadata().AsInt32();
|
|
||||||
terrainSet = packed >> 16;
|
|
||||||
terrain = packed & 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CaGeneratorSettings
|
|
||||||
{
|
|
||||||
Mode = _mode,
|
|
||||||
Width = (int)_widthSpin.Value,
|
|
||||||
Height = (int)_heightSpin.Value,
|
|
||||||
Offset = new Vector2I((int)_offsetXSpin.Value, (int)_offsetYSpin.Value),
|
|
||||||
FillDensity = (float)_densitySlider.Value / 100f,
|
|
||||||
SmoothingPasses = (int)_smoothPassSlider.Value,
|
|
||||||
BorderWalls = _borderCheck.ButtonPressed,
|
|
||||||
Seed = (int)_seedSpin.Value,
|
|
||||||
TerrainSet = terrainSet,
|
|
||||||
Terrain = terrain,
|
|
||||||
SourceId = (int)_sourceIdSpin.Value,
|
|
||||||
AtlasCoords = new Vector2I((int)_atlasXSpin.Value, (int)_atlasYSpin.Value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGenerate()
|
|
||||||
{
|
|
||||||
if (_selectedLayer == null) return;
|
|
||||||
var settings = BuildSettings();
|
|
||||||
var grid = CaGenerator.Generate(settings);
|
|
||||||
TilemapPainter.Paint(_selectedLayer, grid, settings, Plugin.UndoRedo);
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSmooth()
|
|
||||||
{
|
|
||||||
if (_selectedLayer == null) return;
|
|
||||||
TilemapPainter.Smooth(_selectedLayer, BuildSettings(), Plugin.UndoRedo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClear()
|
|
||||||
{
|
|
||||||
if (_selectedLayer == null) return;
|
|
||||||
TilemapPainter.Clear(_selectedLayer, BuildSettings(), Plugin.UndoRedo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveSettings()
|
|
||||||
{
|
|
||||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
|
||||||
es.SetSetting("ca_generator/mode", (int)_mode);
|
|
||||||
es.SetSetting("ca_generator/width", (int)_widthSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/height", (int)_heightSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/offset_x", (int)_offsetXSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/offset_y", (int)_offsetYSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/fill_density", _densitySlider.Value);
|
|
||||||
es.SetSetting("ca_generator/smoothing_passes", (int)_smoothPassSlider.Value);
|
|
||||||
es.SetSetting("ca_generator/border_walls", _borderCheck.ButtonPressed);
|
|
||||||
es.SetSetting("ca_generator/seed", (int)_seedSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/source_id", (int)_sourceIdSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/atlas_x", (int)_atlasXSpin.Value);
|
|
||||||
es.SetSetting("ca_generator/atlas_y", (int)_atlasYSpin.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadSettings()
|
|
||||||
{
|
|
||||||
var es = EditorInterface.Singleton.GetEditorSettings();
|
|
||||||
if (es == null) return;
|
|
||||||
|
|
||||||
SetMode((CaMode)(es.HasSetting("ca_generator/mode") ? es.GetSetting("ca_generator/mode").AsInt32() : 0));
|
|
||||||
_widthSpin.Value = es.HasSetting("ca_generator/width") ? es.GetSetting("ca_generator/width").AsDouble() : 40.0;
|
|
||||||
_heightSpin.Value = es.HasSetting("ca_generator/height") ? es.GetSetting("ca_generator/height").AsDouble() : 22.0;
|
|
||||||
_offsetXSpin.Value = es.HasSetting("ca_generator/offset_x") ? es.GetSetting("ca_generator/offset_x").AsDouble() : 0.0;
|
|
||||||
_offsetYSpin.Value = es.HasSetting("ca_generator/offset_y") ? es.GetSetting("ca_generator/offset_y").AsDouble() : 0.0;
|
|
||||||
_densitySlider.Value = es.HasSetting("ca_generator/fill_density") ? es.GetSetting("ca_generator/fill_density").AsDouble() : 48.0;
|
|
||||||
_smoothPassSlider.Value = es.HasSetting("ca_generator/smoothing_passes") ? es.GetSetting("ca_generator/smoothing_passes").AsDouble() : 4.0;
|
|
||||||
_borderCheck.ButtonPressed = es.HasSetting("ca_generator/border_walls") ? es.GetSetting("ca_generator/border_walls").AsBool() : true;
|
|
||||||
_seedSpin.Value = es.HasSetting("ca_generator/seed") ? es.GetSetting("ca_generator/seed").AsDouble() : 12345.0;
|
|
||||||
|
|
||||||
_sourceIdSpin.Value = es.HasSetting("ca_generator/source_id") ? es.GetSetting("ca_generator/source_id").AsDouble() : 0.0;
|
|
||||||
_atlasXSpin.Value = es.HasSetting("ca_generator/atlas_x") ? es.GetSetting("ca_generator/atlas_x").AsDouble() : 0.0;
|
|
||||||
_atlasYSpin.Value = es.HasSetting("ca_generator/atlas_y") ? es.GetSetting("ca_generator/atlas_y").AsDouble() : 0.0;
|
|
||||||
|
|
||||||
_densityLabel.Text = $"{(int)_densitySlider.Value}%";
|
|
||||||
_smoothPassLabel.Text = $"{(int)_smoothPassSlider.Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://dnuvfv3rgyywd
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#if TOOLS
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
[Tool]
|
|
||||||
public partial class CaLevelGeneratorPlugin : EditorPlugin
|
|
||||||
{
|
|
||||||
private EditorDock _editorDock;
|
|
||||||
private CaLevelGeneratorDock _dock;
|
|
||||||
|
|
||||||
public override void _EnterTree()
|
|
||||||
{
|
|
||||||
_dock = new CaLevelGeneratorDock { Plugin = this };
|
|
||||||
_editorDock = new EditorDock { Title = "CA Level Generator" };
|
|
||||||
_editorDock.AddChild(_dock);
|
|
||||||
AddDock(_editorDock);
|
|
||||||
SceneChanged += OnSceneChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _ExitTree()
|
|
||||||
{
|
|
||||||
SceneChanged -= OnSceneChanged;
|
|
||||||
RemoveDock(_editorDock);
|
|
||||||
_editorDock.QueueFree();
|
|
||||||
_editorDock = null;
|
|
||||||
_dock = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSceneChanged(Node sceneRoot)
|
|
||||||
{
|
|
||||||
_dock?.OnSceneChanged(sceneRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EditorUndoRedoManager UndoRedo => GetUndoRedo();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cojnhxpcgepi2
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#if TOOLS
|
|
||||||
using Godot;
|
|
||||||
using Godot.Collections;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Tools.CaLevelGenerator;
|
|
||||||
|
|
||||||
public static class TilemapPainter
|
|
||||||
{
|
|
||||||
public static void Paint(
|
|
||||||
TileMapLayer layer,
|
|
||||||
bool[,] grid,
|
|
||||||
CaGeneratorSettings settings,
|
|
||||||
EditorUndoRedoManager undoRedo)
|
|
||||||
{
|
|
||||||
var before = layer.TileMapData;
|
|
||||||
ApplyGrid(layer, grid, settings);
|
|
||||||
var after = layer.TileMapData;
|
|
||||||
RegisterUndo(layer, before, after, undoRedo, "CA Generate");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Smooth(
|
|
||||||
TileMapLayer layer,
|
|
||||||
CaGeneratorSettings settings,
|
|
||||||
EditorUndoRedoManager undoRedo)
|
|
||||||
{
|
|
||||||
var grid = ReadGrid(layer, settings.Offset, new Vector2I(settings.Width, settings.Height));
|
|
||||||
grid = CaGenerator.Smooth(grid);
|
|
||||||
var before = layer.TileMapData;
|
|
||||||
ApplyGrid(layer, grid, settings);
|
|
||||||
var after = layer.TileMapData;
|
|
||||||
RegisterUndo(layer, before, after, undoRedo, "CA Smooth");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Clear(
|
|
||||||
TileMapLayer layer,
|
|
||||||
CaGeneratorSettings settings,
|
|
||||||
EditorUndoRedoManager undoRedo)
|
|
||||||
{
|
|
||||||
var before = layer.TileMapData;
|
|
||||||
for (int x = 0; x < settings.Width; x++)
|
|
||||||
for (int y = 0; y < settings.Height; y++)
|
|
||||||
layer.EraseCell(settings.Offset + new Vector2I(x, y));
|
|
||||||
var after = layer.TileMapData;
|
|
||||||
RegisterUndo(layer, before, after, undoRedo, "CA Clear");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool[,] ReadGrid(TileMapLayer layer, Vector2I offset, Vector2I size)
|
|
||||||
{
|
|
||||||
var grid = new bool[size.X, size.Y];
|
|
||||||
for (int x = 0; x < size.X; x++)
|
|
||||||
for (int y = 0; y < size.Y; y++)
|
|
||||||
grid[x, y] = layer.GetCellSourceId(offset + new Vector2I(x, y)) != -1;
|
|
||||||
return grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ApplyGrid(TileMapLayer layer, bool[,] grid, CaGeneratorSettings settings)
|
|
||||||
{
|
|
||||||
int w = grid.GetLength(0), h = grid.GetLength(1);
|
|
||||||
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
for (int y = 0; y < h; y++)
|
|
||||||
layer.EraseCell(settings.Offset + new Vector2I(x, y));
|
|
||||||
|
|
||||||
if (settings.TerrainSet >= 0)
|
|
||||||
{
|
|
||||||
var solidCells = new Array<Vector2I>();
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
for (int y = 0; y < h; y++)
|
|
||||||
if (grid[x, y])
|
|
||||||
solidCells.Add(settings.Offset + new Vector2I(x, y));
|
|
||||||
|
|
||||||
if (solidCells.Count > 0)
|
|
||||||
layer.SetCellsTerrainConnect(solidCells, settings.TerrainSet, settings.Terrain);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int x = 0; x < w; x++)
|
|
||||||
for (int y = 0; y < h; y++)
|
|
||||||
if (grid[x, y])
|
|
||||||
layer.SetCell(
|
|
||||||
settings.Offset + new Vector2I(x, y),
|
|
||||||
settings.SourceId,
|
|
||||||
settings.AtlasCoords);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RegisterUndo(
|
|
||||||
TileMapLayer layer,
|
|
||||||
byte[] before,
|
|
||||||
byte[] after,
|
|
||||||
EditorUndoRedoManager undoRedo,
|
|
||||||
string actionName)
|
|
||||||
{
|
|
||||||
undoRedo.CreateAction(actionName);
|
|
||||||
undoRedo.AddUndoProperty(layer, "tile_map_data", before);
|
|
||||||
undoRedo.AddDoProperty(layer, "tile_map_data", after);
|
|
||||||
undoRedo.CommitAction(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://y55l4iw35tih
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://test_ca_generator"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/ca_level_generator/CaGeneratorTestRunner.cs" id="1_catest"]
|
|
||||||
|
|
||||||
[node name="CaGeneratorTestRunner" type="Node"]
|
|
||||||
script = ExtResource("1_catest")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[plugin]
|
|
||||||
name="CA Level Generator"
|
|
||||||
description="Cellular automata level blockout generator"
|
|
||||||
author="Gabriel Kaszewski"
|
|
||||||
version="1.0"
|
|
||||||
script="CaLevelGeneratorPlugin.cs"
|
|
||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/banner.png-7e9e6a304eef850602c8d5afb80df9c3.c
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.cte
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/responses_menu.svg-87cf63ca685d53616205049572
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.c
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
22
addons/godotsteam/godotsteam.gdextension
Normal file
22
addons/godotsteam/godotsteam.gdextension
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[configuration]
|
||||||
|
entry_symbol = "godotsteam_init"
|
||||||
|
compatibility_minimum = "4.4"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
macos.debug = "res://addons/godotsteam/osx/libgodotsteam.macos.template_debug.framework"
|
||||||
|
macos.release = "res://addons/godotsteam/osx/libgodotsteam.macos.template_release.framework"
|
||||||
|
windows.debug.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_debug.x86_64.dll"
|
||||||
|
windows.debug.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_debug.x86_32.dll"
|
||||||
|
windows.release.x86_64 = "res://addons/godotsteam/win64/libgodotsteam.windows.template_release.x86_64.dll"
|
||||||
|
windows.release.x86_32 = "res://addons/godotsteam/win32/libgodotsteam.windows.template_release.x86_32.dll"
|
||||||
|
linux.debug.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_debug.x86_64.so"
|
||||||
|
linux.debug.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_debug.x86_32.so"
|
||||||
|
linux.release.x86_64 = "res://addons/godotsteam/linux64/libgodotsteam.linux.template_release.x86_64.so"
|
||||||
|
linux.release.x86_32 = "res://addons/godotsteam/linux32/libgodotsteam.linux.template_release.x86_32.so"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
macos.universal = { "res://addons/godotsteam/osx/libsteam_api.dylib": "" }
|
||||||
|
windows.x86_64 = { "res://addons/godotsteam/win64/steam_api64.dll": "" }
|
||||||
|
windows.x86_32 = { "res://addons/godotsteam/win32/steam_api.dll": "" }
|
||||||
|
linux.x86_64 = { "res://addons/godotsteam/linux64/libsteam_api.so": "" }
|
||||||
|
linux.x86_32 = { "res://addons/godotsteam/linux32/libsteam_api.so": "" }
|
||||||
1
addons/godotsteam/godotsteam.gdextension.uid
Normal file
1
addons/godotsteam/godotsteam.gdextension.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cbt61wgh4qe5l
|
||||||
Binary file not shown.
Binary file not shown.
BIN
addons/godotsteam/linux32/libsteam_api.so
Normal file
BIN
addons/godotsteam/linux32/libsteam_api.so
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
addons/godotsteam/linux64/libsteam_api.so
Normal file
BIN
addons/godotsteam/linux64/libsteam_api.so
Normal file
Binary file not shown.
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>libgodotsteam.debug</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.godotsteam.godotsteam</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>libgodotsteam.debug</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>4.15</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>MacOSX</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>4.15</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.12</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>libgodotsteam</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.godotsteam.godotsteam</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>libgodotsteam</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>4.15</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>MacOSX</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>4.15</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.12</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
Binary file not shown.
BIN
addons/godotsteam/osx/libsteam_api.dylib
Normal file
BIN
addons/godotsteam/osx/libsteam_api.dylib
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
addons/godotsteam/win32/steam_api.dll
Normal file
BIN
addons/godotsteam/win32/steam_api.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
addons/godotsteam/win64/steam_api64.dll
Normal file
BIN
addons/godotsteam/win64/steam_api64.dll
Normal file
Binary file not shown.
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/PhantomCameraBtnPrimaryDefault.png-fcf3696b58
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/PhantomCameraBtnPrimaryHover.png-3d2e4d225f6a
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/inventory_container.png-12241277f279bfc4bf7d5
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/level_spritesheet.png-26a44dd21a040a5480d5ccb
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_2d_sprite.png-deab230b83ae03ae
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/player_sprite.svg-8862ecb19e12152eb892607676f
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/sign_prompt.png-18d451127e1cd1a16367acd23cec4
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166
|
|||||||
compress/mode=2
|
compress/mode=2
|
||||||
compress/high_quality=true
|
compress/high_quality=true
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=true
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/target.png-878c5e8d057c8a9a4c2322d4ab88e9ef.s
|
|||||||
compress/mode=2
|
compress/mode=2
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=true
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ msdf_pixel_range=8
|
|||||||
msdf_size=48
|
msdf_size=48
|
||||||
allow_system_fallback=true
|
allow_system_fallback=true
|
||||||
force_autohinter=false
|
force_autohinter=false
|
||||||
modulate_color_glyphs=false
|
|
||||||
hinting=1
|
hinting=1
|
||||||
subpixel_positioning=1
|
subpixel_positioning=1
|
||||||
keep_rounding_remainders=true
|
keep_rounding_remainders=true
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/PriorityOverride.svg-e76e07f4bbd98169f119e17f
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_2d.svg-e5483cbc858fc5f95f7210b
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_3d.svg-41ed612e834470377fb56ee
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_camera_3d_resource.svg-f8bf8d1
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d
|
|||||||
compress/mode=2
|
compress/mode=2
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=true
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_glow_logo.png-078f944973b55b32
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ dest_files=["res://.godot/imported/phantom_camera_host.svg-3150f8f2d82ca9ecab9a3
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -28,10 +26,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_logo.png-8b8d347b5e4800c86cd80
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ dest_files=["res://.godot/imported/phantom_camera_noise_emitter_2d.svg-1b3d37fe3
|
|||||||
compress/mode=0
|
compress/mode=0
|
||||||
compress/high_quality=false
|
compress/high_quality=false
|
||||||
compress/lossy_quality=0.7
|
compress/lossy_quality=0.7
|
||||||
compress/uastc_level=0
|
|
||||||
compress/rdo_quality_loss=0.0
|
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
@@ -27,10 +25,6 @@ mipmaps/generate=false
|
|||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
process/channel_remap/red=0
|
|
||||||
process/channel_remap/green=1
|
|
||||||
process/channel_remap/blue=2
|
|
||||||
process/channel_remap/alpha=3
|
|
||||||
process/fix_alpha_border=true
|
process/fix_alpha_border=true
|
||||||
process/premult_alpha=false
|
process/premult_alpha=false
|
||||||
process/normal_map_invert_y=false
|
process/normal_map_invert_y=false
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user