Compare commits

...

3 Commits

46 changed files with 599 additions and 211 deletions

View File

@@ -5,139 +5,9 @@
<RootNamespace>Mr.BrickAdventures</RootNamespace> <RootNamespace>Mr.BrickAdventures</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="README.md" /> <PackageReference Include="Chickensoft.AutoInject" Version="2.8.21" />
<Content Include="scripts\components\.idea\.gitignore" /> <PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.31" />
<Content Include="scripts\components\.idea\encodings.xml" /> <PackageReference Include="Chickensoft.Introspection" Version="3.0.2" />
<Content Include="scripts\components\.idea\indexLayout.xml" /> <PackageReference Include="Chickensoft.Introspection.Generator" Version="3.0.2" PrivateAssets="all" OutputItemType="analyzer" />
<Content Include="scripts\components\.idea\inspectionProfiles\Project_Default.xml" />
<Content Include="scripts\components\.idea\projectSettingsUpdater.xml" />
<Content Include="scripts\components\.idea\vcs.xml" />
<Content Include="scripts\components\.idea\workspace.xml" />
<Content Include="scripts\components\BeamComponent.cs.uid" />
<Content Include="scripts\components\beam_component.gd" />
<Content Include="scripts\components\beam_component.gd.uid" />
<Content Include="scripts\components\brick_throw.gd" />
<Content Include="scripts\components\brick_throw.gd.uid" />
<Content Include="scripts\components\bullet_component.gd" />
<Content Include="scripts\components\bullet_component.gd.uid" />
<Content Include="scripts\components\cage_component.gd" />
<Content Include="scripts\components\cage_component.gd.uid" />
<Content Include="scripts\components\cannot_stomp_component.gd" />
<Content Include="scripts\components\cannot_stomp_component.gd.uid" />
<Content Include="scripts\components\can_be_launched_component.gd" />
<Content Include="scripts\components\can_be_launched_component.gd.uid" />
<Content Include="scripts\components\can_pickup.gd" />
<Content Include="scripts\components\can_pickup.gd.uid" />
<Content Include="scripts\components\charge_throw_component.gd" />
<Content Include="scripts\components\charge_throw_component.gd.uid" />
<Content Include="scripts\components\chase_level_component.gd" />
<Content Include="scripts\components\chase_level_component.gd.uid" />
<Content Include="scripts\components\cleanup_component.gd" />
<Content Include="scripts\components\cleanup_component.gd.uid" />
<Content Include="scripts\components\collapsable.gd" />
<Content Include="scripts\components\collapsable.gd.uid" />
<Content Include="scripts\components\collectable.gd" />
<Content Include="scripts\components\collectable.gd.uid" />
<Content Include="scripts\components\damage_component.gd" />
<Content Include="scripts\components\damage_component.gd.uid" />
<Content Include="scripts\components\destroyable_component.gd" />
<Content Include="scripts\components\destroyable_component.gd.uid" />
<Content Include="scripts\components\effect_inflictor_component.gd" />
<Content Include="scripts\components\effect_inflictor_component.gd.uid" />
<Content Include="scripts\components\enemy_death.gd" />
<Content Include="scripts\components\enemy_death.gd.uid" />
<Content Include="scripts\components\enemy_wave_trigger.gd" />
<Content Include="scripts\components\enemy_wave_trigger.gd.uid" />
<Content Include="scripts\components\exit_door_component.gd" />
<Content Include="scripts\components\exit_door_component.gd.uid" />
<Content Include="scripts\components\explosive_component.gd" />
<Content Include="scripts\components\explosive_component.gd.uid" />
<Content Include="scripts\components\fade_away.gd" />
<Content Include="scripts\components\fade_away.gd.uid" />
<Content Include="scripts\components\fire_effect_component.gd" />
<Content Include="scripts\components\fire_effect_component.gd.uid" />
<Content Include="scripts\components\flashing_component.gd" />
<Content Include="scripts\components\flashing_component.gd.uid" />
<Content Include="scripts\components\flip_player.gd" />
<Content Include="scripts\components\flip_player.gd.uid" />
<Content Include="scripts\components\gravity_motion_component.gd" />
<Content Include="scripts\components\gravity_motion_component.gd.uid" />
<Content Include="scripts\components\health.gd" />
<Content Include="scripts\components\health.gd.uid" />
<Content Include="scripts\components\heal_component.gd" />
<Content Include="scripts\components\heal_component.gd.uid" />
<Content Include="scripts\components\hit_component.gd" />
<Content Include="scripts\components\hit_component.gd.uid" />
<Content Include="scripts\components\homing_missile_motion.gd" />
<Content Include="scripts\components\homing_missile_motion.gd.uid" />
<Content Include="scripts\components\ice_effect_component.gd" />
<Content Include="scripts\components\ice_effect_component.gd.uid" />
<Content Include="scripts\components\invulnerability_component.gd" />
<Content Include="scripts\components\invulnerability_component.gd.uid" />
<Content Include="scripts\components\jump_pad_component.gd" />
<Content Include="scripts\components\jump_pad_component.gd.uid" />
<Content Include="scripts\components\kill_player_out_of_screen.gd" />
<Content Include="scripts\components\kill_player_out_of_screen.gd.uid" />
<Content Include="scripts\components\knockback.gd" />
<Content Include="scripts\components\knockback.gd.uid" />
<Content Include="scripts\components\launch_component.gd" />
<Content Include="scripts\components\launch_component.gd.uid" />
<Content Include="scripts\components\lever_component.gd" />
<Content Include="scripts\components\lever_component.gd.uid" />
<Content Include="scripts\components\lifetime_component.gd" />
<Content Include="scripts\components\lifetime_component.gd.uid" />
<Content Include="scripts\components\magnetic_skill.gd" />
<Content Include="scripts\components\magnetic_skill.gd.uid" />
<Content Include="scripts\components\out_of_screen_component.gd" />
<Content Include="scripts\components\out_of_screen_component.gd.uid" />
<Content Include="scripts\components\periodic_shooting.gd" />
<Content Include="scripts\components\periodic_shooting.gd.uid" />
<Content Include="scripts\components\PlatformMovementComponent.cs.uid" />
<Content Include="scripts\components\platform_movement.gd" />
<Content Include="scripts\components\PlayerController.cs.uid" />
<Content Include="scripts\components\player_death.gd" />
<Content Include="scripts\components\player_death.gd.uid" />
<Content Include="scripts\components\player_movement.gd" />
<Content Include="scripts\components\player_movement.gd.uid" />
<Content Include="scripts\components\progressive_damage_component.gd" />
<Content Include="scripts\components\progressive_damage_component.gd.uid" />
<Content Include="scripts\components\projectile_component.gd" />
<Content Include="scripts\components\projectile_component.gd.uid" />
<Content Include="scripts\components\projectile_init_component.gd" />
<Content Include="scripts\components\projectile_init_component.gd.uid" />
<Content Include="scripts\components\requirement_component.gd" />
<Content Include="scripts\components\requirement_component.gd.uid" />
<Content Include="scripts\components\score.gd" />
<Content Include="scripts\components\score.gd.uid" />
<Content Include="scripts\components\ship_movement.gd" />
<Content Include="scripts\components\ship_movement.gd.uid" />
<Content Include="scripts\components\ship_shooter.gd" />
<Content Include="scripts\components\ship_shooter.gd.uid" />
<Content Include="scripts\components\side_to_side_movement.gd" />
<Content Include="scripts\components\side_to_side_movement.gd.uid" />
<Content Include="scripts\components\skill_unlocker_component.gd" />
<Content Include="scripts\components\skill_unlocker_component.gd.uid" />
<Content Include="scripts\components\spaceship_enter_component.gd" />
<Content Include="scripts\components\spaceship_enter_component.gd.uid" />
<Content Include="scripts\components\spaceship_exit_component.gd" />
<Content Include="scripts\components\spaceship_exit_component.gd.uid" />
<Content Include="scripts\components\spin_component.gd" />
<Content Include="scripts\components\spin_component.gd.uid" />
<Content Include="scripts\components\status_effect_component.gd" />
<Content Include="scripts\components\status_effect_component.gd.uid" />
<Content Include="scripts\components\stomp_damage_component.gd" />
<Content Include="scripts\components\stomp_damage_component.gd.uid" />
<Content Include="scripts\components\straight_motion_component.gd" />
<Content Include="scripts\components\straight_motion_component.gd.uid" />
<Content Include="scripts\components\terrain_hit_fx.gd" />
<Content Include="scripts\components\terrain_hit_fx.gd.uid" />
<Content Include="scripts\components\tooltip_component.gd" />
<Content Include="scripts\components\tooltip_component.gd.uid" />
<Content Include="scripts\components\trail_component.gd" />
<Content Include="scripts\components\trail_component.gd.uid" />
<Content Include="scripts\components\trigger_lever_component.gd" />
<Content Include="scripts\components\trigger_lever_component.gd.uid" />
<Content Include="scripts\components\unlock_on_requirement_component.gd" />
<Content Include="scripts\components\unlock_on_requirement_component.gd.uid" />
</ItemGroup> </ItemGroup>
</Project> </Project>

