map ui, skill ui, update skills data
This commit is contained in:
1 parent
86b3bf97a7
commit
d82733e3f7
10 files changed
+2061
No files matched your search
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Cthangover.Core.Mods;
|
||||
using Cthangover.Core.Mods.Providers;
|
||||
using Cthangover.Core.UI.Tool;
|
||||
using Cthangover.Core.Utils;
|
||||
|
||||
namespace Cthangover.Tools.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Static service for discovering, reading and writing world map zone files
|
||||
/// (<c>mods/<mod>/map/*.json</c>) from the map editor. Follows
|
||||
/// <see cref="ScenarioFileService"/>: reading goes through the mod file
|
||||
/// provider abstraction so zip mods are visible, writing is deliberately
|
||||
/// limited to folder-backed mods because zip archives are read-only.
|
||||
/// <para/>
|
||||
/// New files are created through the provider's <c>RootPath</c> rather than
|
||||
/// <c>GetFileSystemPath</c>, which reports <c>null</c> for files that do not
|
||||
/// exist yet — the editor must be able to author a brand-new map file, not
|
||||
/// only overwrite an existing one.
|
||||
/// </summary>
|
||||
public static class MapFileService
|
||||
{
|
||||
private const string MapDir = "map";
|
||||
private const string JsonExtension = ".json";
|
||||
|
||||
/// <summary>
|
||||
/// IDs of every loaded mod that can be written to (folder-backed).
|
||||
/// Zip mods are skipped silently: they appear in the game but the editor
|
||||
/// cannot save into them.
|
||||
/// </summary>
|
||||
public static List<string> ListWritableMods()
|
||||
{
|
||||
var result = new List<string>();
|
||||
var manager = ModManager.Instance;
|
||||
if (manager?.Mods == null)
|
||||
return result;
|
||||
|
||||
foreach (var pair in manager.Mods)
|
||||
{
|
||||
if (pair.Value?.FileProvider is FolderModFileProvider)
|
||||
result.Add(pair.Key);
|
||||
}
|
||||
|
||||
result.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mod-relative paths of all <c>.json</c> files under the mod's
|
||||
/// <c>map/</c> directory, including nested subdirectories. Returns an
|
||||
/// empty list for unknown mods.
|
||||
/// </summary>
|
||||
public static List<string> ListMapFiles(string modId)
|
||||
{
|
||||
var result = new List<string>();
|
||||
if (string.IsNullOrEmpty(modId))
|
||||
return result;
|
||||
|
||||
CollectJsonFiles(modId, MapDir, result);
|
||||
result.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Reads a map file through the mod provider; <c>null</c> when missing.</summary>
|
||||
public static string ReadFile(string modId, string relativePath)
|
||||
{
|
||||
return ModManager.Instance.ReadFileText(modId, relativePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a map file into a folder-backed mod, creating the
|
||||
/// <c>map/</c> directory and any missing parents. Returns <c>false</c>
|
||||
/// for zip mods, unknown mods and empty paths.
|
||||
/// </summary>
|
||||
public static bool WriteFile(string modId, string relativePath, string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(modId) || string.IsNullOrEmpty(relativePath))
|
||||
return false;
|
||||
|
||||
var mod = ModManager.Instance.GetMod(modId);
|
||||
if (mod?.FileProvider is not FolderModFileProvider provider)
|
||||
return false;
|
||||
|
||||
var root = provider.RootPath;
|
||||
if (string.IsNullOrEmpty(root))
|
||||
return false;
|
||||
|
||||
var absolute = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\', '/')));
|
||||
var directory = Path.GetDirectoryName(absolute);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
File.WriteAllText(absolute, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the standard <c>{"Items": [...]}</c> envelope into zones.
|
||||
/// Throws <see cref="JsonException"/> on malformed JSON so the editor
|
||||
/// can show the error instead of silently opening an empty map.
|
||||
/// </summary>
|
||||
public static List<MapZone> ParseZones(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return new List<MapZone>();
|
||||
|
||||
var data = JsonSerializer.Deserialize<FileData<MapZone>>(json, JsonOptionsProvider.ReadOptions);
|
||||
return data?.Items ?? new List<MapZone>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialises zones into the standard envelope with indentation and
|
||||
/// without null fields, so hand-editing the result stays pleasant and
|
||||
/// optional properties such as <c>label_pos</c> only appear when used.
|
||||
/// </summary>
|
||||
public static string BuildJson(IEnumerable<MapZone> zones)
|
||||
{
|
||||
var data = new FileData<MapZone> { Items = zones?.ToList() ?? new List<MapZone>() };
|
||||
return JsonSerializer.Serialize(data, JsonOptionsProvider.WriteIndentedIgnoreNullOptions);
|
||||
}
|
||||
|
||||
private static void CollectJsonFiles(string modId, string directory, List<string> result)
|
||||
{
|
||||
foreach (var entry in ModManager.Instance.ListFiles(modId, directory))
|
||||
{
|
||||
if (entry.EndsWith("/"))
|
||||
{
|
||||
CollectJsonFiles(modId, entry.TrimEnd('/'), result);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.EndsWith(JsonExtension, StringComparison.OrdinalIgnoreCase))
|
||||
result.Add(entry.Replace('\\', '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Cthangover.Core.Factories.Impls;
|
||||
@@ -53,6 +54,27 @@ namespace Cthangover.Tools.Services
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns every texture logical name (filename without extension) from
|
||||
/// all configured texture groups, sorted for stable UI lists. Used by
|
||||
/// the map editor's marker icon picker: these are exactly the IDs that
|
||||
/// <c>MapZone.Image</c> accepts, because resolution also keys textures
|
||||
/// by basename.
|
||||
/// </summary>
|
||||
public static List<string> GetTextureIds()
|
||||
{
|
||||
var ids = new List<string>();
|
||||
var textures = Textures.Collect();
|
||||
if (textures == null)
|
||||
return ids;
|
||||
|
||||
foreach (var kvp in textures)
|
||||
ids.Add(kvp.Key);
|
||||
|
||||
ids.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
return ids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a background texture by ID via <see cref="BackgroundFactory.Instance.Get"/>.
|
||||
|
||||
Reference in new issue
Block a user