Files
CapySR/gui/BuildFile.cs
T
2026-10-17 20:42:15 -07:00

384 lines
14 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
namespace CapyLauncher
{
public class SubStat
{
public int Id;
public int Count = 1;
public int Step;
}
public class BuildFile
{
static readonly int[] PromotionCaps = { 20, 30, 40, 50, 60, 70 };
readonly JavaScriptSerializer serializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue, RecursionLimit = 256 };
Dictionary<string, object> root = new Dictionary<string, object>();
public string Path { get; }
public DateTime LoadedStamp { get; private set; }
public bool Dirty { get; set; }
public BuildFile(string path)
{
Path = path;
}
public void Load()
{
root = File.Exists(Path)
? serializer.DeserializeObject(File.ReadAllText(Path)) as Dictionary<string, object> ?? new Dictionary<string, object>()
: new Dictionary<string, object>();
if (root.TryGetValue("data", out object wrapped) && wrapped is Dictionary<string, object> inner && inner.ContainsKey("avatars"))
root = inner;
Dict(root, "avatars");
List(root, "relics");
List(root, "lightcones");
LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue;
Dirty = false;
}
// ---- json tree helpers ----
public static Dictionary<string, object> Dict(Dictionary<string, object> parent, string key)
{
if (parent.TryGetValue(key, out object value) && value is Dictionary<string, object> dict)
return dict;
var fresh = new Dictionary<string, object>();
parent[key] = fresh;
return fresh;
}
public static List<object> List(Dictionary<string, object> parent, string key)
{
if (parent.TryGetValue(key, out object value) && value is List<object> list)
return list;
var fresh = value is IEnumerable items && !(value is string) ? items.Cast<object>().ToList() : new List<object>();
parent[key] = fresh;
return fresh;
}
public static int Int(Dictionary<string, object> d, string key, int fallback = 0) =>
d != null && d.TryGetValue(key, out object v) && v != null && !(v is string) && !(v is IEnumerable) ? Convert.ToInt32(v, CultureInfo.InvariantCulture) : fallback;
public static string Str(Dictionary<string, object> d, string key, string fallback = "") =>
d != null && d.TryGetValue(key, out object v) && v is string s ? s : fallback;
public static int PromotionFor(int level) => PromotionCaps.Count(cap => level > cap);
int NextUid(List<object> items) =>
items.OfType<Dictionary<string, object>>().Select(i => Int(i, "internal_uid")).DefaultIfEmpty(0).Max() + 1;
// ---- avatars ----
public Dictionary<string, object> Avatars => Dict(root, "avatars");
public Dictionary<string, object> Avatar(int id) =>
Avatars.TryGetValue(id.ToString(), out object value) ? value as Dictionary<string, object> : null;
public Dictionary<string, object> AddAvatar(AvatarInfo info)
{
var avatar = new Dictionary<string, object>
{
["avatar_id"] = info.Id,
["data"] = new Dictionary<string, object>
{
["rank"] = 0,
["skills"] = new Dictionary<string, object>(),
["skills_by_anchor_type"] = new Dictionary<string, object>(),
},
["level"] = 80,
["promotion"] = 6,
["sp_max"] = info.Sp,
["sp_value"] = info.Sp / 2,
["techniques"] = new List<object> { info.Id * 100 + 1 },
["enhanced_id"] = 0,
};
Avatars[info.Id.ToString()] = avatar;
foreach (Anchor anchor in info.Anchors)
SetAnchor(avatar, anchor, anchor.Max);
Dirty = true;
return avatar;
}
public void RemoveAvatar(int id)
{
Avatars.Remove(id.ToString());
foreach (var item in Relics.Concat(Lightcones).OfType<Dictionary<string, object>>())
{
if (Int(item, "equip_avatar") == id)
item["equip_avatar"] = 0;
}
Dirty = true;
}
public static int Rank(Dictionary<string, object> avatar) => Int(Dict(avatar, "data"), "rank");
public void SetRank(Dictionary<string, object> avatar, int rank)
{
Dict(avatar, "data")["rank"] = rank;
Dirty = true;
}
public void SetLevel(Dictionary<string, object> avatar, int level)
{
avatar["level"] = level;
avatar["promotion"] = PromotionFor(level);
Dirty = true;
}
public static int AnchorLevel(Dictionary<string, object> avatar, Anchor anchor) =>
Int(Dict(Dict(avatar, "data"), "skills_by_anchor_type"), anchor.Number.ToString());
public void SetAnchor(Dictionary<string, object> avatar, Anchor anchor, int level)
{
var data = Dict(avatar, "data");
Dict(data, "skills_by_anchor_type")[anchor.Number.ToString()] = level;
var skills = Dict(data, "skills");
foreach (int point in anchor.Points)
skills[point.ToString()] = level;
Dirty = true;
}
public void MaxAvatar(Dictionary<string, object> avatar, AvatarInfo info, bool eidolons)
{
SetLevel(avatar, 80);
if (eidolons)
SetRank(avatar, info.MaxRank);
foreach (Anchor anchor in info.Anchors)
SetAnchor(avatar, anchor, anchor.Max);
}
// ---- light cones ----
public List<object> Lightcones => List(root, "lightcones");
public Dictionary<string, object> LightconeOf(int avatarId) =>
Lightcones.OfType<Dictionary<string, object>>().FirstOrDefault(l => Int(l, "equip_avatar") == avatarId);
public Dictionary<string, object> EquipLightcone(int avatarId, int itemId)
{
var cone = LightconeOf(avatarId);
if (cone == null)
{
cone = new Dictionary<string, object>
{
["equip_avatar"] = avatarId,
["internal_uid"] = NextUid(Lightcones),
["item_id"] = itemId,
["level"] = 80,
["promotion"] = 6,
["rank"] = 1,
};
Lightcones.Add(cone);
}
cone["item_id"] = itemId;
Dirty = true;
return cone;
}
public void UnequipLightcone(int avatarId)
{
var cone = LightconeOf(avatarId);
if (cone != null)
Lightcones.Remove(cone);
Dirty = true;
}
public void SetLightconeLevel(Dictionary<string, object> cone, int level)
{
cone["level"] = level;
cone["promotion"] = PromotionFor(level);
Dirty = true;
}
// ---- relics ----
public List<object> Relics => List(root, "relics");
public static int Slot(Dictionary<string, object> relic) => Int(relic, "relic_id") % 10;
public Dictionary<string, object> RelicOf(int avatarId, int slot) =>
Relics.OfType<Dictionary<string, object>>().FirstOrDefault(r => Int(r, "equip_avatar") == avatarId && Slot(r) == slot);
public Dictionary<string, object> PutRelic(int avatarId, int slot, RelicSetInfo set, int mainAffix)
{
var relic = RelicOf(avatarId, slot);
if (relic == null)
{
relic = new Dictionary<string, object>
{
["equip_avatar"] = avatarId,
["internal_uid"] = NextUid(Relics),
["level"] = 15,
["sub_affixes"] = new List<object>(),
};
Relics.Add(relic);
}
relic["relic_id"] = set.Pieces[slot];
relic["relic_set_id"] = set.Id;
relic["main_affix_id"] = mainAffix;
Dirty = true;
return relic;
}
public void RemoveRelic(int avatarId, int slot)
{
var relic = RelicOf(avatarId, slot);
if (relic != null)
Relics.Remove(relic);
Dirty = true;
}
public static List<SubStat> Subs(Dictionary<string, object> relic) =>
List(relic, "sub_affixes").OfType<Dictionary<string, object>>()
.Select(s => new SubStat { Id = Int(s, "sub_affix_id"), Count = Int(s, "count", 1), Step = Int(s, "step") }).ToList();
public void SetSubs(Dictionary<string, object> relic, IEnumerable<SubStat> subs)
{
relic["sub_affixes"] = subs.Where(s => s.Id != 0).Select(s => (object)new Dictionary<string, object>
{
["sub_affix_id"] = s.Id,
["count"] = s.Count,
["step"] = s.Step,
}).ToList();
Dirty = true;
}
public void Touch() => Dirty = true;
public void Restamp() => LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue;
// ---- battle ----
public Dictionary<string, object> Battle => Dict(root, "battle_config");
// ---- saving ----
public string ToJson()
{
var text = new StringBuilder();
Write(text, root, 0);
text.Append('\n');
return text.ToString();
}
static void Write(StringBuilder text, object value, int depth)
{
string pad = new string(' ', (depth + 1) * 2), close = new string(' ', depth * 2);
switch (value)
{
case null:
text.Append("null");
break;
case string s:
text.Append(new JavaScriptSerializer().Serialize(s));
break;
case bool b:
text.Append(b ? "true" : "false");
break;
case Dictionary<string, object> dict:
if (dict.Count == 0) { text.Append("{}"); break; }
text.Append("{\n");
int i = 0;
foreach (var pair in dict)
{
text.Append(pad).Append(new JavaScriptSerializer().Serialize(pair.Key)).Append(": ");
Write(text, pair.Value, depth + 1);
text.Append(++i < dict.Count ? ",\n" : "\n");
}
text.Append(close).Append('}');
break;
case IEnumerable list:
var items = list.Cast<object>().ToList();
if (items.Count == 0) { text.Append("[]"); break; }
bool flat = items.All(x => !(x is Dictionary<string, object>) && (x is string || !(x is IEnumerable)));
if (flat)
{
text.Append('[');
for (int j = 0; j < items.Count; j++)
{
if (j > 0) text.Append(", ");
Write(text, items[j], depth + 1);
}
text.Append(']');
break;
}
text.Append("[\n");
for (int j = 0; j < items.Count; j++)
{
text.Append(pad);
Write(text, items[j], depth + 1);
text.Append(j + 1 < items.Count ? ",\n" : "\n");
}
text.Append(close).Append(']');
break;
case double d:
text.Append(d.ToString("R", CultureInfo.InvariantCulture));
break;
case float f:
text.Append(f.ToString("R", CultureInfo.InvariantCulture));
break;
case IFormattable number:
text.Append(number.ToString(null, CultureInfo.InvariantCulture));
break;
default:
text.Append(new JavaScriptSerializer().Serialize(value));
break;
}
}
public void WriteToDisk()
{
string temp = Path + ".tmp";
File.WriteAllText(temp, ToJson(), new UTF8Encoding(false));
if (File.Exists(Path))
{
string backup = Path + ".bak";
File.Replace(temp, Path, backup, true);
}
else
{
File.Move(temp, Path);
}
LoadedStamp = File.GetLastWriteTimeUtc(Path);
Dirty = false;
}
public async Task<string> SendToServer(string dispatch)
{
string body = ToJson();
var request = (HttpWebRequest)WebRequest.Create(dispatch.TrimEnd('/') + "/srtools");
request.Method = "POST";
request.ContentType = "application/json";
request.Timeout = 10000;
byte[] bytes = Encoding.UTF8.GetBytes(body);
using (Stream stream = await request.GetRequestStreamAsync())
await stream.WriteAsync(bytes, 0, bytes.Length);
using (var response = (HttpWebResponse)await request.GetResponseAsync())
using (var reader = new StreamReader(response.GetResponseStream()))
{
var reply = serializer.DeserializeObject(await reader.ReadToEndAsync()) as Dictionary<string, object>;
string message = Str(reply, "message", "no reply");
if (message == "OK")
{
await Task.Delay(150);
LoadedStamp = File.Exists(Path) ? File.GetLastWriteTimeUtc(Path) : DateTime.MinValue;
Dirty = false;
}
return message;
}
}
}
}