10
app/IGameScenes.cs Normal file
View File

@@ -0,0 +1,10 @@
using Godot;
namespace Mr.BrickAdventures.app;
public interface IGameScenes
{
void Load(PackedScene scene);
void Restart();
void ReturnToMain(PackedScene mainMenu);
}

1
app/IGameScenes.cs.uid Normal file
View File

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

11
app/ILevelCatalog.cs Normal file
View File

@@ -0,0 +1,11 @@
using Godot;
namespace Mr.BrickAdventures.app;
public interface ILevelCatalog
{
int Count { get; }
PackedScene? Get(int index);
PackedScene First { get; }
PackedScene MainMenu { get; }
}

1
app/ILevelCatalog.cs.uid Normal file
View File

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

70
common/AppRoot.cs Normal file
View File

@@ -0,0 +1,70 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.app;
using Mr.BrickAdventures.data;
using Mr.BrickAdventures.game.repositories;
using Mr.BrickAdventures.game.services;
namespace Mr.BrickAdventures.common;
[Meta(typeof(IAutoNode))]
public partial class AppRoot : Node2D,
IProvide<PlayerRepository>,
IProvide<LevelRepository>,
IProvide<SaveClient>,
IProvide<SaveService>,
IProvide<LevelService>,
IProvide<IGameScenes>,
IProvide<ILevelCatalog>,
IGameScenes
{
public override void _Notification(int what) => this.Notify(what);
[Export] private Array<PackedScene> _levels = [];
[Export] private PackedScene _mainMenu = null!;
private readonly SaveClient _save = new("user://savegame.save", version: 2);
private readonly PlayerRepository _players = new();
private readonly LevelRepository _levelsRepo = new();
private SaveService _saveService = null!;
private LevelService _levelService = null!;
private ILevelCatalog _catalog = null!;
PlayerRepository IProvide<PlayerRepository>.Value() => _players;
LevelRepository IProvide<LevelRepository>.Value() => _levelsRepo;
SaveClient IProvide<SaveClient>.Value() => _save;
SaveService IProvide<SaveService>.Value() => _saveService;
LevelService IProvide<LevelService>.Value() => _levelService;
ILevelCatalog IProvide<ILevelCatalog>.Value() => _catalog;
IGameScenes IProvide<IGameScenes>.Value() => this;
public void OnReady()
{
_saveService = new SaveService(_players, _levelsRepo, _save);
_levelService = new LevelService(_levelsRepo);
_catalog = new ExportedLevelCatalog(_levels, _mainMenu);
_saveService.TryLoad();
this.Provide();
}
public void Load(PackedScene scene) => GetTree().ChangeSceneToPacked(scene);
public void Restart() => GetTree().ReloadCurrentScene();
public void ReturnToMain(PackedScene mainMenu) => GetTree().ChangeSceneToPacked(mainMenu);
private sealed class ExportedLevelCatalog : ILevelCatalog
{
private readonly Array<PackedScene> _levels;
public PackedScene MainMenu { get; }
public ExportedLevelCatalog(Array<PackedScene> levels, PackedScene mainMenu) {
_levels = levels; MainMenu = mainMenu;
}
public int Count => _levels.Count;
public PackedScene? Get(int index) => (index >= 0 && index < _levels.Count) ? _levels[index] : null;
public PackedScene First => _levels.Count > 0 ? _levels[0] : MainMenu;
}
}

