Adopt the engine Core/Host split; add LittleSim.Server headless prototype
Engine bump: MrGameEng.Core is now platform-free (Friflo only), the windowed MonoGame host lives in the new MrGameEng.Host library, and the core gains HeadlessHost — a fixed-timestep loop without a window or GPU. Game side: scenes switch to Transitions.Fade from the Host library, WorldScene reads the graphics device via Context.GetGraphicsDevice(), GameContent.Load(buildAtlases: false) skips atlas building for headless runs. LittleSim.Server is the dedicated-server seed and the showcase for HeadlessHost: it loads mods/defs without textures and fast-forwards the world calendar and climate on a fixed tick (~3.6M ticks/s in Debug): dotnet run --project src/LittleSim.Server -- --days 10 --tps 60 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bfe1b139f0
commit
8a7e2cce52
@@ -14,12 +14,14 @@ Game design docs live in `docs/` and are written in **Russian**. Engine rules li
|
|||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
engine/ mrgameeng git submodule (own repo, own CLAUDE.md)
|
engine/ mrgameeng git submodule (own repo, own CLAUDE.md)
|
||||||
src/LittleSim the game (net8.0); references engine projects directly
|
src/LittleSim the game (net8.0); references engine projects directly
|
||||||
Mods/Core the game's own content as a mod: About, Defs, Languages, Textures
|
src/LittleSim.Server dedicated-server prototype: the world headless (engine HeadlessHost),
|
||||||
Cache/ runtime-built atlases (gitignored)
|
loads mods/defs without atlases; the future network server grows here
|
||||||
docs/ концепт, симуляция, моды, roadmap (Russian)
|
Mods/Core the game's own content as a mod: About, Defs, Languages, Textures
|
||||||
LittleSim.sln game + engine sources + engine tests — one window for everything
|
Cache/ runtime-built atlases (gitignored)
|
||||||
|
docs/ концепт, симуляция, моды, roadmap (Russian)
|
||||||
|
LittleSim.sln game + engine sources + engine tests — one window for everything
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
@@ -28,6 +30,7 @@ LittleSim.sln game + engine sources + engine tests — one window for everythin
|
|||||||
git submodule update --init # after fresh clone
|
git submodule update --init # after fresh clone
|
||||||
dotnet build LittleSim.sln
|
dotnet build LittleSim.sln
|
||||||
dotnet run --project src/LittleSim -c Release # measure perf in Release only
|
dotnet run --project src/LittleSim -c Release # measure perf in Release only
|
||||||
|
dotnet run --project src/LittleSim.Server -- --days 10 --tps 60 # headless fast-forward
|
||||||
dotnet test LittleSim.sln # runs the engine test suites
|
dotnet test LittleSim.sln # runs the engine test suites
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.UI.Tests", "engin
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio.Tests", "engine\tests\MrGameEng.Audio.Tests\MrGameEng.Audio.Tests.csproj", "{E0E37D87-4F62-41E9-9CD1-3E7432301508}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio.Tests", "engine\tests\MrGameEng.Audio.Tests\MrGameEng.Audio.Tests.csproj", "{E0E37D87-4F62-41E9-9CD1-3E7432301508}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Host", "engine\src\MrGameEng.Host\MrGameEng.Host.csproj", "{D4222C26-40D8-4465-9B26-CD6BD0722EC0}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Host.Tests", "engine\tests\MrGameEng.Host.Tests\MrGameEng.Host.Tests.csproj", "{41F0249B-106E-4D56-B022-73D2C2D74807}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LittleSim.Server", "src\LittleSim.Server\LittleSim.Server.csproj", "{BEFB468B-6784-468E-9B60-EB44AB078D15}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -231,6 +237,42 @@ Global
|
|||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x64.Build.0 = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.ActiveCfg = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.Build.0 = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -253,5 +295,8 @@ Global
|
|||||||
{86ECA4A0-46A2-45F4-8CF4-8FB8BB5DC050} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{86ECA4A0-46A2-45F4-8CF4-8FB8BB5DC050} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
{BC64F7AF-0FC3-4608-A4FF-1273AB8F2ABD} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{BC64F7AF-0FC3-4608-A4FF-1273AB8F2ABD} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
|
{D4222C26-40D8-4465-9B26-CD6BD0722EC0} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519}
|
||||||
|
{41F0249B-106E-4D56-B022-73D2C2D74807} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
|
{BEFB468B-6784-468E-9B60-EB44AB078D15} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
+1
-1
Submodule engine updated: d0104df304...2ac074004a
@@ -0,0 +1,46 @@
|
|||||||
|
using Friflo.Engine.ECS.Systems;
|
||||||
|
using LittleSim.Content;
|
||||||
|
using LittleSim.Scenes;
|
||||||
|
using MrGameEng.Core;
|
||||||
|
|
||||||
|
namespace LittleSim.Server;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Серверная сцена-зародыш: календарь и климат мира идут на фиксированном тике
|
||||||
|
/// <see cref="HeadlessHost"/>, без окна, GPU и текстур. Демонстрирует headless-режим
|
||||||
|
/// движка; дальше сюда переедет полная симуляция мира (растения, жители) и сетевой слой.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class HeadlessWorldScene : Scene
|
||||||
|
{
|
||||||
|
private readonly GameContent _content;
|
||||||
|
|
||||||
|
public HeadlessWorldScene(GameContent content) => _content = content;
|
||||||
|
|
||||||
|
protected override void OnLoad()
|
||||||
|
{
|
||||||
|
Context.Services.Add(_content);
|
||||||
|
var calendar = Context.UseCalendar(WorldScene.SecondsPerDay);
|
||||||
|
var climate = Context.UseClimate(ClimateSettings.Default);
|
||||||
|
UpdateSystems.Add(new DayReportSystem(calendar, climate));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Пишет строку состояния мира в лог на рассвете каждого игрового дня.</summary>
|
||||||
|
private sealed class DayReportSystem(Calendar calendar, Climate climate) : BaseSystem
|
||||||
|
{
|
||||||
|
private int _lastDay;
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
if (calendar.Day == _lastDay)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastDay = calendar.Day;
|
||||||
|
Log.Info(
|
||||||
|
$"День {calendar.Day} | год {climate.Year}, день года {climate.DayOfYear + 1}, "
|
||||||
|
+ $"{climate.Season} | {climate.Temperature:+0.0;-0.0;0.0} °C"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LittleSim\LittleSim.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using LittleSim.Content;
|
||||||
|
using LittleSim.Scenes;
|
||||||
|
using LittleSim.Server;
|
||||||
|
using MrGameEng.Core;
|
||||||
|
|
||||||
|
// Дедикейтед-сервер LittleSim (прототип): мир без окна и GPU на HeadlessHost движка.
|
||||||
|
// Пока умеет загрузить моды/дефы (без атласов) и прогнать календарь и климат на
|
||||||
|
// фиксированном тике. Использование:
|
||||||
|
// dotnet run --project src/LittleSim.Server [-- --days N] [--tps N]
|
||||||
|
|
||||||
|
var days = ReadOption("--days", 10);
|
||||||
|
var ticksPerSecond = ReadOption("--tps", 60);
|
||||||
|
|
||||||
|
Log.MessageLogged += (level, message) => Console.WriteLine($"[{level}] {message}");
|
||||||
|
|
||||||
|
var content = GameContent.Load(buildAtlases: false);
|
||||||
|
Log.Info(
|
||||||
|
$"Моды: {string.Join(", ", content.Mods.Select(m => m.ToString()))} | "
|
||||||
|
+ $"дефов: {content.Defs.All<TerrainDef>().Count} террейна, "
|
||||||
|
+ $"{content.Defs.All<PlantDef>().Count} растений, "
|
||||||
|
+ $"{content.Defs.All<PawnDef>().Count} жителей"
|
||||||
|
);
|
||||||
|
|
||||||
|
using var host = new HeadlessHost(
|
||||||
|
new HeadlessHostOptions { TicksPerSecond = ticksPerSecond, Realtime = false },
|
||||||
|
new HeadlessWorldScene(content)
|
||||||
|
);
|
||||||
|
|
||||||
|
var ticks = (long)((double)days * WorldScene.SecondsPerDay * ticksPerSecond);
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
host.RunTicks(ticks);
|
||||||
|
Log.Info(
|
||||||
|
$"{days} игровых дней за {stopwatch.Elapsed.TotalSeconds:F1} с реального времени "
|
||||||
|
+ $"({ticks} тиков, {ticks / Math.Max(stopwatch.Elapsed.TotalSeconds, 0.001):F0} тиков/с)"
|
||||||
|
);
|
||||||
|
|
||||||
|
int ReadOption(string name, int fallback)
|
||||||
|
{
|
||||||
|
var index = Array.IndexOf(args, name);
|
||||||
|
return index >= 0 && index + 1 < args.Length && int.TryParse(args[index + 1], out var value)
|
||||||
|
? value
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
@@ -44,8 +44,10 @@ public sealed class GameContent
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Находит папку Mods (вверх по дереву от исполняемого файла), загружает моды, дефы и
|
/// Находит папку Mods (вверх по дереву от исполняемого файла), загружает моды, дефы и
|
||||||
/// языки и инкрементально собирает атласы из смерженного дерева текстур всех модов.
|
/// языки и инкрементально собирает атласы из смерженного дерева текстур всех модов.
|
||||||
|
/// С <paramref name="buildAtlases"/> = false атласы не собираются — headless-режим
|
||||||
|
/// (дедикейтед-сервер) текстур не рисует.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static GameContent Load()
|
public static GameContent Load(bool buildAtlases = true)
|
||||||
{
|
{
|
||||||
var modsRoot =
|
var modsRoot =
|
||||||
ModLoader.FindModsRoot(AppContext.BaseDirectory)
|
ModLoader.FindModsRoot(AppContext.BaseDirectory)
|
||||||
@@ -71,19 +73,26 @@ public sealed class GameContent
|
|||||||
"Cache",
|
"Cache",
|
||||||
"Atlases"
|
"Atlases"
|
||||||
);
|
);
|
||||||
var textures = ModContentTree.Build(mods, "Textures", ".png", ".jpg", ".jpeg", ".bmp");
|
if (buildAtlases)
|
||||||
var result = AtlasBuilder.Build(
|
{
|
||||||
new AtlasBuildOptions
|
var textures = ModContentTree.Build(mods, "Textures", ".png", ".jpg", ".jpeg", ".bmp");
|
||||||
{
|
var result = AtlasBuilder.Build(
|
||||||
OutputDirectory = atlasCacheDirectory,
|
new AtlasBuildOptions
|
||||||
GroupDepth = ModAtlases.GroupDepth,
|
{
|
||||||
},
|
OutputDirectory = atlasCacheDirectory,
|
||||||
textures.Files.Select(f => (f.FullPath, f.RelativePath))
|
GroupDepth = ModAtlases.GroupDepth,
|
||||||
);
|
},
|
||||||
var built = result.Groups.Count(g => !g.Skipped);
|
textures.Files.Select(f => (f.FullPath, f.RelativePath))
|
||||||
Log.Info(
|
);
|
||||||
$"Atlases: {built} built, {result.Groups.Count - built} up to date ({result.Groups.Count} total)"
|
var built = result.Groups.Count(g => !g.Skipped);
|
||||||
);
|
Log.Info(
|
||||||
|
$"Atlases: {built} built, {result.Groups.Count - built} up to date ({result.Groups.Count} total)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Info("Atlases: skipped (headless)");
|
||||||
|
}
|
||||||
|
|
||||||
return new GameContent(
|
return new GameContent(
|
||||||
mods,
|
mods,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Core\MrGameEng.Core.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Core\MrGameEng.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Host\MrGameEng.Host.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Graphics\MrGameEng.Graphics.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Graphics\MrGameEng.Graphics.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Content\MrGameEng.Content.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Content\MrGameEng.Content.csproj" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using LittleSim.Scenes;
|
using LittleSim.Scenes;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Host;
|
||||||
|
|
||||||
// Контент Core-мода грузится уже в окне — на загрузочном экране (BootScene), в фоне.
|
// Контент Core-мода грузится уже в окне — на загрузочном экране (BootScene), в фоне.
|
||||||
using var host = new GameHost(
|
using var host = new GameHost(
|
||||||
|
|||||||
@@ -1,78 +1,79 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Host;
|
||||||
using Myra.Graphics2D.UI;
|
using MrGameEng.UI;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Загрузочный экран. Поднимает аудио и управление скоростью, грузит настройки и применяет
|
/// <summary>
|
||||||
/// окно/громкость, затем грузит контент Core-мода в фоне (чистый CPU/диск — без GPU) с
|
/// Загрузочный экран. Поднимает аудио и управление скоростью, грузит настройки и применяет
|
||||||
/// анимированной надписью. По готовности регистрирует сервисы и уходит в главное меню.
|
/// окно/громкость, затем грузит контент Core-мода в фоне (чистый CPU/диск — без GPU) с
|
||||||
/// </summary>
|
/// анимированной надписью. По готовности регистрирует сервисы и уходит в главное меню.
|
||||||
public sealed class BootScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class BootScene : Scene
|
||||||
private readonly string[] _steps = { "Загрузка", "Loading" };
|
{
|
||||||
private Task<GameContent>? _load;
|
private readonly string[] _steps = { "Загрузка", "Loading" };
|
||||||
private GameSettings _settings = new();
|
private Task<GameContent>? _load;
|
||||||
private Label _label = null!;
|
private GameSettings _settings = new();
|
||||||
|
private Label _label = null!;
|
||||||
protected override void OnLoad()
|
|
||||||
{
|
protected override void OnLoad()
|
||||||
Context.UseAudio();
|
{
|
||||||
Context.UseGameSpeed(1f, 3f, 6f);
|
Context.UseAudio();
|
||||||
|
Context.UseGameSpeed(1f, 3f, 6f);
|
||||||
_settings = GameSettingsStore.Load();
|
|
||||||
var host = (GameHost)Context.Services.Get<Game>();
|
_settings = GameSettingsStore.Load();
|
||||||
host.Graphics.IsFullScreen = _settings.Fullscreen;
|
var host = (GameHost)Context.Services.Get<Game>();
|
||||||
host.Graphics.SynchronizeWithVerticalRetrace = _settings.VSync;
|
host.Graphics.IsFullScreen = _settings.Fullscreen;
|
||||||
host.Graphics.PreferredBackBufferWidth = _settings.Width;
|
host.Graphics.SynchronizeWithVerticalRetrace = _settings.VSync;
|
||||||
host.Graphics.PreferredBackBufferHeight = _settings.Height;
|
host.Graphics.PreferredBackBufferWidth = _settings.Width;
|
||||||
host.Graphics.ApplyChanges();
|
host.Graphics.PreferredBackBufferHeight = _settings.Height;
|
||||||
Context.Services.Get<AudioManager>().MasterVolume = _settings.Volume;
|
host.Graphics.ApplyChanges();
|
||||||
|
Context.Services.Get<AudioManager>().MasterVolume = _settings.Volume;
|
||||||
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
|
||||||
_label = new Label
|
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
||||||
{
|
_label = new Label
|
||||||
TextColor = Ui.Accent,
|
{
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
TextColor = Ui.Accent,
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
};
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
desktop.Root = Ui.Screen(_label);
|
};
|
||||||
|
desktop.Root = Ui.Screen(_label);
|
||||||
// Сборка атласов и дефов — без GPU, поэтому безопасно вне главного потока.
|
|
||||||
_load = Task.Run(GameContent.Load);
|
// Сборка атласов и дефов — без GPU, поэтому безопасно вне главного потока.
|
||||||
UpdateSystems.Add(new CallbackSystem(Tick));
|
_load = Task.Run(() => GameContent.Load());
|
||||||
}
|
UpdateSystems.Add(new CallbackSystem(Tick));
|
||||||
|
}
|
||||||
private void Tick()
|
|
||||||
{
|
private void Tick()
|
||||||
var word = _settings.Language == "en" ? _steps[1] : _steps[0];
|
{
|
||||||
var dots = new string('.', (int)(Context.Clock.UnscaledTotalTime * 2) % 4);
|
var word = _settings.Language == "en" ? _steps[1] : _steps[0];
|
||||||
_label.Text = word + dots;
|
var dots = new string('.', (int)(Context.Clock.UnscaledTotalTime * 2) % 4);
|
||||||
|
_label.Text = word + dots;
|
||||||
if (_load is null || !_load.IsCompleted || Context.Scenes.IsTransitioning)
|
|
||||||
{
|
if (_load is null || !_load.IsCompleted || Context.Scenes.IsTransitioning)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
if (_load.IsFaulted)
|
|
||||||
{
|
if (_load.IsFaulted)
|
||||||
_label.Text = _load.Exception?.GetBaseException().Message ?? "load failed";
|
{
|
||||||
Log.Error($"Content load failed: {_label.Text}");
|
_label.Text = _load.Exception?.GetBaseException().Message ?? "load failed";
|
||||||
return;
|
Log.Error($"Content load failed: {_label.Text}");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
var content = _load.Result;
|
|
||||||
_load = null;
|
var content = _load.Result;
|
||||||
Context.Services.Add(content);
|
_load = null;
|
||||||
Context.Services.Add(new ModAtlases(content.AtlasCacheDirectory));
|
Context.Services.Add(content);
|
||||||
content.Languages.SetLanguage(_settings.Language);
|
Context.Services.Add(new ModAtlases(content.AtlasCacheDirectory));
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.6f));
|
content.Languages.SetLanguage(_settings.Language);
|
||||||
}
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.6f));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,46 +1,47 @@
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Input;
|
||||||
|
using MrGameEng.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>Экран «Авторы»: демонстрационный, пока один автор. Назад — кнопкой или Esc.</summary>
|
|
||||||
public sealed class CreditsScene : Scene
|
/// <summary>Экран «Авторы»: демонстрационный, пока один автор. Назад — кнопкой или Esc.</summary>
|
||||||
{
|
public sealed class CreditsScene : Scene
|
||||||
protected override void OnLoad()
|
{
|
||||||
{
|
protected override void OnLoad()
|
||||||
var lang = Context.Services.Get<GameContent>().Languages;
|
{
|
||||||
var input = this.UseInput();
|
var lang = Context.Services.Get<GameContent>().Languages;
|
||||||
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
var input = this.UseInput();
|
||||||
|
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
||||||
var column = Ui.Column(12);
|
|
||||||
column.Widgets.Add(Ui.Title(lang.Get("credits.title")));
|
var column = Ui.Column(12);
|
||||||
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
column.Widgets.Add(Ui.Title(lang.Get("credits.title")));
|
||||||
column.Widgets.Add(Ui.Subtitle(lang.Get("credits.author")));
|
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
||||||
column.Widgets.Add(Ui.Subtitle(lang.Get("credits.role")));
|
column.Widgets.Add(Ui.Subtitle(lang.Get("credits.author")));
|
||||||
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
column.Widgets.Add(Ui.Subtitle(lang.Get("credits.role")));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("credits.back"), Back));
|
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
||||||
|
column.Widgets.Add(Ui.Button(lang.Get("credits.back"), Back));
|
||||||
desktop.Root = Ui.Screen(column);
|
|
||||||
UpdateSystems.Add(
|
desktop.Root = Ui.Screen(column);
|
||||||
new CallbackSystem(() =>
|
UpdateSystems.Add(
|
||||||
{
|
new CallbackSystem(() =>
|
||||||
if (input.IsKeyPressed(Keys.Escape))
|
{
|
||||||
{
|
if (input.IsKeyPressed(Keys.Escape))
|
||||||
Back();
|
{
|
||||||
}
|
Back();
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
private void Back()
|
|
||||||
{
|
private void Back()
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
{
|
||||||
}
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,110 +1,111 @@
|
|||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Input;
|
||||||
using Myra.Graphics2D.UI;
|
using MrGameEng.UI;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Экран загрузки: список сохранений из <see cref="SaveStore"/> (имя, размер, дата) с
|
/// <summary>
|
||||||
/// кнопками «Загрузить» и «Удалить». Загрузка пересоздаёт мир из полного состояния симуляции.
|
/// Экран загрузки: список сохранений из <see cref="SaveStore"/> (имя, размер, дата) с
|
||||||
/// </summary>
|
/// кнопками «Загрузить» и «Удалить». Загрузка пересоздаёт мир из полного состояния симуляции.
|
||||||
public sealed class LoadGameScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class LoadGameScene : Scene
|
||||||
private readonly SaveStore _store = new();
|
{
|
||||||
private GameContent _content = null!;
|
private readonly SaveStore _store = new();
|
||||||
private Desktop _desktop = null!;
|
private GameContent _content = null!;
|
||||||
|
private Desktop _desktop = null!;
|
||||||
protected override void OnLoad()
|
|
||||||
{
|
protected override void OnLoad()
|
||||||
_content = Context.Services.Get<GameContent>();
|
{
|
||||||
var input = this.UseInput();
|
_content = Context.Services.Get<GameContent>();
|
||||||
_desktop = this.UseUI();
|
var input = this.UseInput();
|
||||||
Rebuild();
|
_desktop = this.UseUI();
|
||||||
|
Rebuild();
|
||||||
UpdateSystems.Add(
|
|
||||||
new CallbackSystem(() =>
|
UpdateSystems.Add(
|
||||||
{
|
new CallbackSystem(() =>
|
||||||
if (input.IsKeyPressed(Keys.Escape))
|
{
|
||||||
{
|
if (input.IsKeyPressed(Keys.Escape))
|
||||||
Back();
|
{
|
||||||
}
|
Back();
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
private void Rebuild()
|
|
||||||
{
|
private void Rebuild()
|
||||||
var lang = _content.Languages;
|
{
|
||||||
var column = Ui.Column(8);
|
var lang = _content.Languages;
|
||||||
column.Widgets.Add(Ui.Title(lang.Get("load.title")));
|
var column = Ui.Column(8);
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
column.Widgets.Add(Ui.Title(lang.Get("load.title")));
|
||||||
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
var saves = _store.List();
|
|
||||||
if (saves.Count == 0)
|
var saves = _store.List();
|
||||||
{
|
if (saves.Count == 0)
|
||||||
column.Widgets.Add(Ui.Subtitle(lang.Get("load.empty")));
|
{
|
||||||
}
|
column.Widgets.Add(Ui.Subtitle(lang.Get("load.empty")));
|
||||||
|
}
|
||||||
foreach (var entry in saves)
|
|
||||||
{
|
foreach (var entry in saves)
|
||||||
var save = entry.Save;
|
{
|
||||||
var row = Ui.Row(8);
|
var save = entry.Save;
|
||||||
row.Widgets.Add(
|
var row = Ui.Row(8);
|
||||||
new Label
|
row.Widgets.Add(
|
||||||
{
|
new Label
|
||||||
TextColor = Ui.Accent,
|
{
|
||||||
Text =
|
TextColor = Ui.Accent,
|
||||||
$"{save.Name} — {save.Width}×{save.Height} — "
|
Text =
|
||||||
+ save.SavedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm"),
|
$"{save.Name} — {save.Width}×{save.Height} — "
|
||||||
}
|
+ save.SavedUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm"),
|
||||||
);
|
}
|
||||||
var file = entry.FileName;
|
);
|
||||||
row.Widgets.Add(
|
var file = entry.FileName;
|
||||||
Ui.Button(
|
row.Widgets.Add(
|
||||||
lang.Get("load.load"),
|
Ui.Button(
|
||||||
() =>
|
lang.Get("load.load"),
|
||||||
{
|
() =>
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(
|
{
|
||||||
new WorldScene(save.ToConfig(), save),
|
Context.Scenes.Switch(
|
||||||
Transition.Fade(0.6f)
|
new WorldScene(save.ToConfig(), save),
|
||||||
);
|
Transitions.Fade(0.6f)
|
||||||
}
|
);
|
||||||
},
|
}
|
||||||
width: 130
|
},
|
||||||
)
|
width: 130
|
||||||
);
|
)
|
||||||
row.Widgets.Add(
|
);
|
||||||
Ui.Button(
|
row.Widgets.Add(
|
||||||
lang.Get("load.delete"),
|
Ui.Button(
|
||||||
() =>
|
lang.Get("load.delete"),
|
||||||
{
|
() =>
|
||||||
_store.Delete(file);
|
{
|
||||||
Rebuild();
|
_store.Delete(file);
|
||||||
},
|
Rebuild();
|
||||||
width: 130
|
},
|
||||||
)
|
width: 130
|
||||||
);
|
)
|
||||||
column.Widgets.Add(row);
|
);
|
||||||
}
|
column.Widgets.Add(row);
|
||||||
|
}
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("load.back"), Back));
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
|
column.Widgets.Add(Ui.Button(lang.Get("load.back"), Back));
|
||||||
_desktop.Root = Ui.Screen(column);
|
|
||||||
}
|
_desktop.Root = Ui.Screen(column);
|
||||||
|
}
|
||||||
private void Back()
|
|
||||||
{
|
private void Back()
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
{
|
||||||
}
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +1,44 @@
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Host;
|
||||||
|
using MrGameEng.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Главное меню: заголовок и кнопки Новый мир / Загрузка / Настройки / Авторы / Выход.
|
/// <summary>
|
||||||
/// Все подписи — из локализации Core-мода. Чистый UI-экран (Myra), без мира и рендерера.
|
/// Главное меню: заголовок и кнопки Новый мир / Загрузка / Настройки / Авторы / Выход.
|
||||||
/// </summary>
|
/// Все подписи — из локализации Core-мода. Чистый UI-экран (Myra), без мира и рендерера.
|
||||||
public sealed class MainMenuScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class MainMenuScene : Scene
|
||||||
protected override void OnLoad()
|
{
|
||||||
{
|
protected override void OnLoad()
|
||||||
var content = Context.Services.Get<GameContent>();
|
{
|
||||||
var lang = content.Languages;
|
var content = Context.Services.Get<GameContent>();
|
||||||
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
var lang = content.Languages;
|
||||||
|
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
||||||
var column = Ui.Column(12);
|
|
||||||
column.Widgets.Add(Ui.Title(lang.Get("menu.title")));
|
var column = Ui.Column(12);
|
||||||
column.Widgets.Add(Ui.Subtitle(lang.Get("menu.subtitle")));
|
column.Widgets.Add(Ui.Title(lang.Get("menu.title")));
|
||||||
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
column.Widgets.Add(Ui.Subtitle(lang.Get("menu.subtitle")));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("menu.newworld"), () => Go(new NewWorldScene())));
|
column.Widgets.Add(new Myra.Graphics2D.UI.Label { Height = 16 });
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("menu.load"), () => Go(new LoadGameScene())));
|
column.Widgets.Add(Ui.Button(lang.Get("menu.newworld"), () => Go(new NewWorldScene())));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("menu.settings"), () => Go(new SettingsScene())));
|
column.Widgets.Add(Ui.Button(lang.Get("menu.load"), () => Go(new LoadGameScene())));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("menu.credits"), () => Go(new CreditsScene())));
|
column.Widgets.Add(Ui.Button(lang.Get("menu.settings"), () => Go(new SettingsScene())));
|
||||||
column.Widgets.Add(
|
column.Widgets.Add(Ui.Button(lang.Get("menu.credits"), () => Go(new CreditsScene())));
|
||||||
Ui.Button(lang.Get("menu.quit"), () => Context.Services.Get<Game>().Exit())
|
column.Widgets.Add(
|
||||||
);
|
Ui.Button(lang.Get("menu.quit"), () => Context.Services.Get<Game>().Exit())
|
||||||
|
);
|
||||||
desktop.Root = Ui.Screen(column);
|
|
||||||
}
|
desktop.Root = Ui.Screen(column);
|
||||||
|
}
|
||||||
private void Go(Scene scene)
|
|
||||||
{
|
private void Go(Scene scene)
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(scene, Transition.Fade(0.4f));
|
{
|
||||||
}
|
Context.Scenes.Switch(scene, Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,166 +1,167 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Input;
|
||||||
using Myra.Graphics2D.UI;
|
using MrGameEng.UI;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Окно настройки нового мира: имя, размер (пресеты из <see cref="WorldPresetDef"/> Core-мода),
|
/// <summary>
|
||||||
/// сид (с кнопкой «случайно») и сглаживание рельефа. «Создать» запускает <see cref="WorldScene"/>.
|
/// Окно настройки нового мира: имя, размер (пресеты из <see cref="WorldPresetDef"/> Core-мода),
|
||||||
/// </summary>
|
/// сид (с кнопкой «случайно») и сглаживание рельефа. «Создать» запускает <see cref="WorldScene"/>.
|
||||||
public sealed class NewWorldScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class NewWorldScene : Scene
|
||||||
private const int MinSmoothing = 1;
|
{
|
||||||
private const int MaxSmoothing = 8;
|
private const int MinSmoothing = 1;
|
||||||
|
private const int MaxSmoothing = 8;
|
||||||
private WorldPresetDef _preset = null!;
|
|
||||||
private int _smoothing = 4;
|
private WorldPresetDef _preset = null!;
|
||||||
private TextBox _name = null!;
|
private int _smoothing = 4;
|
||||||
private TextBox _seed = null!;
|
private TextBox _name = null!;
|
||||||
private readonly List<Action> _refreshers = [];
|
private TextBox _seed = null!;
|
||||||
|
private readonly List<Action> _refreshers = [];
|
||||||
protected override void OnLoad()
|
|
||||||
{
|
protected override void OnLoad()
|
||||||
var content = Context.Services.Get<GameContent>();
|
{
|
||||||
var lang = content.Languages;
|
var content = Context.Services.Get<GameContent>();
|
||||||
var input = this.UseInput();
|
var lang = content.Languages;
|
||||||
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
var input = this.UseInput();
|
||||||
|
var desktop = this.UseUI(); // ставит MyraEnvironment.Game до создания виджетов
|
||||||
var presets = content.Defs.All<WorldPresetDef>().OrderBy(p => p.Order).ToList();
|
|
||||||
_preset = presets[0];
|
var presets = content.Defs.All<WorldPresetDef>().OrderBy(p => p.Order).ToList();
|
||||||
|
_preset = presets[0];
|
||||||
var column = Ui.Column(10);
|
|
||||||
column.Widgets.Add(Ui.Title(lang.Get("newworld.title")));
|
var column = Ui.Column(10);
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
column.Widgets.Add(Ui.Title(lang.Get("newworld.title")));
|
||||||
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
_name = new TextBox { Text = lang.Get("newworld.defaultname"), Width = 280 };
|
|
||||||
column.Widgets.Add(LabeledRow(lang.Get("newworld.name"), _name));
|
_name = new TextBox { Text = lang.Get("newworld.defaultname"), Width = 280 };
|
||||||
|
column.Widgets.Add(LabeledRow(lang.Get("newworld.name"), _name));
|
||||||
// Размер — сегменты пресетов.
|
|
||||||
var sizeRow = Ui.Row(6);
|
// Размер — сегменты пресетов.
|
||||||
sizeRow.Widgets.Add(new Label { Text = lang.Get("newworld.size"), TextColor = Ui.Muted });
|
var sizeRow = Ui.Row(6);
|
||||||
foreach (var preset in presets)
|
sizeRow.Widgets.Add(new Label { Text = lang.Get("newworld.size"), TextColor = Ui.Muted });
|
||||||
{
|
foreach (var preset in presets)
|
||||||
var value = preset;
|
{
|
||||||
var button = new TextButton { Text = lang.Get(preset.Label) };
|
var value = preset;
|
||||||
button.Click += (_, _) =>
|
var button = new TextButton { Text = lang.Get(preset.Label) };
|
||||||
{
|
button.Click += (_, _) =>
|
||||||
_preset = value;
|
{
|
||||||
Refresh();
|
_preset = value;
|
||||||
};
|
Refresh();
|
||||||
sizeRow.Widgets.Add(button);
|
};
|
||||||
_refreshers.Add(() =>
|
sizeRow.Widgets.Add(button);
|
||||||
button.TextColor = ReferenceEquals(_preset, value) ? Ui.Accent : Ui.Muted
|
_refreshers.Add(() =>
|
||||||
);
|
button.TextColor = ReferenceEquals(_preset, value) ? Ui.Accent : Ui.Muted
|
||||||
}
|
);
|
||||||
|
}
|
||||||
column.Widgets.Add(sizeRow);
|
|
||||||
|
column.Widgets.Add(sizeRow);
|
||||||
// Сид + случайно.
|
|
||||||
_seed = new TextBox { Text = Random.Shared.Next().ToString(), Width = 200 };
|
// Сид + случайно.
|
||||||
var seedRow = Ui.Row(6);
|
_seed = new TextBox { Text = Random.Shared.Next().ToString(), Width = 200 };
|
||||||
seedRow.Widgets.Add(new Label { Text = lang.Get("newworld.seed"), TextColor = Ui.Muted });
|
var seedRow = Ui.Row(6);
|
||||||
seedRow.Widgets.Add(_seed);
|
seedRow.Widgets.Add(new Label { Text = lang.Get("newworld.seed"), TextColor = Ui.Muted });
|
||||||
seedRow.Widgets.Add(
|
seedRow.Widgets.Add(_seed);
|
||||||
Ui.Button(
|
seedRow.Widgets.Add(
|
||||||
lang.Get("newworld.random"),
|
Ui.Button(
|
||||||
() => _seed.Text = Random.Shared.Next().ToString(),
|
lang.Get("newworld.random"),
|
||||||
width: 120
|
() => _seed.Text = Random.Shared.Next().ToString(),
|
||||||
)
|
width: 120
|
||||||
);
|
)
|
||||||
column.Widgets.Add(seedRow);
|
);
|
||||||
|
column.Widgets.Add(seedRow);
|
||||||
// Сглаживание −/+.
|
|
||||||
var smoothRow = Ui.Row(6);
|
// Сглаживание −/+.
|
||||||
var smoothLabel = new Label { TextColor = Ui.Muted };
|
var smoothRow = Ui.Row(6);
|
||||||
_refreshers.Add(() => smoothLabel.Text = $"{lang.Get("newworld.smoothing")}: {_smoothing}");
|
var smoothLabel = new Label { TextColor = Ui.Muted };
|
||||||
var minus = new TextButton { Text = "−" };
|
_refreshers.Add(() => smoothLabel.Text = $"{lang.Get("newworld.smoothing")}: {_smoothing}");
|
||||||
minus.Click += (_, _) =>
|
var minus = new TextButton { Text = "−" };
|
||||||
{
|
minus.Click += (_, _) =>
|
||||||
_smoothing = Math.Max(MinSmoothing, _smoothing - 1);
|
{
|
||||||
Refresh();
|
_smoothing = Math.Max(MinSmoothing, _smoothing - 1);
|
||||||
};
|
Refresh();
|
||||||
var plus = new TextButton { Text = "+" };
|
};
|
||||||
plus.Click += (_, _) =>
|
var plus = new TextButton { Text = "+" };
|
||||||
{
|
plus.Click += (_, _) =>
|
||||||
_smoothing = Math.Min(MaxSmoothing, _smoothing + 1);
|
{
|
||||||
Refresh();
|
_smoothing = Math.Min(MaxSmoothing, _smoothing + 1);
|
||||||
};
|
Refresh();
|
||||||
smoothRow.Widgets.Add(smoothLabel);
|
};
|
||||||
smoothRow.Widgets.Add(minus);
|
smoothRow.Widgets.Add(smoothLabel);
|
||||||
smoothRow.Widgets.Add(plus);
|
smoothRow.Widgets.Add(minus);
|
||||||
column.Widgets.Add(smoothRow);
|
smoothRow.Widgets.Add(plus);
|
||||||
|
column.Widgets.Add(smoothRow);
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
|
||||||
var buttons = Ui.Row(10);
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
buttons.Widgets.Add(Ui.Button(lang.Get("newworld.create"), Create, width: 150));
|
var buttons = Ui.Row(10);
|
||||||
buttons.Widgets.Add(Ui.Button(lang.Get("newworld.back"), Back, width: 150));
|
buttons.Widgets.Add(Ui.Button(lang.Get("newworld.create"), Create, width: 150));
|
||||||
column.Widgets.Add(buttons);
|
buttons.Widgets.Add(Ui.Button(lang.Get("newworld.back"), Back, width: 150));
|
||||||
|
column.Widgets.Add(buttons);
|
||||||
desktop.Root = Ui.Screen(column);
|
|
||||||
Refresh();
|
desktop.Root = Ui.Screen(column);
|
||||||
|
Refresh();
|
||||||
UpdateSystems.Add(
|
|
||||||
new CallbackSystem(() =>
|
UpdateSystems.Add(
|
||||||
{
|
new CallbackSystem(() =>
|
||||||
if (input.IsKeyPressed(Keys.Escape))
|
{
|
||||||
{
|
if (input.IsKeyPressed(Keys.Escape))
|
||||||
Back();
|
{
|
||||||
}
|
Back();
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
private static Widget LabeledRow(string label, Widget control)
|
|
||||||
{
|
private static Widget LabeledRow(string label, Widget control)
|
||||||
var row = Ui.Row(6);
|
{
|
||||||
row.Widgets.Add(new Label { Text = label, TextColor = Ui.Muted });
|
var row = Ui.Row(6);
|
||||||
row.Widgets.Add(control);
|
row.Widgets.Add(new Label { Text = label, TextColor = Ui.Muted });
|
||||||
return row;
|
row.Widgets.Add(control);
|
||||||
}
|
return row;
|
||||||
|
}
|
||||||
private void Refresh()
|
|
||||||
{
|
private void Refresh()
|
||||||
foreach (var refresh in _refreshers)
|
{
|
||||||
{
|
foreach (var refresh in _refreshers)
|
||||||
refresh();
|
{
|
||||||
}
|
refresh();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void Create()
|
|
||||||
{
|
private void Create()
|
||||||
if (Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (Context.Scenes.IsTransitioning)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
var seed = int.TryParse(_seed.Text, out var parsed) ? parsed : Random.Shared.Next();
|
|
||||||
var name = string.IsNullOrWhiteSpace(_name.Text) ? "World" : _name.Text.Trim();
|
var seed = int.TryParse(_seed.Text, out var parsed) ? parsed : Random.Shared.Next();
|
||||||
var config = new WorldConfig
|
var name = string.IsNullOrWhiteSpace(_name.Text) ? "World" : _name.Text.Trim();
|
||||||
{
|
var config = new WorldConfig
|
||||||
Name = name,
|
{
|
||||||
Width = _preset.Width,
|
Name = name,
|
||||||
Height = _preset.Height,
|
Width = _preset.Width,
|
||||||
Population = _preset.Population,
|
Height = _preset.Height,
|
||||||
Seed = seed,
|
Population = _preset.Population,
|
||||||
SmoothPasses = _smoothing,
|
Seed = seed,
|
||||||
};
|
SmoothPasses = _smoothing,
|
||||||
Context.Scenes.Switch(new WorldScene(config), Transition.Fade(0.6f));
|
};
|
||||||
}
|
Context.Scenes.Switch(new WorldScene(config), Transitions.Fade(0.6f));
|
||||||
|
}
|
||||||
private void Back()
|
|
||||||
{
|
private void Back()
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
{
|
||||||
}
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+168
-167
@@ -1,167 +1,168 @@
|
|||||||
using System;
|
using System;
|
||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using Myra.Graphics2D.Brushes;
|
using MrGameEng.Host;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.Brushes;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Внутриигровое меню-пауза: затемняющий оверлей поверх мира (мир остаётся загруженным).
|
/// <summary>
|
||||||
/// Открытие ставит игру на паузу через <see cref="GameSpeed"/>, закрытие — снимает.
|
/// Внутриигровое меню-пауза: затемняющий оверлей поверх мира (мир остаётся загруженным).
|
||||||
/// Кнопки: Продолжить / Настройки (общая <see cref="SettingsPanel"/>) / Сохранить /
|
/// Открытие ставит игру на паузу через <see cref="GameSpeed"/>, закрытие — снимает.
|
||||||
/// Главное меню / Выход. <see cref="Root"/> добавляется в корневую панель сцены.
|
/// Кнопки: Продолжить / Настройки (общая <see cref="SettingsPanel"/>) / Сохранить /
|
||||||
/// </summary>
|
/// Главное меню / Выход. <see cref="Root"/> добавляется в корневую панель сцены.
|
||||||
internal sealed class PauseMenu
|
/// </summary>
|
||||||
{
|
internal sealed class PauseMenu
|
||||||
private readonly EngineContext _context;
|
{
|
||||||
private readonly GameContent _content;
|
private readonly EngineContext _context;
|
||||||
private readonly GameSpeed _speed;
|
private readonly GameContent _content;
|
||||||
private readonly GameHost _host;
|
private readonly GameSpeed _speed;
|
||||||
private readonly AudioManager _audio;
|
private readonly GameHost _host;
|
||||||
private readonly Func<string> _onSave;
|
private readonly AudioManager _audio;
|
||||||
private readonly Action _onMainMenu;
|
private readonly Func<string> _onSave;
|
||||||
private readonly Action _onQuit;
|
private readonly Action _onMainMenu;
|
||||||
private readonly Panel _overlay;
|
private readonly Action _onQuit;
|
||||||
private GameSettings _settings = GameSettingsStore.Load();
|
private readonly Panel _overlay;
|
||||||
private bool _inSettings;
|
private GameSettings _settings = GameSettingsStore.Load();
|
||||||
private string? _savedToast;
|
private bool _inSettings;
|
||||||
|
private string? _savedToast;
|
||||||
public PauseMenu(
|
|
||||||
EngineContext context,
|
public PauseMenu(
|
||||||
GameContent content,
|
EngineContext context,
|
||||||
GameSpeed speed,
|
GameContent content,
|
||||||
Func<string> onSave,
|
GameSpeed speed,
|
||||||
Action onMainMenu,
|
Func<string> onSave,
|
||||||
Action onQuit
|
Action onMainMenu,
|
||||||
)
|
Action onQuit
|
||||||
{
|
)
|
||||||
_context = context;
|
{
|
||||||
_content = content;
|
_context = context;
|
||||||
_speed = speed;
|
_content = content;
|
||||||
_host = (GameHost)context.Services.Get<Game>();
|
_speed = speed;
|
||||||
_audio = context.Services.Get<AudioManager>();
|
_host = (GameHost)context.Services.Get<Game>();
|
||||||
_onSave = onSave;
|
_audio = context.Services.Get<AudioManager>();
|
||||||
_onMainMenu = onMainMenu;
|
_onSave = onSave;
|
||||||
_onQuit = onQuit;
|
_onMainMenu = onMainMenu;
|
||||||
|
_onQuit = onQuit;
|
||||||
_overlay = new Panel
|
|
||||||
{
|
_overlay = new Panel
|
||||||
Visible = false,
|
{
|
||||||
Background = new SolidBrush(new Color(0, 0, 0, 200)),
|
Visible = false,
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
Background = new SolidBrush(new Color(0, 0, 0, 200)),
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
};
|
VerticalAlignment = VerticalAlignment.Stretch,
|
||||||
}
|
};
|
||||||
|
}
|
||||||
/// <summary>Виджет оверлея для добавления в корень сцены.</summary>
|
|
||||||
public Widget Root => _overlay;
|
/// <summary>Виджет оверлея для добавления в корень сцены.</summary>
|
||||||
|
public Widget Root => _overlay;
|
||||||
/// <summary>Открыт ли оверлей паузы.</summary>
|
|
||||||
public bool IsOpen { get; private set; }
|
/// <summary>Открыт ли оверлей паузы.</summary>
|
||||||
|
public bool IsOpen { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// Реакция на Esc: закрыть подменю настроек → вернуться к кнопкам; иначе открыть/закрыть
|
/// <summary>
|
||||||
/// меню паузы.
|
/// Реакция на Esc: закрыть подменю настроек → вернуться к кнопкам; иначе открыть/закрыть
|
||||||
/// </summary>
|
/// меню паузы.
|
||||||
public void Toggle()
|
/// </summary>
|
||||||
{
|
public void Toggle()
|
||||||
if (!IsOpen)
|
{
|
||||||
{
|
if (!IsOpen)
|
||||||
Open();
|
{
|
||||||
}
|
Open();
|
||||||
else if (_inSettings)
|
}
|
||||||
{
|
else if (_inSettings)
|
||||||
_inSettings = false;
|
{
|
||||||
ShowButtons();
|
_inSettings = false;
|
||||||
}
|
ShowButtons();
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
Close();
|
{
|
||||||
}
|
Close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void Open()
|
|
||||||
{
|
private void Open()
|
||||||
IsOpen = true;
|
{
|
||||||
_inSettings = false;
|
IsOpen = true;
|
||||||
_savedToast = null;
|
_inSettings = false;
|
||||||
_speed.Pause();
|
_savedToast = null;
|
||||||
ShowButtons();
|
_speed.Pause();
|
||||||
_overlay.Visible = true;
|
ShowButtons();
|
||||||
}
|
_overlay.Visible = true;
|
||||||
|
}
|
||||||
private void Close()
|
|
||||||
{
|
private void Close()
|
||||||
IsOpen = false;
|
{
|
||||||
_inSettings = false;
|
IsOpen = false;
|
||||||
_overlay.Visible = false;
|
_inSettings = false;
|
||||||
_speed.Resume();
|
_overlay.Visible = false;
|
||||||
}
|
_speed.Resume();
|
||||||
|
}
|
||||||
private void ShowButtons()
|
|
||||||
{
|
private void ShowButtons()
|
||||||
var lang = _content.Languages;
|
{
|
||||||
var column = Ui.Column(10);
|
var lang = _content.Languages;
|
||||||
column.Widgets.Add(Ui.Title(lang.Get("pause.title")));
|
var column = Ui.Column(10);
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
column.Widgets.Add(Ui.Title(lang.Get("pause.title")));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("pause.resume"), Close));
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
column.Widgets.Add(
|
column.Widgets.Add(Ui.Button(lang.Get("pause.resume"), Close));
|
||||||
Ui.Button(
|
column.Widgets.Add(
|
||||||
lang.Get("pause.settings"),
|
Ui.Button(
|
||||||
() =>
|
lang.Get("pause.settings"),
|
||||||
{
|
() =>
|
||||||
_inSettings = true;
|
{
|
||||||
ShowSettings();
|
_inSettings = true;
|
||||||
}
|
ShowSettings();
|
||||||
)
|
}
|
||||||
);
|
)
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("pause.save"), Save));
|
);
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("pause.mainmenu"), _onMainMenu));
|
column.Widgets.Add(Ui.Button(lang.Get("pause.save"), Save));
|
||||||
column.Widgets.Add(Ui.Button(lang.Get("pause.quit"), _onQuit));
|
column.Widgets.Add(Ui.Button(lang.Get("pause.mainmenu"), _onMainMenu));
|
||||||
|
column.Widgets.Add(Ui.Button(lang.Get("pause.quit"), _onQuit));
|
||||||
if (_savedToast is not null)
|
|
||||||
{
|
if (_savedToast is not null)
|
||||||
column.Widgets.Add(
|
{
|
||||||
new Label { Text = lang.Format("pause.saved", _savedToast), TextColor = Ui.Muted }
|
column.Widgets.Add(
|
||||||
);
|
new Label { Text = lang.Format("pause.saved", _savedToast), TextColor = Ui.Muted }
|
||||||
}
|
);
|
||||||
|
}
|
||||||
SetContent(column);
|
|
||||||
}
|
SetContent(column);
|
||||||
|
}
|
||||||
private void ShowSettings() =>
|
|
||||||
SetContent(
|
private void ShowSettings() =>
|
||||||
SettingsPanel.Build(
|
SetContent(
|
||||||
_settings,
|
SettingsPanel.Build(
|
||||||
_content.Languages,
|
_settings,
|
||||||
onApply: () =>
|
_content.Languages,
|
||||||
{
|
onApply: () =>
|
||||||
GameSettingsStore.Save(_settings);
|
{
|
||||||
GameSettingsStore.Apply(_settings, _host.Graphics, _content.Languages, _audio);
|
GameSettingsStore.Save(_settings);
|
||||||
ShowSettings(); // язык мог смениться
|
GameSettingsStore.Apply(_settings, _host.Graphics, _content.Languages, _audio);
|
||||||
},
|
ShowSettings(); // язык мог смениться
|
||||||
onBack: () =>
|
},
|
||||||
{
|
onBack: () =>
|
||||||
_inSettings = false;
|
{
|
||||||
ShowButtons();
|
_inSettings = false;
|
||||||
}
|
ShowButtons();
|
||||||
)
|
}
|
||||||
);
|
)
|
||||||
|
);
|
||||||
private void Save()
|
|
||||||
{
|
private void Save()
|
||||||
_savedToast = _onSave();
|
{
|
||||||
ShowButtons();
|
_savedToast = _onSave();
|
||||||
}
|
ShowButtons();
|
||||||
|
}
|
||||||
private void SetContent(Widget content)
|
|
||||||
{
|
private void SetContent(Widget content)
|
||||||
_overlay.Widgets.Clear();
|
{
|
||||||
_overlay.Widgets.Add(content);
|
_overlay.Widgets.Clear();
|
||||||
}
|
_overlay.Widgets.Add(content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,65 +1,66 @@
|
|||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Input;
|
||||||
using Myra.Graphics2D.UI;
|
using MrGameEng.UI;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Экран настроек игры (язык, экран, громкость, разрешение). Использует общий
|
/// <summary>
|
||||||
/// <see cref="SettingsPanel"/>. «Применить» сохраняет в settings.json и применяет к движку
|
/// Экран настроек игры (язык, экран, громкость, разрешение). Использует общий
|
||||||
/// (смена языка перестраивает подписи на лету); «Назад»/Esc — в главное меню.
|
/// <see cref="SettingsPanel"/>. «Применить» сохраняет в settings.json и применяет к движку
|
||||||
/// </summary>
|
/// (смена языка перестраивает подписи на лету); «Назад»/Esc — в главное меню.
|
||||||
public sealed class SettingsScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class SettingsScene : Scene
|
||||||
private GameContent _content = null!;
|
{
|
||||||
private AudioManager _audio = null!;
|
private GameContent _content = null!;
|
||||||
private GameHost _host = null!;
|
private AudioManager _audio = null!;
|
||||||
private Desktop _desktop = null!;
|
private GameHost _host = null!;
|
||||||
private GameSettings _settings = GameSettingsStore.Load();
|
private Desktop _desktop = null!;
|
||||||
|
private GameSettings _settings = GameSettingsStore.Load();
|
||||||
protected override void OnLoad()
|
|
||||||
{
|
protected override void OnLoad()
|
||||||
_content = Context.Services.Get<GameContent>();
|
{
|
||||||
_audio = Context.Services.Get<AudioManager>();
|
_content = Context.Services.Get<GameContent>();
|
||||||
_host = (GameHost)Context.Services.Get<Game>();
|
_audio = Context.Services.Get<AudioManager>();
|
||||||
var input = this.UseInput();
|
_host = (GameHost)Context.Services.Get<Game>();
|
||||||
|
var input = this.UseInput();
|
||||||
_desktop = this.UseUI();
|
|
||||||
Rebuild();
|
_desktop = this.UseUI();
|
||||||
|
Rebuild();
|
||||||
UpdateSystems.Add(
|
|
||||||
new CallbackSystem(() =>
|
UpdateSystems.Add(
|
||||||
{
|
new CallbackSystem(() =>
|
||||||
if (input.IsKeyPressed(Keys.Escape))
|
{
|
||||||
{
|
if (input.IsKeyPressed(Keys.Escape))
|
||||||
Back();
|
{
|
||||||
}
|
Back();
|
||||||
})
|
}
|
||||||
);
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
private void Rebuild() =>
|
|
||||||
_desktop.Root = Ui.Screen(SettingsPanel.Build(_settings, _content.Languages, Apply, Back));
|
private void Rebuild() =>
|
||||||
|
_desktop.Root = Ui.Screen(SettingsPanel.Build(_settings, _content.Languages, Apply, Back));
|
||||||
private void Apply()
|
|
||||||
{
|
private void Apply()
|
||||||
GameSettingsStore.Save(_settings);
|
{
|
||||||
GameSettingsStore.Apply(_settings, _host.Graphics, _content.Languages, _audio);
|
GameSettingsStore.Save(_settings);
|
||||||
Rebuild(); // язык мог смениться — перестраиваем подписи
|
GameSettingsStore.Apply(_settings, _host.Graphics, _content.Languages, _audio);
|
||||||
}
|
Rebuild(); // язык мог смениться — перестраиваем подписи
|
||||||
|
}
|
||||||
private void Back()
|
|
||||||
{
|
private void Back()
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
{
|
||||||
}
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+312
-311
@@ -1,311 +1,312 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Friflo.Engine.ECS;
|
using Friflo.Engine.ECS;
|
||||||
using LittleSim.App;
|
using LittleSim.App;
|
||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.Sim;
|
using LittleSim.Sim;
|
||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Assets;
|
using MrGameEng.Assets;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.DevConsole;
|
using MrGameEng.DevConsole;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Inspector;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.Lighting;
|
using MrGameEng.Inspector;
|
||||||
using MrGameEng.Tilemaps;
|
using MrGameEng.Lighting;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.Tilemaps;
|
||||||
using Myra.Graphics2D;
|
using MrGameEng.UI;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
namespace LittleSim.Scenes;
|
|
||||||
|
namespace LittleSim.Scenes;
|
||||||
/// <summary>
|
|
||||||
/// Мир LittleSim: тайловый рельеф целиком описан дефами Core-мода и строится из
|
/// <summary>
|
||||||
/// <see cref="WorldConfig"/> (размер/сид/масштаб деталей) процедурной генерацией движка
|
/// Мир LittleSim: тайловый рельеф целиком описан дефами Core-мода и строится из
|
||||||
/// (<see cref="WorldGenerator"/>). Каждый тип клетки рисуется текстурой-поверхностью из атласа
|
/// <see cref="WorldConfig"/> (размер/сид/масштаб деталей) процедурной генерацией движка
|
||||||
/// (вода — тонированным тайлом). Поверх мира — HUD, полоса скорости (пауза/x1/x3/x6 + горячие
|
/// (<see cref="WorldGenerator"/>). Каждый тип клетки рисуется текстурой-поверхностью из атласа
|
||||||
/// клавиши), меню-пауза (Esc) и дев-консоль. Жителей/растений пока нет — только террейн.
|
/// (вода — тонированным тайлом). Поверх мира — HUD, полоса скорости (пауза/x1/x3/x6 + горячие
|
||||||
/// </summary>
|
/// клавиши), меню-пауза (Esc) и дев-консоль. Жителей/растений пока нет — только террейн.
|
||||||
public sealed class WorldScene : Scene
|
/// </summary>
|
||||||
{
|
public sealed class WorldScene : Scene
|
||||||
public const int CellSize = 16;
|
{
|
||||||
|
public const int CellSize = 16;
|
||||||
/// <summary>
|
|
||||||
/// Сколько секунд масштабированного времени длится один игровой день. База (x1) — 3 игровые
|
/// <summary>
|
||||||
/// минуты за 1 реальную секунду: сутки = 1440 мин ÷ 3 = 480 с.
|
/// Сколько секунд масштабированного времени длится один игровой день. База (x1) — 3 игровые
|
||||||
/// </summary>
|
/// минуты за 1 реальную секунду: сутки = 1440 мин ÷ 3 = 480 с.
|
||||||
public const float SecondsPerDay = 480f;
|
/// </summary>
|
||||||
|
public const float SecondsPerDay = 480f;
|
||||||
private readonly WorldConfig _config;
|
|
||||||
private readonly WorldSave? _save;
|
private readonly WorldConfig _config;
|
||||||
private readonly RectF _bounds;
|
private readonly WorldSave? _save;
|
||||||
|
private readonly RectF _bounds;
|
||||||
private GameSpeed _speed = null!;
|
|
||||||
private PauseMenu _pause = null!;
|
private GameSpeed _speed = null!;
|
||||||
private readonly List<Action> _speedRefreshers = [];
|
private PauseMenu _pause = null!;
|
||||||
|
private readonly List<Action> _speedRefreshers = [];
|
||||||
/// <summary>Новый мир из конфига.</summary>
|
|
||||||
public WorldScene(WorldConfig config)
|
/// <summary>Новый мир из конфига.</summary>
|
||||||
: this(config, null) { }
|
public WorldScene(WorldConfig config)
|
||||||
|
: this(config, null) { }
|
||||||
/// <summary>Мир, восстановленный из сохранения (рельеф детерминирован сидом конфига).</summary>
|
|
||||||
public WorldScene(WorldConfig config, WorldSave? save)
|
/// <summary>Мир, восстановленный из сохранения (рельеф детерминирован сидом конфига).</summary>
|
||||||
{
|
public WorldScene(WorldConfig config, WorldSave? save)
|
||||||
_config = config;
|
{
|
||||||
_save = save;
|
_config = config;
|
||||||
_bounds = new RectF(0f, 0f, config.Width * CellSize, config.Height * CellSize);
|
_save = save;
|
||||||
}
|
_bounds = new RectF(0f, 0f, config.Width * CellSize, config.Height * CellSize);
|
||||||
|
}
|
||||||
protected override void OnLoad()
|
|
||||||
{
|
protected override void OnLoad()
|
||||||
var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
|
{
|
||||||
var content = Context.Services.Get<GameContent>();
|
var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
|
||||||
var atlases = Context.Services.Get<ModAtlases>();
|
var content = Context.Services.Get<GameContent>();
|
||||||
var device = Context.GraphicsDevice;
|
var atlases = Context.Services.Get<ModAtlases>();
|
||||||
var input = this.UseInput();
|
var device = Context.GetGraphicsDevice();
|
||||||
_speed = Context.Services.Get<GameSpeed>();
|
var input = this.UseInput();
|
||||||
_speed.SetStep(0); // новый мир/загрузка стартуют на x1
|
_speed = Context.Services.Get<GameSpeed>();
|
||||||
|
_speed.SetStep(0); // новый мир/загрузка стартуют на x1
|
||||||
var renderer = this.UseRenderer2D(
|
|
||||||
new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }
|
var renderer = this.UseRenderer2D(
|
||||||
);
|
new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }
|
||||||
GameLayers.EnsureRegistered(renderer);
|
);
|
||||||
this.UseTilemaps();
|
GameLayers.EnsureRegistered(renderer);
|
||||||
|
this.UseTilemaps();
|
||||||
var calendar = Context.UseCalendar(SecondsPerDay);
|
|
||||||
var climate = Context.UseClimate(ClimateSettings.Default);
|
var calendar = Context.UseCalendar(SecondsPerDay);
|
||||||
var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
|
var climate = Context.UseClimate(ClimateSettings.Default);
|
||||||
|
var dayNight = this.UseDayNight(renderer); // мир темнеет ночью — амбиент идёт в рендер
|
||||||
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
|
|
||||||
var plants = new PlantSet(content, atlases, device);
|
// Рельеф и расстановка растений детерминированы сидом мира (независимые потоки seed).
|
||||||
var random = new Random(_config.Seed);
|
var plants = new PlantSet(content, atlases, device);
|
||||||
BuildTerrain(content, atlases, device, assets, plants, random);
|
var random = new Random(_config.Seed);
|
||||||
|
BuildTerrain(content, atlases, device, assets, plants, random);
|
||||||
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
|
||||||
|
var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));
|
||||||
// HUD, полоса скорости и меню-пауза в одной корневой панели.
|
|
||||||
var desktop = this.UseUI();
|
// HUD, полоса скорости и меню-пауза в одной корневой панели.
|
||||||
var hudLabel = new Label { Left = 10, Top = 8 };
|
var desktop = this.UseUI();
|
||||||
var speedBar = BuildSpeedBar(content);
|
var hudLabel = new Label { Left = 10, Top = 8 };
|
||||||
_pause = new PauseMenu(
|
var speedBar = BuildSpeedBar(content);
|
||||||
Context,
|
_pause = new PauseMenu(
|
||||||
content,
|
Context,
|
||||||
_speed,
|
content,
|
||||||
onSave: SaveWorld,
|
_speed,
|
||||||
onMainMenu: () => Switch(new MainMenuScene()),
|
onSave: SaveWorld,
|
||||||
onQuit: () => Context.Services.Get<Game>().Exit()
|
onMainMenu: () => Switch(new MainMenuScene()),
|
||||||
);
|
onQuit: () => Context.Services.Get<Game>().Exit()
|
||||||
desktop.Root = Ui.Screen(hudLabel, speedBar, _pause.Root);
|
);
|
||||||
|
desktop.Root = Ui.Screen(hudLabel, speedBar, _pause.Root);
|
||||||
this.UseInspector(renderer);
|
|
||||||
var console = this.UseDevConsole();
|
this.UseInspector(renderer);
|
||||||
RegisterCommands(console, content, atlases);
|
var console = this.UseDevConsole();
|
||||||
|
RegisterCommands(console, content, atlases);
|
||||||
UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, climate, dayNight, CellSize));
|
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(new PlantGrowthSystem(plants, calendar, climate, dayNight, CellSize));
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
UpdateSystems.Add(
|
||||||
);
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
UpdateSystems.Add(
|
);
|
||||||
new HudSystem(
|
UpdateSystems.Add(
|
||||||
Context,
|
new HudSystem(
|
||||||
content.Languages,
|
Context,
|
||||||
hudLabel,
|
content.Languages,
|
||||||
"hud.world",
|
hudLabel,
|
||||||
_config.Seed,
|
"hud.world",
|
||||||
calendar,
|
_config.Seed,
|
||||||
climate
|
calendar,
|
||||||
)
|
climate
|
||||||
);
|
)
|
||||||
UpdateSystems.Add(new CallbackSystem(() => Hotkeys(input)));
|
);
|
||||||
|
UpdateSystems.Add(new CallbackSystem(() => Hotkeys(input)));
|
||||||
Log.Info(
|
|
||||||
$"World '{_config.Name}': {_config.Width}x{_config.Height}, seed {_config.Seed}"
|
Log.Info(
|
||||||
+ (_save is null ? " (new)" : " (loaded)")
|
$"World '{_config.Name}': {_config.Width}x{_config.Height}, seed {_config.Seed}"
|
||||||
);
|
+ (_save is null ? " (new)" : " (loaded)")
|
||||||
}
|
);
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Строит одну сущность-<see cref="Tilemap"/>: тайлсет из дефов рельефа (поверхность из
|
/// <summary>
|
||||||
/// атласа либо тонированный тайл) и сетка тайлов по карте высот процедурной генерации, а
|
/// Строит одну сущность-<see cref="Tilemap"/>: тайлсет из дефов рельефа (поверхность из
|
||||||
/// поверх — растительность по скаттеру дефов (с компонентом роста, разной зрелости).
|
/// атласа либо тонированный тайл) и сетка тайлов по карте высот процедурной генерации, а
|
||||||
/// </summary>
|
/// поверх — растительность по скаттеру дефов (с компонентом роста, разной зрелости).
|
||||||
private void BuildTerrain(
|
/// </summary>
|
||||||
GameContent content,
|
private void BuildTerrain(
|
||||||
ModAtlases atlases,
|
GameContent content,
|
||||||
Microsoft.Xna.Framework.Graphics.GraphicsDevice device,
|
ModAtlases atlases,
|
||||||
AssetManager assets,
|
Microsoft.Xna.Framework.Graphics.GraphicsDevice device,
|
||||||
PlantSet plants,
|
AssetManager assets,
|
||||||
Random random
|
PlantSet plants,
|
||||||
)
|
Random random
|
||||||
{
|
)
|
||||||
var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));
|
{
|
||||||
|
var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));
|
||||||
// Тайлсет из дефов: есть поверхность — текстура из атласа, нет — тонированный тайл (вода).
|
|
||||||
var tiles = new TileSet();
|
// Тайлсет из дефов: есть поверхность — текстура из атласа, нет — тонированный тайл (вода).
|
||||||
var tileByDef = new Dictionary<TerrainDef, ushort>();
|
var tiles = new TileSet();
|
||||||
foreach (var terrain in content.Terrains.All)
|
var tileByDef = new Dictionary<TerrainDef, ushort>();
|
||||||
{
|
foreach (var terrain in content.Terrains.All)
|
||||||
tileByDef[terrain] = terrain.Surface is { } surface
|
{
|
||||||
? tiles.Add(atlases.GetRegion(device, surface))
|
tileByDef[terrain] = terrain.Surface is { } surface
|
||||||
: tiles.Add(white, terrain.Tint);
|
? tiles.Add(atlases.GetRegion(device, surface))
|
||||||
}
|
: tiles.Add(white, terrain.Tint);
|
||||||
|
}
|
||||||
// Рельеф детерминирован сидом и масштабом деталей — и для нового мира, и для загрузки.
|
|
||||||
var heights = WorldGenerator.Generate(
|
// Рельеф детерминирован сидом и масштабом деталей — и для нового мира, и для загрузки.
|
||||||
_config.Width,
|
var heights = WorldGenerator.Generate(
|
||||||
_config.Height,
|
_config.Width,
|
||||||
_config.Seed,
|
_config.Height,
|
||||||
_config.SmoothPasses
|
_config.Seed,
|
||||||
);
|
_config.SmoothPasses
|
||||||
var grid = new TileGrid(_config.Width, _config.Height);
|
);
|
||||||
for (var x = 0; x < _config.Width; x++)
|
var grid = new TileGrid(_config.Width, _config.Height);
|
||||||
{
|
for (var x = 0; x < _config.Width; x++)
|
||||||
for (var y = 0; y < _config.Height; y++)
|
{
|
||||||
{
|
for (var y = 0; y < _config.Height; y++)
|
||||||
var terrain = content.Terrains.Classify(heights[x, y]);
|
{
|
||||||
grid[x, y] = tileByDef[terrain];
|
var terrain = content.Terrains.Classify(heights[x, y]);
|
||||||
ScatterSpawner.Spawn(
|
grid[x, y] = tileByDef[terrain];
|
||||||
this,
|
ScatterSpawner.Spawn(
|
||||||
content,
|
this,
|
||||||
plants,
|
content,
|
||||||
terrain,
|
plants,
|
||||||
new Point(x, y),
|
terrain,
|
||||||
CellSize,
|
new Point(x, y),
|
||||||
random
|
CellSize,
|
||||||
);
|
random
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Store.CreateEntity(new Tilemap(grid, tiles, CellSize));
|
|
||||||
}
|
Store.CreateEntity(new Tilemap(grid, tiles, CellSize));
|
||||||
|
}
|
||||||
private HorizontalStackPanel BuildSpeedBar(GameContent content)
|
|
||||||
{
|
private HorizontalStackPanel BuildSpeedBar(GameContent content)
|
||||||
var bar = Ui.Row(6);
|
{
|
||||||
bar.HorizontalAlignment = HorizontalAlignment.Center;
|
var bar = Ui.Row(6);
|
||||||
bar.VerticalAlignment = VerticalAlignment.Bottom;
|
bar.HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
bar.Margin = new Thickness(0, 0, 0, 12);
|
bar.VerticalAlignment = VerticalAlignment.Bottom;
|
||||||
|
bar.Margin = new Thickness(0, 0, 0, 12);
|
||||||
var pause = new TextButton { Text = content.Languages.Get("speed.pause") };
|
|
||||||
pause.Click += (_, _) => _speed.Pause();
|
var pause = new TextButton { Text = content.Languages.Get("speed.pause") };
|
||||||
bar.Widgets.Add(pause);
|
pause.Click += (_, _) => _speed.Pause();
|
||||||
_speedRefreshers.Add(() => pause.TextColor = _speed.IsPaused ? Ui.Accent : Ui.Muted);
|
bar.Widgets.Add(pause);
|
||||||
|
_speedRefreshers.Add(() => pause.TextColor = _speed.IsPaused ? Ui.Accent : Ui.Muted);
|
||||||
for (var i = 0; i < _speed.Steps.Count; i++)
|
|
||||||
{
|
for (var i = 0; i < _speed.Steps.Count; i++)
|
||||||
var index = i;
|
{
|
||||||
var button = new TextButton { Text = $"x{_speed.Steps[i]:0}" };
|
var index = i;
|
||||||
button.Click += (_, _) => _speed.SetStep(index);
|
var button = new TextButton { Text = $"x{_speed.Steps[i]:0}" };
|
||||||
bar.Widgets.Add(button);
|
button.Click += (_, _) => _speed.SetStep(index);
|
||||||
_speedRefreshers.Add(() =>
|
bar.Widgets.Add(button);
|
||||||
button.TextColor =
|
_speedRefreshers.Add(() =>
|
||||||
!_speed.IsPaused && _speed.StepIndex == index ? Ui.Accent : Ui.Muted
|
button.TextColor =
|
||||||
);
|
!_speed.IsPaused && _speed.StepIndex == index ? Ui.Accent : Ui.Muted
|
||||||
}
|
);
|
||||||
|
}
|
||||||
void Refresh()
|
|
||||||
{
|
void Refresh()
|
||||||
foreach (var refresh in _speedRefreshers)
|
{
|
||||||
{
|
foreach (var refresh in _speedRefreshers)
|
||||||
refresh();
|
{
|
||||||
}
|
refresh();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_speed.Changed += Refresh;
|
|
||||||
RegisterUnload(() => _speed.Changed -= Refresh);
|
_speed.Changed += Refresh;
|
||||||
Refresh();
|
RegisterUnload(() => _speed.Changed -= Refresh);
|
||||||
return bar;
|
Refresh();
|
||||||
}
|
return bar;
|
||||||
|
}
|
||||||
private void Hotkeys(InputManager input)
|
|
||||||
{
|
private void Hotkeys(InputManager input)
|
||||||
if (input.IsKeyPressed(Keys.Escape))
|
{
|
||||||
{
|
if (input.IsKeyPressed(Keys.Escape))
|
||||||
_pause.Toggle();
|
{
|
||||||
}
|
_pause.Toggle();
|
||||||
|
}
|
||||||
if (_pause.IsOpen)
|
|
||||||
{
|
if (_pause.IsOpen)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
if (input.IsKeyPressed(Keys.Space))
|
|
||||||
{
|
if (input.IsKeyPressed(Keys.Space))
|
||||||
_speed.TogglePause();
|
{
|
||||||
}
|
_speed.TogglePause();
|
||||||
|
}
|
||||||
if (input.IsKeyPressed(Keys.D1))
|
|
||||||
{
|
if (input.IsKeyPressed(Keys.D1))
|
||||||
_speed.SetStep(0);
|
{
|
||||||
}
|
_speed.SetStep(0);
|
||||||
|
}
|
||||||
if (input.IsKeyPressed(Keys.D2) && _speed.Steps.Count > 1)
|
|
||||||
{
|
if (input.IsKeyPressed(Keys.D2) && _speed.Steps.Count > 1)
|
||||||
_speed.SetStep(1);
|
{
|
||||||
}
|
_speed.SetStep(1);
|
||||||
|
}
|
||||||
if (input.IsKeyPressed(Keys.D3) && _speed.Steps.Count > 2)
|
|
||||||
{
|
if (input.IsKeyPressed(Keys.D3) && _speed.Steps.Count > 2)
|
||||||
_speed.SetStep(2);
|
{
|
||||||
}
|
_speed.SetStep(2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private string SaveWorld()
|
|
||||||
{
|
private string SaveWorld()
|
||||||
// Жителей пока нет — сохраняем только конфиг мира; рельеф воспроизводится из сида.
|
{
|
||||||
var save = new WorldSave
|
// Жителей пока нет — сохраняем только конфиг мира; рельеф воспроизводится из сида.
|
||||||
{
|
var save = new WorldSave
|
||||||
Name = _config.Name,
|
{
|
||||||
Width = _config.Width,
|
Name = _config.Name,
|
||||||
Height = _config.Height,
|
Width = _config.Width,
|
||||||
Seed = _config.Seed,
|
Height = _config.Height,
|
||||||
SmoothPasses = _config.SmoothPasses,
|
Seed = _config.Seed,
|
||||||
Population = _config.Population,
|
SmoothPasses = _config.SmoothPasses,
|
||||||
SavedUtc = DateTime.UtcNow,
|
Population = _config.Population,
|
||||||
ElapsedSeconds = Context.Clock.TotalTime,
|
SavedUtc = DateTime.UtcNow,
|
||||||
};
|
ElapsedSeconds = Context.Clock.TotalTime,
|
||||||
|
};
|
||||||
new SaveStore().Write(save);
|
|
||||||
Log.Info($"World '{_config.Name}' saved");
|
new SaveStore().Write(save);
|
||||||
return _config.Name;
|
Log.Info($"World '{_config.Name}' saved");
|
||||||
}
|
return _config.Name;
|
||||||
|
}
|
||||||
private void Switch(Scene scene)
|
|
||||||
{
|
private void Switch(Scene scene)
|
||||||
if (!Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (!Context.Scenes.IsTransitioning)
|
||||||
_speed.Resume();
|
{
|
||||||
Context.Scenes.Switch(scene, Transition.Fade(0.5f));
|
_speed.Resume();
|
||||||
}
|
Context.Scenes.Switch(scene, Transitions.Fade(0.5f));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private void RegisterCommands(DevConsole console, GameContent content, ModAtlases atlases)
|
|
||||||
{
|
private void RegisterCommands(DevConsole console, GameContent content, ModAtlases atlases)
|
||||||
ContentCommands.Register(console, content, atlases);
|
{
|
||||||
console.Register(
|
ContentCommands.Register(console, content, atlases);
|
||||||
"regen",
|
console.Register(
|
||||||
"regen [seed] — regenerate the world with a new seed",
|
"regen",
|
||||||
(c, args) =>
|
"regen [seed] — regenerate the world with a new seed",
|
||||||
{
|
(c, args) =>
|
||||||
if (Context.Scenes.IsTransitioning)
|
{
|
||||||
{
|
if (Context.Scenes.IsTransitioning)
|
||||||
return;
|
{
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next();
|
|
||||||
c.WriteLine($"regenerating world, seed {seed}");
|
var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next();
|
||||||
Context.Scenes.Switch(
|
c.WriteLine($"regenerating world, seed {seed}");
|
||||||
new WorldScene(_config with { Seed = seed }),
|
Context.Scenes.Switch(
|
||||||
Transition.Fade(0.6f)
|
new WorldScene(_config with { Seed = seed }),
|
||||||
);
|
Transitions.Fade(0.6f)
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
console.Register(
|
);
|
||||||
"menu",
|
console.Register(
|
||||||
"menu — return to the main menu",
|
"menu",
|
||||||
(_, _) => Switch(new MainMenuScene())
|
"menu — return to the main menu",
|
||||||
);
|
(_, _) => Switch(new MainMenuScene())
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user