+EA 23.312 Nightly Patch 4 - Plugin.BaseCore โ
June 4, 2026
12 files modified. 1 new file created.
Important Changes โ
Possible breaking changes. Click the filename to view the chunk.
ClassCache (1) โ
public T Create<T2>(Type type)
public Func<T> CreateDelegate(Type type) Lang (1) โ
public static HashSet<string> extraExcelDialogs = new HashSet<string>();
public static List<Func<List<string>>> excelDialogLoaders = new List<Func<List<string>>>(); BaseModManager โ
public void Parse<T>(ModItemList<T> list) where T : UnityEngine.Object
private static readonly Dictionary<string, HashSet<Action<object>>> _eventHandlers = new Dictionary<string, HashSet<Action<object>>>();
private static readonly Dictionary<(string, Delegate), Action<object>> _typedEventHandlers = new Dictionary<(string, Delegate), Action<object>>();
public DirectoryInfo dirWorkshop;
public int priorityIndex;public virtual void Init(string path, string defaultPackage = "_Elona")
Debug.Log("Initializing ModManager:" + defaultPackage + "/" + path);
Instance = this;
rootMod = (rootDefaultPacakge = path);
_eventHandlers.Clear();
_typedEventHandlers.Clear();
if (!defaultPackage.IsEmpty())
{
rootDefaultPacakge = rootMod + defaultPackage + "/";public virtual void Init(string path, string defaultPackage = "_Elona")
public void InitLang()
{
Debug.Log("Initializing Langs: " + Lang.langCode);
foreach (LangSetting value in MOD.langs.Values)
MOD.listTalk.Clear();
MOD.listGodTalk.Clear();
MOD.tones.Clear();
LangSetting langSetting = MOD.langs.TryGetValue(Lang.langCode);
if (langSetting == null)
{
if (value.id != Lang.langCode)
{
continue;
}
new DirectoryInfo(value.dir);
FileInfo[] files = new DirectoryInfo(value.dir + "Data").GetFiles();
foreach (FileInfo fileInfo in files)
Debug.LogError("Lang: " + Lang.langCode + " is not set");
langSetting = MOD.langs.FirstItem();
}
FileInfo[] files = new DirectoryInfo(langSetting.dir + "Data").GetFiles();
foreach (FileInfo fileInfo in files)
{
switch (fileInfo.Name)
{
if (fileInfo.Name == "Alias.xlsx")
{
Lang.alias = new ExcelData(fileInfo.FullName);
}
if (fileInfo.Name == "Name.xlsx")
{
Lang.names = new ExcelData(fileInfo.FullName);
}
if (fileInfo.Name == "chara_talk.xlsx")
{
MOD.listTalk.items.Add(new ExcelData(fileInfo.FullName));
}
if (fileInfo.Name == "chara_tone.xlsx")
{
MOD.tones.items.Add(new ExcelData(fileInfo.FullName));
}
case "Alias.xlsx":
Lang.alias = new ExcelData(fileInfo.FullName);
break;
case "Name.xlsx":
Lang.names = new ExcelData(fileInfo.FullName);
break;
case "chara_talk.xlsx":
MOD.listTalk.Add(new ExcelData(fileInfo.FullName));
break;
case "god_talk.xlsx":
MOD.listGodTalk.Add(new ExcelData(fileInfo.FullName));
break;
case "chara_tone.xlsx":
MOD.tones.Add(new ExcelData(fileInfo.FullName));
break;
}
}
}public virtual void ParseExtra(DirectoryInfo dir, BaseModPackage package)
public static void SubscribeEvent(string eventId, Action<object> handler)
{
if (!_eventHandlers.TryGetValue(eventId, out var value))
if (handler != null)
{
if (!_eventHandlers.TryGetValue(eventId, out var value))
{
value = new HashSet<Action<object>>();
_eventHandlers[eventId] = value;
}
value.Add(handler);
}
}
public static void SubscribeEvent(string eventId, Action handler)
{
if (handler != null)
{
Action<object> action = delegate
{
handler();
};
(string, Delegate) key = (eventId, handler);
_typedEventHandlers[key] = action;
SubscribeEvent(eventId, action);
}
}
public static void SubscribeEvent<T>(string eventId, Action<T> handler)
{
if (handler == null)
{
value = new HashSet<Action<object>>();
_eventHandlers[eventId] = value;
return;
}
value.Add(handler);
Action<object> action = delegate(object data)
{
if (!(data is T obj))
{
if (data != null)
{
throw new InvalidCastException($"{handler.Method.DeclaringType?.Assembly.GetName()} " + "expects " + typeof(T).Name + ", got " + data.GetType().Name);
}
handler(default(T));
}
else
{
handler(obj);
}
};
(string, Delegate) key = (eventId, handler);
_typedEventHandlers[key] = action;
SubscribeEvent(eventId, action);
}
public static void UnsubscribeEvent(string eventId, Action<object> handler)public static void UnsubscribeEvent(string eventId, Action<object> handler)
}
}
public static void UnsubscribeEvent(string eventId, Action handler)
{
if (handler != null)
{
(string, Delegate) key = (eventId, handler);
if (_typedEventHandlers.TryGetValue(key, out var value))
{
UnsubscribeEvent(eventId, value);
_typedEventHandlers.Remove(key);
}
}
}
public static void UnsubscribeEvent<T>(string eventId, Action<T> handler)
{
if (handler != null)
{
(string, Delegate) key = (eventId, handler);
if (_typedEventHandlers.TryGetValue(key, out var value))
{
UnsubscribeEvent(eventId, value);
_typedEventHandlers.Remove(key);
}
}
}
public static void PublishEvent(string eventId, object data = null)
{
if (!_eventHandlers.TryGetValue(eventId, out var value))public static void PublishEvent(string eventId, object data = null)
}
}
public static void PublishEvent<T>(string eventId, T data = default(T))
{
PublishEvent(eventId, (object)data);
}
public static bool HasEventSubscriber(string eventId)
{
int? num = _eventHandlers.GetValueOrDefault(eventId)?.Count;BaseModPackage โ
{
if (directoryInfo.Name == "Actor")
{
FileInfo[] files = directoryInfo.GetFiles();
FileInfo[] files = directoryInfo.GetFiles("*.xlsx", SearchOption.TopDirectoryOnly);
foreach (FileInfo fileInfo in files)
{
if (fileInfo.Name.EndsWith(".xlsx"))
{
MOD.actorSources.items.Add(new ExcelData(fileInfo.FullName));
}
MOD.actorSources.Add(new ExcelData(fileInfo.FullName));
}
DirectoryInfo[] directories2 = directoryInfo.GetDirectories();
foreach (DirectoryInfo directoryInfo2 in directories2)ClassCache โ
{
public Dictionary<string, Func<T>> dict = new Dictionary<string, Func<T>>();
public T Create<T2>(Type type)
public Func<T> CreateDelegate(Type type)
{
Func<T> result = () => default(T);
if (type == null)
{
return default(T);
}
Func<T> func = dict.TryGetValue(type.Name);
if (func != null)
{
return func();
return result;
}
ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor == null)
if (constructor != null)
{
return default(T);
result = Expression.Lambda<Func<T>>(Expression.New(constructor), Array.Empty<ParameterExpression>()).Compile();
}
func = Expression.Lambda<Func<T>>(Expression.New(constructor), Array.Empty<ParameterExpression>()).Compile();
dict.Add(type.Name, func);
return func();
return result;
}
public T Create<T2>(string id, string assembly)public T Create<T2>(string id, string assembly)
Type type = Type.GetType(id + ", " + assembly);
if (type == null)
{
foreach (string assembly2 in ClassCache.assemblies)
for (int num = ClassCache.typeLoaders.Count - 1; num >= 0; num--)
{
type = Type.GetType(id + ", " + assembly2);
type = ClassCache.typeLoaders[num](id);
if (type != null)
{
break;
}
}
}
return Create<T2>(type);
func = CreateDelegate(type);
dict[id] = func;
return func();
}
}
public class ClassCache
public static HashSet<string> assemblies = new HashSet<string>();
public static List<Func<string, Type>> typeLoaders = new List<Func<string, Type>> { LoadTypeFromGlobalNamespace };
public static T Create<T>(string id, string assembly = "Assembly-CSharp")
{
return (T)caches.Create<T>(id, assembly);
}
public static Type LoadTypeFromGlobalNamespace(string id)
{
foreach (string assembly in assemblies)
{
Type type = Type.GetType(id + ", " + assembly);
if (type != null)
{
return type;
}
}
return null;
}
}ERROR โ
public class ERROR
{
public static string msg;
public static int lastImported;
}ExcelDataList โ
public virtual void OnInitialize()
{
}
public bool HasRow(string id)
{
Initialize();
return all.ContainsKey(id);
}
public Dictionary<string, string> GetRow(string id)
{
if (!initialized)
{
Initialize();
}
return all.TryGetValue(id) ?? list[0];
Initialize();
return all.TryGetValue(id);
}
public void Add(ExcelData data)
{
items.Add(data);
Reload();
}
public void Clear()
{
items.Clear();
Reload();
}
}ExcelParser โ
public static string[] GetStringArray(int id)
string str = GetStr(id);
if (str != null)
{
return str.Split(',');
return str.Split(',').ToArray();
}
return Array.Empty<string>();
}+GodTalkDataList โ
File Created
using System.Collections.Generic;
public class GodTalkDataList : ExcelDataList
{
public override int StartIndex => 3;
public override void Initialize()
{
if (initialized)
{
return;
}
foreach (ExcelData item in items)
{
item.startIndex = StartIndex;
item.BuildMap();
foreach (KeyValuePair<string, Dictionary<string, string>> item2 in item.sheets["_default"].map)
{
item2.Deconstruct(out var key, out var value);
string key2 = key;
Dictionary<string, string> dictionary = value;
if (all.TryAdd(key2, dictionary))
{
list.Add(dictionary);
continue;
}
foreach (KeyValuePair<string, string> item3 in dictionary)
{
item3.Deconstruct(out key, out var value2);
string key3 = key;
string value3 = value2;
all[key2][key3] = value3;
}
}
}
initialized = true;
}
public string GetTalk(string id, string idTopic)
{
Dictionary<string, string> row = GetRow(idTopic);
return ((row == null) ? null : row.TryGetValue(id)?.SplitByNewline().RandomItem()) ?? "";
}
}IO โ
public static T LoadFile<T>(string path, bool compress = false, JsonSerializerSe
{
if (!File.Exists(path))
{
Debug.Log("File does not exist:" + path);
Debug.Log("#io File does not exist:" + path);
return default(T);
}
string value = (IsCompressed(path) ? Decompress(path) : File.ReadAllText(path));Lang โ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class Lang
public static ExcelData excelDialog;
public static HashSet<string> extraExcelDialogs = new HashSet<string>();
public static List<Func<List<string>>> excelDialogLoaders = new List<Func<List<string>>>();
public static bool IsBuiltin(string id)
{public static ExcelData.Sheet GetDialogSheet(string idSheet)
{
list.Add(new ExcelData(CorePath.CorePackage.TextDialogLocal + "dialog.xlsx"));
}
foreach (string extraExcelDialog in extraExcelDialogs)
foreach (Func<List<string>> excelDialogLoader in excelDialogLoaders)
{
list.Add(new ExcelData(extraExcelDialog));
list.AddRange(from f in excelDialogLoader()
select new ExcelData(f));
}
for (int i = 0; i < excelDialog.book.NumberOfSheets; i++)
{MOD โ
public static TalkDataList listTalk = new TalkDataList();
public static GodTalkDataList listGodTalk = new GodTalkDataList();
public static ToneDataList tones = new ToneDataList();
public static ExcelDataList actorSources = new ExcelDataList(); public static List<FileInfo> listPartialMaps = new List<FileInfo>();
public static Dictionary<string, FileInfo> sounds = new Dictionary<string, FileInfo>();
public static void ResetResources()
{
langs.Clear();
listTalk.Clear();
listGodTalk.Clear();
tones.Clear();
actorSources.Clear();
sprites = new ModItemList<Sprite>();
listMaps.Clear();
listPartialMaps.Clear();
sounds.Clear();
}
}SourceData โ
public override bool ImportData(ISheet sheet, string bookname, bool overwrite =
nameBook = bookname;
SourceData.rowHeader = sheet.GetRow(0);
SourceData.rowDefault = sheet.GetRow(2);
IRow row = sheet.GetRow(3);
if (SourceData.rowHeader == null || row == null)
{
ERROR.lastImported = 0;
return true;
}
IReadOnlyDictionary<string, int> rowMapping = GetRowMapping();
Dictionary<string, int> dictionary = null;
Dictionary<string, int> header = null;
if (rowMapping != null)
{
dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (ICell item in SourceData.rowHeader.Cells.Where((ICell c) => c.ToString() != "0" && !c.ToString().IsEmpty()))
header = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (ICell cell in SourceData.rowHeader.Cells)
{
dictionary[item.ToString().Trim()] = item.ColumnIndex;
string text = cell.ToString().Trim();
if (!(text == "0") && !text.IsEmpty())
{
header[text] = cell.ColumnIndex;
}
}
if (!rowMapping.Keys.All(dictionary.ContainsKey) || rowMapping.Count > dictionary.Count)
if (!rowMapping.All((KeyValuePair<string, int> kv) => header.TryGetValue(kv.Key, 0) == kv.Value))
{
string[] array = rowMapping.Keys.Except(dictionary.Keys).ToArray();
string[] array = rowMapping.Keys.Except(header.Keys).ToArray();
if (array.Length != 0)
{
Debug.LogWarning("#source ill-format file with missing columns, init with empty values\n" + ExcelParser.GetRowHeaderDiff(rowMapping, dictionary));
Debug.LogWarning("#source ill-format file with missing columns, init with empty values\n" + ExcelParser.GetRowHeaderDiff(rowMapping, header));
string[] array2 = array;
foreach (string text in array2)
foreach (string text2 in array2)
{
SourceData.rowHeader.CreateCell(276, CellType.String).SetCellValue(text);
dictionary[text] = 276;
SourceData.rowHeader.CreateCell(276, CellType.String).SetCellValue(text2);
header[text2] = 276;
}
}
elsepublic override bool ImportData(ISheet sheet, string bookname, bool overwrite =
}
else
{
dictionary = null;
header = null;
}
}
int num = 0;
for (int j = 3; j <= sheet.LastRowNum; j++)
{
SourceData.row = sheet.GetRow(j);
if (string.IsNullOrEmpty(SourceData.row?.GetCell(0)?.ToString()))
try
{
break;
if (string.IsNullOrEmpty(SourceData.row?.GetCell(0)?.ToString()))
{
break;
}
T val = ((header != null) ? CreateRowByMapping(header) : CreateRow());
val.OnImportData(this);
rows.Add(val);
num++;
continue;
}
catch (Exception arg)
{
Debug.LogError($"#source failed to create row#{j + 1}\n{arg}");
continue;
}
T val = ((dictionary != null) ? CreateRowByMapping(dictionary) : CreateRow());
val.OnImportData(this);
rows.Add(val);
num++;
}
string text2 = sheet.SheetName + "/" + sheet.LastRowNum + "/" + num;
Debug.Log(text2);
ERROR.msg = text2;
Debug.Log(sheet.SheetName + "/" + sheet.LastRowNum + "/" + num);
ERROR.lastImported = num;
OnAfterImportData();
initialized = false;
return true;public virtual T CreateRowByMapping(IReadOnlyDictionary<string, int> mapping)
return null;
}
public override BaseRow[] ExportRows()
{
return rows.OfType<BaseRow>().ToArray();
}
public override int ImportRows(IEnumerable<BaseRow> sourceRows)
{
int num = 0;public virtual void RemoveDuplicateRows()
}
if (field.FieldType != typeof(T2))
{
Debug.LogError($"#source override: {arg} id field mismatch {field.FieldType} != {typeof(T2)}");
Debug.LogError($"#source-override: {arg} id field mismatch {field.FieldType} != {typeof(T2)}");
}
for (int num = rows.Count - 1; num >= 0; num--)
{public virtual void RemoveDuplicateRows()
}
else if (!flag)
{
Debug.Log($"#source override: {arg} {val2}");
Debug.Log($"#source-override: {arg} {val2}");
}
}
if (rows.Count != list.Count)
{
Debug.Log($"#source override: {arg} total/{rows.Count} -> unique/{list.Count}");
Debug.Log($"#source-override: {arg} total/{rows.Count} -> unique/{list.Count}");
list.Reverse();
rows = list;
}public virtual int ImportRows(IEnumerable<BaseRow> sourceRows)
return 0;
}
public virtual BaseRow[] ExportRows()
{
return Array.Empty<BaseRow>();
}
public virtual void BackupSource()
{
}TalkDataList โ
public override void OnInitialize()
public string GetTalk(string id, string idTopic, bool useDefault = false, bool human = true)
{
if (!initialized)
{
Initialize();
}
bool flag = false;
Dictionary<string, string> dictionary = all.TryGetValue(idTopic);
if (dictionary == null)
Dictionary<string, string> row = GetRow(idTopic);
if (row == null)
{
return "";
}
string text = dictionary.TryGetValue(id);
string text = row.TryGetValue(id);
if (text.IsEmpty())
{
if (!useDefault)public string GetTalk(string id, string idTopic, bool useDefault = false, bool h
Debug.LogError("id not found:" + id);
return "";
}
text = dictionary.TryGetValue("default");
text = row.TryGetValue("default");
flag = true;
}
text = text.Split(new string[3] { "\r\n", "\r", "\n" }, StringSplitOptions.None).RandomItem();ToneDataList โ
public override void OnInitialize()
public StringBuilder ApplyTone(string id, ref string text, int gender)
{
if (!initialized)
{
Initialize();
}
Initialize();
Dictionary<string, string> dictionary = all[id];
StringBuilder stringBuilder = new StringBuilder();
bool flag = false;public StringBuilder ApplyTone(string id, ref string text, int gender)
public string GetToneID(string id, int gender)
{
if (!initialized)
{
Initialize();
}
Initialize();
if (!Lang.isJP)
{
return id + "|I|YOU";