1
common/AppRoot.cs.uid Normal file
View File

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

6
data/ConfigClient.cs Normal file
View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.data;
public class ConfigClient
{
}

1
data/ConfigClient.cs.uid Normal file
View File

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

103
data/SaveClient.cs Normal file
View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Godot;
using Godot.Collections;
using Mr.BrickAdventures.game.repositories;
namespace Mr.BrickAdventures.data;
public sealed class SaveClient
{
private readonly string _path;
private readonly int _version;
public SaveClient(string path, int version) { _path = path; _version = version; }
public bool Exists() => FileAccess.FileExists(_path);
public bool TryLoad(out PlayerState player, out LevelState level) {
player = null!; level = null!;
if (!Exists()) return false;
using var f = FileAccess.Open(_path, FileAccess.ModeFlags.Read);
var dict = (Dictionary)f.GetVar();
if ((int)dict.GetValueOrDefault("version", -1) != _version) return false;
player = ToPlayer((Dictionary)dict["player"]);
level = ToLevel((Dictionary)dict["level"]);
return true;
}
// Strict load: requires version + player_state + level_state.
// If anything is off, delete the file and act as "no save".
public bool TryLoadStrict(out PlayerState player, out LevelState level) {
player = default!;
level = default!;
if (!Exists()) return false;
try {
using var f = FileAccess.Open(_path, FileAccess.ModeFlags.Read);
if (f == null) { Delete(); return false; }
var dictionary = (Dictionary)f.GetVar();
if (!dictionary.TryGetValue("version", out var v) || (int)v != _version) { Delete(); return false; }
if (!dictionary.TryGetValue("player_state", out var pObj) || (Dictionary)pObj is not { } p) { Delete(); return false; }
if (!dictionary.TryGetValue("level_state", out var lObj) || (Dictionary)lObj is not { } l) { Delete(); return false; }
player = ToPlayer(p);
level = ToLevel(l);
return true;
}
catch (Exception e) {
GD.PushWarning($"SaveClient: load failed — deleting bad save. {e.GetType().Name}: {e.Message}");
Delete();
return false;
}
}
public void Save(PlayerState player, LevelState level) {
using var f = FileAccess.Open(_path, FileAccess.ModeFlags.Write);
var dict = new Dictionary {
{ "version", _version },
{ "player", FromPlayer(player) },
{ "level", FromLevel(level) }
};
f.StoreVar(dict);
}
public void Delete() {
if (!Exists()) return;
var abs = ProjectSettings.GlobalizePath(_path);
var ok = DirAccess.RemoveAbsolute(abs);
if (ok != Error.Ok) GD.PushWarning($"SaveClient: failed to delete {_path}: {ok}");
}
private static Dictionary FromPlayer(PlayerState s) => new() { { "coins", s.Coins }, { "lives", s.Lives } };
private static PlayerState ToPlayer(Dictionary d) => new() {
Coins = d.TryGetValue("coins", out var c) ? (int)c : 0,
Lives = d.TryGetValue("lives", out var l) ? (int)l : 3
};
private static Dictionary FromLevel(LevelState s) => new() {
{ "current", s.Current },
{ "unlocked", new Array<int>(s.Unlocked) },
{ "completed", new Array<int>(s.Completed) },
};
private static LevelState ToLevel(Dictionary d) => new() {
Current = d.TryGetValue("current", out var cur) ? (int)cur : 0,
Unlocked = d.TryGetValue("unlocked", out var ul) && (Array<int>)ul is { } a1 ? a1.ToArray() : [0],
Completed = d.TryGetValue("completed", out var cl) && (Array<int>)cl is { } a2 ? a2.ToArray() : [],
};
}
public record SaveSnapshot {
public required PlayerState PlayerState { get; init; }
public required LevelState LevelState { get; init; }
}

