Skip to content

+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) โ€‹

cs
public T Create<T2>(Type type) 
public Func<T> CreateDelegate(Type type) 

Lang (1) โ€‹

cs
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

cs

	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")

cs
	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")

cs
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)

cs

	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)

cs
		}
	}

	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)

cs
		}
	}

	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 โ€‹

public void Parse()

cs
	{
		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 class ClassCache<T>

cs
{
	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)

cs
		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 class ClassCache

cs

	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

cs
public class ERROR
{
	public static string msg;
	public static int lastImported; 
}

ExcelDataList โ€‹

public virtual void OnInitialize()

cs
	{
	}

	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)

cs
	string str = GetStr(id);
	if (str != null)
	{
		return str.Split(','); 
		return str.Split(',').ToArray(); 
	}
	return Array.Empty<string>();
}

+GodTalkDataList โ€‹

File Created
cs
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

cs
{
	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;

cs
using System;
using System.Collections.Generic;
using System.Linq; 
using System.Text;

public class Lang

public class Words

cs

	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)

cs
		{
			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 class MOD

cs

	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 class MOD

cs
	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 =

cs
	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; 
				}
			}
			else

public override bool ImportData(ISheet sheet, string bookname, bool overwrite =

cs
		}
		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)

cs
		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()

cs
	}
	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()

cs
		}
		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)

cs
		return 0;
	}

	public virtual BaseRow[] ExportRows() 
	{ 
		return Array.Empty<BaseRow>(); 
	} 
	public virtual void BackupSource()
	{
	}

TalkDataList โ€‹

public override void OnInitialize()

cs

	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

cs
			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()

cs

	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)

cs

	public string GetToneID(string id, int gender)
	{
		if (!initialized) 
		{ 
			Initialize(); 
		} 
		Initialize(); 
		if (!Lang.isJP)
		{
			return id + "|I|YOU";