feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,40 @@
using System;
namespace PCL.Core.Minecraft.Saves.Editing;
/// <summary>
/// 可编辑值的标记联合 —— 表示"不修改"或"修改为某值"。
/// 值类型实现,避免在 <see cref="Editing.SaveChanges"/> 中产生堆分配。
/// </summary>
/// <typeparam name="T">值的类型(必须为值类型)。</typeparam>
public readonly record struct Editable<T> where T : struct
{
private readonly T _value;
private readonly bool _hasValue;
/// <summary>创建一个"修改为指定值"的实例。</summary>
public Editable(T value)
{
_value = value;
_hasValue = true;
}
/// <summary>是否显式设置了新值。</summary>
public bool HasValue => _hasValue;
/// <summary>新值。如果 <see cref="HasValue"/> 为 false,则抛出异常。</summary>
public T Value => _hasValue ? _value : throw new InvalidOperationException("Editable 没有值。");
/// <summary>有值则返回新值,否则返回 <paramref name="defaultValue"/>。</summary>
public T GetValueOrDefault(T defaultValue) => _hasValue ? _value : defaultValue;
/// <summary>尝试获取新值。</summary>
public bool TryGetValue(out T value)
{
value = _value;
return _hasValue;
}
/// <summary>返回值的字符串表示,无值时返回 "&lt;unspecified&gt;"。</summary>
public override string ToString() => _hasValue ? _value!.ToString() ?? "" : "<unspecified>";
}
@@ -0,0 +1,19 @@
using fNbt;
namespace PCL.Core.Minecraft.Saves.Editing;
/// <summary>
/// 存档编辑器接口 —— 负责将修改写入 level.dat 的 Data 复合标签(内存操作)。
/// 实现类需声明自己支持的 DataVersion 范围。
/// </summary>
public interface ISaveEditor
{
/// <summary>返回此编辑器能否处理指定 DataVersion 的存档。</summary>
bool CanHandle(int? dataVersion);
/// <summary>
/// 将 <paramref name="changes"/> 中的修改写入 <paramref name="data"/> 复合标签。
/// 返回 true 表示至少有一项修改被成功写入。
/// </summary>
bool ApplyChanges(NbtCompound data, SaveChanges changes);
}
@@ -0,0 +1,53 @@
using fNbt;
namespace PCL.Core.Minecraft.Saves.Editing.Internal;
/// <summary>
/// 26.1 之前的存档编辑器(含整个 1.x 版本体系)。
/// 仅操作内存中的 NbtCompound,文件 IO 由 <see cref="SaveManager"/> 统一处理。
/// </summary>
internal sealed class Pre261SaveEditor : ISaveEditor
{
public bool CanHandle(int? dataVersion)
=> dataVersion is null || dataVersion < DataVersionBoundaries._261snapshot6;
public bool ApplyChanges(NbtCompound data, SaveChanges changes)
{
if (changes.IsEmpty)
return false;
var changed = false;
changed |= WriteAllowCommands(data, changes);
changed |= WriteDifficulty(data, changes);
changed |= WriteDifficultyLocked(data, changes);
return changed;
}
/// <summary>写入 Data.allowCommands(字节型:0/1)。仅当该字段原本存在时才写入,避免向 pre-1.3.1 存档添加新字段。</summary>
internal static bool WriteAllowCommands(NbtCompound data, SaveChanges changes)
{
if (!changes.AllowCommands.HasValue || !data.Contains("allowCommands"))
return false;
data["allowCommands"] = new NbtByte("allowCommands", (byte)(changes.AllowCommands.Value ? 1 : 0));
return true;
}
/// <summary>写入 Data.Difficulty(字节型:0=和平, 1=简单, 2=普通, 3=困难)。仅当该字段原本存在时才写入。</summary>
internal static bool WriteDifficulty(NbtCompound data, SaveChanges changes)
{
if (!changes.Difficulty.HasValue || !data.Contains("Difficulty"))
return false;
data["Difficulty"] = new NbtByte("Difficulty", (byte)changes.Difficulty.Value);
return true;
}
/// <summary>写入 Data.DifficultyLocked(字节型:0/1)。仅当该字段原本存在时才写入。</summary>
internal static bool WriteDifficultyLocked(NbtCompound data, SaveChanges changes)
{
if (!changes.LockDifficulty.HasValue || !data.Contains("DifficultyLocked"))
return false;
data["DifficultyLocked"] = new NbtByte("DifficultyLocked", (byte)(changes.LockDifficulty.Value ? 1 : 0));
return true;
}
}
@@ -0,0 +1,59 @@
using fNbt;
namespace PCL.Core.Minecraft.Saves.Editing.Internal;
/// <summary>
/// 26.1-snapshot-6 及之后的存档编辑器(2026 新版本号体系)。
/// 仅操作内存中的 NbtCompound,文件 IO 由 <see cref="SaveManager"/> 统一处理。
/// </summary>
internal sealed class Version261PlusSaveEditor : ISaveEditor
{
public bool CanHandle(int? dataVersion)
=> dataVersion >= DataVersionBoundaries._261snapshot6;
public bool ApplyChanges(NbtCompound data, SaveChanges changes)
{
if (changes.IsEmpty)
return false;
// 确保 difficulty_settings 复合标签存在
if (!data.TryGet<NbtCompound>("difficulty_settings", out var ds) || ds is null)
{
ds = new NbtCompound("difficulty_settings");
data.Add(ds);
}
var changed = false;
changed |= Pre261SaveEditor.WriteAllowCommands(data, changes);
changed |= WriteDifficulty(ds!, changes);
changed |= WriteLocked(ds!, changes);
return changed;
}
/// <summary>写入 difficulty_settings.difficulty(字符串型)。</summary>
internal static bool WriteDifficulty(NbtCompound difficultySettings, SaveChanges changes)
{
if (!changes.Difficulty.HasValue)
return false;
var val = changes.Difficulty.Value switch
{
Difficulty.Peaceful => "peaceful",
Difficulty.Easy => "easy",
Difficulty.Normal => "normal",
Difficulty.Hard => "hard",
_ => "normal",
};
difficultySettings["difficulty"] = new NbtString("difficulty", val);
return true;
}
/// <summary>写入 difficulty_settings.locked(字节型:0/1)。</summary>
internal static bool WriteLocked(NbtCompound difficultySettings, SaveChanges changes)
{
if (!changes.LockDifficulty.HasValue)
return false;
difficultySettings["locked"] = new NbtByte("locked", (byte)(changes.LockDifficulty.Value ? 1 : 0));
return true;
}
}
@@ -0,0 +1,20 @@
namespace PCL.Core.Minecraft.Saves.Editing;
/// <summary>
/// 用户希望对存档应用的修改。使用 <c>default</c> 表示"无任何修改"。
/// 仅当 <see cref="Editable{T}.HasValue"/> 为 true 的字段才会被写入。
/// </summary>
public record struct SaveChanges
{
/// <summary>是否允许作弊命令的修改。</summary>
public Editable<bool> AllowCommands { get; set; }
/// <summary>游戏难度的修改。</summary>
public Editable<Difficulty> Difficulty { get; set; }
/// <summary>是否锁定难度的修改。</summary>
public Editable<bool> LockDifficulty { get; set; }
/// <summary>此结构体是否不包含任何待写入的修改。</summary>
public bool IsEmpty => !AllowCommands.HasValue && !Difficulty.HasValue && !LockDifficulty.HasValue;
}