1
data/SaveClient.cs.uid Normal file
View File

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

View File

@@ -0,0 +1,40 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Mr.BrickAdventures.app;
using Mr.BrickAdventures.game.services;
namespace Mr.BrickAdventures.features.level;
[Meta(typeof(IAutoNode))]
public partial class ExitDoorComponent : Area2D
{
public override void _Notification(int what) => this.Notify(what);
[Export] public CollisionShape2D UnlockIndicator { get; set; } = null!;
[Export] public bool Unlocked { get; set; } = false;
[Dependency] public LevelService Levels => this.DependOn<LevelService>();
[Dependency] public ILevelCatalog Catalog => this.DependOn<ILevelCatalog>();
[Dependency] public IGameScenes Scenes => this.DependOn<IGameScenes>();
public void OnReady() {
BodyEntered += OnBodyEntered;
UpdateVisuals();
}
private void OnBodyEntered(Node body) {
if (!Unlocked) return;
if (body is not CharacterBody2D) return;
var nextIdx = Levels.CompleteAndAdvance();
var next = Catalog.Get(nextIdx);
if (next != null) Scenes.Load(next); else Scenes.ReturnToMain(Catalog.MainMenu);
}
private void UpdateVisuals() {
if (UnlockIndicator != null) UnlockIndicator.Disabled = !Unlocked;
}
public void SetUnlocked(bool value) { Unlocked = value; UpdateVisuals(); }
}

View File

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

34
features/ui/hud/Hud.cs Normal file
View File

@@ -0,0 +1,34 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Mr.BrickAdventures.game.repositories;
using Mr.BrickAdventures.scripts.components;
namespace Mr.BrickAdventures.features.ui.hud;
[Meta(typeof(IAutoNode))]
public partial class Hud : Node
{
public override void _Notification(int what) => this.Notify(what);
[Export] public HealthComponent Health { get; set; } = null!;
[Export] public Label CoinsLabel { get; set; } = null!;
[Export] public ProgressBar HealthBar { get; set; } = null!;
[Export] public Label LivesLabel { get; set; } = null!;
[Dependency] public PlayerRepository Player => this.DependOn<PlayerRepository>();
public void OnResolved() {
CoinsLabel.Text = $"{Tr("COINS_LABEL")}: {Player.Coins}";
LivesLabel.Text = $"{Tr("LIVES_LABEL")}: {Player.Lives}";
Player.CoinsChanged += c => CoinsLabel.Text = $"{Tr("COINS_LABEL")}: {c}";
Player.LivesChanged += l => LivesLabel.Text = $"{Tr("LIVES_LABEL")}: {l}";
if (Health != null) {
HealthBar.MaxValue = Health.MaxHealth;
HealthBar.Value = Health.Health;
Health.HealthChanged += (_, total) => { HealthBar.Value = total; };
}
}
}

View File

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

View File

