384 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|