@@ -0,0 +1,26 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Mr.BrickAdventures.app;
namespace Mr.BrickAdventures.features.ui.menus;
[Meta(typeof(IAutoNode))]
public partial class GameOverScreen : Control
{
public override void _Notification(int what) => this.Notify(what);
[Export] public Button RestartButton { get; set; } = null!;
[Export] public Button MainMenuButton { get; set; } = null!;
[Export] public PackedScene MainMenuScene { get; set; } = null!;
[Dependency] public IGameScenes Scenes => this.DependOn<IGameScenes>();
public void OnReady() {
Visible = false;
RestartButton.Pressed += () => Scenes.Restart();
MainMenuButton.Pressed += () => Scenes.ReturnToMain(MainMenuScene);
}
public void ShowGameOver() => Show();
}

View File

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

View File

@@ -0,0 +1,58 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Mr.BrickAdventures.app;
using Mr.BrickAdventures.game.repositories;
using Mr.BrickAdventures.game.services;
namespace Mr.BrickAdventures.features.ui.menus;
[Meta(typeof(IAutoNode))]
public partial class MainMenu : Control
{
public override void _Notification(int what) => this.Notify(what);
[Export] public Control MainMenuControl { get; set; } = null!;
[Export] public Button NewGameButton { get; set; } = null!;
[Export] public Button ContinueButton { get; set; } = null!;
[Export] public Button SettingsButton { get; set; } = null!;
[Export] public Button CreditsButton { get; set; } = null!;
[Export] public Button ExitButton { get; set; } = null!;
[Export] public Label VersionLabel { get; set; } = null!;
[Export] public Control SettingsControl { get; set; } = null!;
[Export] public Control CreditsControl { get; set; } = null!;
[Dependency] public SaveService Save => this.DependOn<SaveService>();
[Dependency] public LevelRepository Levels => this.DependOn<LevelRepository>();
[Dependency] public IGameScenes Scenes => this.DependOn<IGameScenes>();
[Dependency] public ILevelCatalog Catalog => this.DependOn<ILevelCatalog>();
public void OnReady() {
VersionLabel.Text = $"v. {ProjectSettings.GetSetting("application/config/version")}";
NewGameButton.Pressed += OnNewGamePressed;
ContinueButton.Pressed += OnContinuePressed;
SettingsButton.Pressed += () => SettingsControl.Show();
CreditsButton.Pressed += () => CreditsControl.Show();
ExitButton.Pressed += () => GetTree().Quit();
}
public void OnResolved()
{
ContinueButton.Disabled = !Save.Exists();
(ContinueButton.Disabled ? NewGameButton : ContinueButton).GrabFocus();
}
private void OnNewGamePressed() {
Save.NewGame();
Levels.SetCurrent(0);
var first = Catalog.First;
Scenes.Load(first);
}
private void OnContinuePressed() {
if (!Save.TryLoad()) { OnNewGamePressed(); return; }
var scene = Catalog.Get(Levels.Current) ?? Catalog.First;
Scenes.Load(scene);
}
}

View File

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

View File

@@ -0,0 +1,33 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using Mr.BrickAdventures.app;
using Mr.BrickAdventures.game.services;
namespace Mr.BrickAdventures.features.ui.menus;
[Meta(typeof(IAutoNode))]
public partial class PauseMenu : Control
{
public override void _Notification(int what) => this.Notify(what);
[Export] public Button ResumeButton { get; set; } = null!;
[Export] public Button RestartButton { get; set; } = null!;
[Export] public Button MainMenuButton { get; set; } = null!;
[Dependency] public IGameScenes Scenes => this.DependOn<IGameScenes>();
[Dependency] public ILevelCatalog Catalog => this.DependOn<ILevelCatalog>();
[Dependency] public LevelService Levels => this.DependOn<LevelService>();
public void OnReady() {
Visible = false;
ResumeButton.Pressed += () => { GetTree().Paused = false; Hide(); };
RestartButton.Pressed += () => { GetTree().Paused = false; Scenes.Restart(); };
MainMenuButton.Pressed += () => { GetTree().Paused = false; Scenes.ReturnToMain(Catalog.MainMenu); };
}
public void Toggle() {
if (Visible) { GetTree().Paused = false; Hide(); }
else { Show(); GetTree().Paused = true; }
}
}

View File

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

View File

@@ -1,25 +1,25 @@
[gd_scene load_steps=3 format=3 uid="uid://8b6ol5sssbgo"] [gd_scene load_steps=3 format=3 uid="uid://8b6ol5sssbgo"]
[ext_resource type="Script" uid="uid://hyfvthdbgjbc" path="res://scripts/ui/main_menu.gd" id="1_epxpl"] [ext_resource type="Script" uid="uid://fcsg0e8s36in" path="res://features/ui/menus/MainMenu.cs" id="1_q8hru"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qv2q0"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qv2q0"]
bg_color = Color(0, 0, 0, 1) bg_color = Color(0, 0, 0, 1)
[node name="MainMenu" type="Control" node_paths=PackedStringArray("main_menu_control", "new_game_button", "continue_button", "settings_button", "credits_button", "exit_button", "version_label")] [node name="MainMenu" type="Control" node_paths=PackedStringArray("MainMenuControl", "NewGameButton", "ContinueButton", "SettingsButton", "CreditsButton", "ExitButton", "VersionLabel")]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_epxpl") script = ExtResource("1_q8hru")
main_menu_control = NodePath(".") MainMenuControl = NodePath(".")
new_game_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/NewGameButton") NewGameButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/NewGameButton")
continue_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/ContinueButton") ContinueButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/ContinueButton")
settings_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/SettingsButton") SettingsButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/SettingsButton")
credits_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/CreditsButton") CreditsButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/CreditsButton")
exit_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/QuitButton") ExitButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/QuitButton")
version_label = NodePath("PanelContainer/MarginContainer/VBoxContainer/version") VersionLabel = NodePath("PanelContainer/MarginContainer/VBoxContainer/version")
[node name="PanelContainer" type="PanelContainer" parent="."] [node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 1 layout_mode = 1

View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.features.ui.settings;
public class AudioSettings
{
}

View File

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

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Mr.BrickAdventures.game.repositories;
public sealed class LevelRepository
{
public int Current { get; private set; } = 0;
public HashSet<int> Unlocked { get; } = new() { 0 };
public HashSet<int> Completed { get; } = new();
public event Action<int>? CurrentChanged;
public void SetCurrent(int idx) { Current = idx; CurrentChanged?.Invoke(Current); }
public void Unlock(int idx) => Unlocked.Add(idx);
public void Complete(int idx) { Completed.Add(idx); Unlock(idx + 1); }
public LevelState Export() => new() {
Current = Current, Unlocked = [..Unlocked], Completed = [..Completed]
};
public void Load(LevelState s) {
Current = s.Current;
Unlocked.Clear(); foreach (var i in s.Unlocked) Unlocked.Add(i);
Completed.Clear(); foreach (var i in s.Completed) Completed.Add(i);
CurrentChanged?.Invoke(Current);
}
}
public record LevelState {
public int Current;
public int[] Unlocked = [];
public int[] Completed = [];
}

View File

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

View File

@@ -0,0 +1,25 @@
using System;
namespace Mr.BrickAdventures.game.repositories;
public sealed class PlayerRepository
{
public int Coins { get; private set; } = 0;
public int Lives { get; private set; } = 3;
public event Action<int>? CoinsChanged;
public event Action<int>? LivesChanged;
public void SetCoins(int value) { Coins = Math.Max(0, value); CoinsChanged?.Invoke(Coins); }
public void AddCoins(int amount) { SetCoins(Coins + amount); }
public void RemoveCoins(int amount){ SetCoins(Coins - amount); }
public void SetLives(int value) { Lives = value; LivesChanged?.Invoke(Lives); }
public void AddLives(int amount) { SetLives(Lives + amount); }
public void RemoveLives(int amount){ SetLives(Lives - amount); }
public PlayerState Export() => new() { Coins = Coins, Lives = Lives };
public void Load(PlayerState s) { SetCoins(s.Coins); SetLives(s.Lives); }
}
public record PlayerState { public int Coins; public int Lives; }

View File

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

View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.game.repositories;
public sealed class SessionRepository
{
}

View File

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

View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.game.repositories;
public class SettingsRepository
{
}

View File

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

View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.game.repositories;
public class SkillsRepository
{
}

View File

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

View File

@@ -0,0 +1,21 @@
using Mr.BrickAdventures.game.repositories;
namespace Mr.BrickAdventures.game.services;
public sealed class LevelService
{
private readonly LevelRepository _levels;
public LevelService(LevelRepository levels) => _levels = levels;
public int CompleteAndAdvance() {
var cur = _levels.Current;
_levels.Complete(cur);
var next = cur + 1;
_levels.SetCurrent(next);
return next;
}
public void StartNew() {
_levels.Load(new LevelState { Current = 0, Unlocked = new [] { 0 }, Completed = [] });
}
}

View File

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

View File

@@ -0,0 +1,32 @@
using Mr.BrickAdventures.data;
using Mr.BrickAdventures.game.repositories;
namespace Mr.BrickAdventures.game.services;
public sealed class SaveService
{
private readonly PlayerRepository _players;
private readonly LevelRepository _levels;
private readonly SaveClient _save;
public SaveService(PlayerRepository players, LevelRepository levels, SaveClient save) {
_players = players; _levels = levels; _save = save;
}
public bool TryLoad() {
if (!_save.TryLoad(out var p, out var l)) return false;
_players.Load(p);
_levels.Load(l);
return true;
}
public void Save() => _save.Save(_players.Export(), _levels.Export());
public bool Exists() => _save.Exists();
public void NewGame() {
_players.Load(new PlayerState { Coins = 0, Lives = 3 });
_levels.Load(new LevelState { Current = 0, Unlocked = [0], Completed = [] });
Save();
}
}

View File

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

View File

@@ -0,0 +1,6 @@
namespace Mr.BrickAdventures.game.services;
public sealed class SkillService
{
}

View File

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

View File

@@ -34,9 +34,8 @@ visible = false
offset_top = 32.0 offset_top = 32.0
components_to_disable = [null] components_to_disable = [null]
[node name="Pause menu" parent="." node_paths=PackedStringArray("settings_menu") instance=ExtResource("6_1q4vn")] [node name="Pause menu" parent="." instance=ExtResource("6_1q4vn")]
visible = false visible = false
settings_menu = NodePath("../Settings menu")
[node name="Settings menu" parent="." node_paths=PackedStringArray("input_settings", "audio_settings") instance=ExtResource("7_hkjav")] [node name="Settings menu" parent="." node_paths=PackedStringArray("input_settings", "audio_settings") instance=ExtResource("7_hkjav")]
visible = false visible = false

View File

@@ -1,12 +1,11 @@
[gd_scene load_steps=4 format=3 uid="uid://i6mnjbjcoqe5"] [gd_scene load_steps=3 format=3 uid="uid://i6mnjbjcoqe5"]
[ext_resource type="Script" uid="uid://cugifchx6jhuk" path="res://scripts/ui/pause_menu.gd" id="1_aktha"] [ext_resource type="Script" uid="uid://bwgs02wcfnm8u" path="res://features/ui/menus/PauseMenu.cs" id="1_ljtns"]
[ext_resource type="PackedScene" uid="uid://cl00e2ocomk3m" path="res://scenes/main_menu.tscn" id="2_h4pd5"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g4ivv"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g4ivv"]
bg_color = Color(0, 0, 0, 1) bg_color = Color(0, 0, 0, 1)
[node name="Pause menu" type="Control" node_paths=PackedStringArray("pause_menu_control", "resume_button", "quit_button", "settings_button", "exit_to_menu_button")] [node name="Pause menu" type="Control" node_paths=PackedStringArray("ResumeButton", "RestartButton", "MainMenuButton")]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -15,13 +14,10 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
size_flags_horizontal = 6 size_flags_horizontal = 6
size_flags_vertical = 6 size_flags_vertical = 6
script = ExtResource("1_aktha") script = ExtResource("1_ljtns")
pause_menu_control = NodePath(".") ResumeButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Resume Button")
resume_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/Resume Button") RestartButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Settings Button")
quit_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/Quit game Button") MainMenuButton = NodePath("PanelContainer/MarginContainer/VBoxContainer/Exit to menu Button")
settings_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/Settings Button")
exit_to_menu_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/Exit to menu Button")
exit_to_menu_scene = ExtResource("2_h4pd5")
[node name="PanelContainer" type="PanelContainer" parent="."] [node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 1 layout_mode = 1

View File

@@ -77,6 +77,10 @@ nodes_to_disable = [NodePath("../../Brick Player")]
skill_unlocker = NodePath("../../Brick Player/SkillUnlockerComponent") skill_unlocker = NodePath("../../Brick Player/SkillUnlockerComponent")
components_to_disable = [NodePath("../../Brick Player")] components_to_disable = [NodePath("../../Brick Player")]
[node name="Pause menu" parent="UI Layer" index="4" node_paths=PackedStringArray("PauseMenuControl", "SettingsControl")]
PauseMenuControl = NodePath(".")
SettingsControl = NodePath("../Settings menu")
[node name="Global Light" parent="." instance=ExtResource("4_mc58c")] [node name="Global Light" parent="." instance=ExtResource("4_mc58c")]
[node name="Camera2D" parent="." instance=ExtResource("5_sskgn")] [node name="Camera2D" parent="." instance=ExtResource("5_sskgn")]

View File

@@ -1,27 +1,38 @@
[gd_scene load_steps=6 format=3 uid="uid://cl00e2ocomk3m"] [gd_scene load_steps=12 format=3 uid="uid://cl00e2ocomk3m"]
[ext_resource type="PackedScene" uid="uid://8b6ol5sssbgo" path="res://objects/ui/main_menu.tscn" id="1_ekxnf"] [ext_resource type="PackedScene" uid="uid://8b6ol5sssbgo" path="res://features/ui/menus/main_menu.tscn" id="1_ekxnf"]
[ext_resource type="Script" uid="uid://dg2l7cw6da4vb" path="res://common/AppRoot.cs" id="1_rtw2f"]
[ext_resource type="PackedScene" uid="uid://y0ae6e7t70fj" path="res://objects/ui/settings_menu.tscn" id="2_bqqt6"] [ext_resource type="PackedScene" uid="uid://y0ae6e7t70fj" path="res://objects/ui/settings_menu.tscn" id="2_bqqt6"]
[ext_resource type="PackedScene" uid="uid://bwgmrcyj4mvu" path="res://objects/ui/credits.tscn" id="3_bqqt6"] [ext_resource type="PackedScene" uid="uid://bwgmrcyj4mvu" path="res://objects/ui/credits.tscn" id="3_bqqt6"]
[ext_resource type="PackedScene" uid="uid://chqb11pfoqmeb" path="res://scenes/level_village_2.tscn" id="3_lgwnu"]
[ext_resource type="PackedScene" uid="uid://bol7g83v2accs" path="res://scenes/level_village_1.tscn" id="3_oa1go"]
[ext_resource type="PackedScene" uid="uid://b5fx1vdfky307" path="res://objects/ui/audio_settings.tscn" id="4_8ln24"] [ext_resource type="PackedScene" uid="uid://b5fx1vdfky307" path="res://objects/ui/audio_settings.tscn" id="4_8ln24"]
[ext_resource type="PackedScene" uid="uid://h60obxmju6mo" path="res://scenes/level_village_3.tscn" id="4_flqon"]
[ext_resource type="PackedScene" uid="uid://bhad760x3vvco" path="res://scenes/level_village_4.tscn" id="5_rcqid"]
[ext_resource type="PackedScene" uid="uid://cvfsbiy5ggrpg" path="res://objects/ui/input_settings.tscn" id="5_rtw2f"] [ext_resource type="PackedScene" uid="uid://cvfsbiy5ggrpg" path="res://objects/ui/input_settings.tscn" id="5_rtw2f"]
[ext_resource type="PackedScene" uid="uid://dagpmlgvr262d" path="res://scenes/level_forest_5.tscn" id="6_1ajci"]
[node name="Main menu" type="CanvasLayer"] [node name="AppRoot" type="Node2D"]
script = ExtResource("1_rtw2f")
_levels = Array[PackedScene]([ExtResource("3_oa1go"), ExtResource("3_lgwnu"), ExtResource("4_flqon"), ExtResource("5_rcqid"), ExtResource("6_1ajci")])
_mainMenu = null
[node name="MainMenu" parent="." node_paths=PackedStringArray("settings_control", "credits_control") instance=ExtResource("1_ekxnf")] [node name="Main menu" type="CanvasLayer" parent="."]
settings_control = NodePath("../Settings menu")
credits_control = NodePath("../Credits")
[node name="Settings menu" parent="." node_paths=PackedStringArray("input_settings", "audio_settings") instance=ExtResource("2_bqqt6")] [node name="MainMenu" parent="Main menu" node_paths=PackedStringArray("SettingsControl", "CreditsControl") instance=ExtResource("1_ekxnf")]
SettingsControl = NodePath("../Settings menu")
CreditsControl = NodePath("../Credits")
[node name="Settings menu" parent="Main menu" node_paths=PackedStringArray("input_settings", "audio_settings") instance=ExtResource("2_bqqt6")]
visible = false visible = false
input_settings = NodePath("../Input Settings") input_settings = NodePath("../Input Settings")
audio_settings = NodePath("../Audio settings") audio_settings = NodePath("../Audio settings")
[node name="Credits" parent="." instance=ExtResource("3_bqqt6")] [node name="Credits" parent="Main menu" instance=ExtResource("3_bqqt6")]
visible = false visible = false
[node name="Audio settings" parent="." instance=ExtResource("4_8ln24")] [node name="Audio settings" parent="Main menu" instance=ExtResource("4_8ln24")]
visible = false visible = false
[node name="Input Settings" parent="." instance=ExtResource("5_rtw2f")] [node name="Input Settings" parent="Main menu" instance=ExtResource("5_rtw2f")]
visible = false visible = false

View File

@@ -1,43 +0,0 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.components;
namespace Mr.BrickAdventures.scripts.UI;
public partial class Hud : Node
{
[Export] public HealthComponent Health { get; set; }
[Export] public Label CoinsLabel { get; set; }
[Export] public ProgressBar HealthBar { get; set; }
[Export] public Label LivesLabel { get; set; }
private GameManager _gameManager;
public override void _Ready()
{
_gameManager = GetNode<GameManager>("/root/GameManager");
}
public override void _Process(double delta)
{
SetHealthBar();
SetLivesLabel();
SetCoinsLabel();
}
private void SetCoinsLabel()
{
CoinsLabel.Text = Tr("COINS_LABEL") + ": " + _gameManager.GetCoins();
}
private void SetLivesLabel()
{
LivesLabel.Text = Tr("LIVES_LABEL") + ": " + _gameManager.GetLives();
}
private void SetHealthBar()
{
HealthBar.Value = Health.Health;
HealthBar.MaxValue = Health.MaxHealth;
}
}

View File

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