Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EasyCraft v1.0.6
plugins/EasyCraft/EasyCraft.dll
Decompiled 3 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("EasyCraft")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.6.0")] [assembly: AssemblyInformationalVersion("1.0.6")] [assembly: AssemblyProduct("EasyCraft")] [assembly: AssemblyTitle("EasyCraft")] [assembly: AssemblyVersion("1.0.6.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EasyCraft { public interface IFillSource { int Provide(Component target, Humanoid user, string[] sharedNames, int maxCount, out string sharedName, out string prefabName, out bool cheated); } [BepInPlugin("easycraft.client.only", "EasyCraft", "1.0.6")] public class EasyCraftPlugin : BaseUnityPlugin { public const string PluginGuid = "easycraft.client.only"; public const string PluginName = "EasyCraft"; public const string PluginVersion = "1.0.6"; private const string LegacySourcesDefault = "piece_dvergr_lantern,piece_dvergr_lantern_pole,piece_groundtorch,piece_groundtorchwood,piece_groundtorchgreen,piece_groundtorchblue,piece_firepit,piece_brazierfloor01,piece_brazierceiling01,piece_hearth"; private const string LegacyExcludeDefault = "piece_firepit"; internal const string DefaultAllowedContainers = "piece_chestwood,piece_chest,piece_chestblackmetal,piece_chestgrausten,piece_chestbarrel,piece_chestwarderobe"; private const string LegacyAllowedDefault = "piece_chest_wood,piece_chest"; internal static EasyCraftPlugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<bool> CfgEnabled; internal static ConfigEntry<int> CfgBatchSize; internal static ConfigEntry<string> CfgStationOreAllow; internal static ConfigEntry<bool> CfgFireEnabled; internal static ConfigEntry<string> CfgFireExclude; internal static ConfigEntry<string> CfgFireSources; internal static ConfigEntry<string> CfgFireOneAtATime; internal static ConfigEntry<float> CfgFireHoldInterval; internal static ConfigEntry<bool> CfgFireRespectToggle; internal static ConfigEntry<bool> CfgNearbyEnabled; internal static ConfigEntry<float> CfgRadius; internal static ConfigEntry<string> CfgAllowed; internal static ConfigEntry<bool> CfgIncludeCarts; internal static ConfigEntry<bool> CfgRespectWards; internal static ConfigEntry<bool> CfgClaimOwnership; internal static ConfigEntry<bool> CfgSkipInUse; internal static ConfigEntry<bool> CfgSkipIfPlayerNear; internal static ConfigEntry<float> CfgPlayerNearDistance; internal static ConfigEntry<bool> CfgDebug; internal static HashSet<string> AllowedSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static readonly List<string> AllowedRaw = new List<string>(); internal static IFillSource ExtraSource; private Harmony _harmony; private NearbyFillSource _source; internal static bool IsActive { get { if (CfgEnabled != null) { return CfgEnabled.Value; } return false; } } internal static bool DebugOn { get { if (CfgDebug != null) { return CfgDebug.Value; } return false; } } private void Awake() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected O, but got Unknown //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Expected O, but got Unknown //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Expected O, but got Unknown //IL_0358: Unknown result type (might be due to invalid IL or missing references) //IL_0362: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; CfgEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - Batch Fill", "Enabled", true, "开启后,每次与熔炉 / 高炉 / 炭窑 / 风车 / 纺车 / Eitr 精炼厂交互,会一次性装入 BatchSize 个材料,取代原版的一次 1 个。关闭则完全回到原版行为。"); CfgBatchSize = ((BaseUnityPlugin)this).Config.Bind<int>("1 - Batch Fill", "BatchSize", 5, new ConfigDescription("一次交互装入的材料数量。实际装入量会被「剩余容量」限制,绝不会超出上限(站点看 m_maxOre / m_maxFuel,火源看 m_maxFuel)。", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())); CfgStationOreAllow = ((BaseUnityPlugin)this).Config.Bind<string>("1 - Batch Fill", "StationOreAllow", "charcoal_kiln=$item_wood", "**站点进料限制**:限定某个炼制站点只允许自动装填指定材料。\n默认值 = 炭窑只烧木材(不放细木 / 圆木):\n charcoal_kiln=$item_wood\n\n格式:分号分组,等号左边站点、右边材料(逗号分隔):\n charcoal_kiln=$item_wood ; smelter=$item_copperore,$item_tinore\n\n材料名三种写法都认(忽略大小写):$item_wood / item_wood / Wood\n站点名认 GameObject 名,也认带 piece_ 前缀的形式:\n 炭窑 = charcoal_kiln(本地化键是 piece_charcoalkiln)\n 冶炼炉 = smelter\u3000\u3000高炉 = blastfurnace\n 风车 = windmill\u3000\u3000纺织机 = piece_spinningwheel\u3000\u3000埃达提炼器 = piece_eitrrefinery\n\n行为:\n · 只影响**本模组的自动装填**(批量 + 就近取料),你手动往站点里放什么都不受限制\n · 留空 = 不限制任何站点(完全回到原版)\n · 某个站点写成 `站点=` (右边留空)= 该站点不限制\n · 若填的材料名在该站点上一个都对不上,会打一条 Warning 列出实际可用材料,\n 并且**这一次不做限制**(避免把站点搞成什么都装不进去)\n · 生效与否看日志里的「站点进料限制生效:…」那一行"); CfgFireEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Fire Sources", "Enabled", true, "开启后,给火源加燃料也变成一次 BatchSize 个(原版是一次 1 个)。\n原版按 E 就是加燃料;只有 m_canTurnOff=true 的物件才会把「点按」当成开/关火。"); CfgFireExclude = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Fire Sources", "Exclude", "fire_pit,piece_firepit", "**默认排除**的火源(填 GameObject 名,逗号分隔,忽略大小写)。\n默认值 fire_pit = Campfire = 官方中文「营火」,也就是能做饭的那个基础火 —— 按要求让它保持原版一次填 1 个。\n⚠\ufe0f 这里填的是 **GameObject 名**,它不一定等于本地化键:\n 营火 = fire_pit(不是 piece_firepit)\n 篝火 = bonfire(不是 piece_bonfire)\n 灶 = hearth(不是 piece_hearth)\n 不确定某个火源叫什么,看日志里的 [火源探测] 那行 —— 它会把「组件在=…」和「ZDO名=…」都打出来。\n想让它也批量,把这行清空即可。"); CfgFireSources = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Fire Sources", "Sources", "", "可选白名单。**留空 = 所有火源都生效**(只受上面的 Exclude 限制),这是默认行为。\n只有你想「只让某几个火源生效」时才填,例如:\n piece_groundtorch,piece_dvergr_lantern\n填了之后,不在名单里的火源一律交回原版。"); CfgFireOneAtATime = ((BaseUnityPlugin)this).Config.Bind<string>("2 - Fire Sources", "OneAtATime", "fire_pit,piece_firepit", "「一次只装 1 个、但允许从附近箱子取料」的火源(GameObject 名,逗号分隔,忽略大小写)。\n默认 fire_pit = 营火:做饭的基础火一次烧 5 个木头太快,所以数量保持原版 1 个,\n但仍然会从附近箱子自动取木头(省得自己翻箱子)。\n⚠\ufe0f 名单里的火源应当同时出现在 Exclude 里(Exclude 负责\"不批量\",本名单负责\"保留取料\"):\n Exclude 命中且不在本名单 → 完全交回原版(连取料都没有)\n Exclude 命中且在本名单 → 数量固定 1 + 从箱子取料\n 未命中 Exclude → 正常批量 BatchSize 个\n想让某个火源彻底回到原版(连取料都不要),把它从本名单移除即可。"); CfgFireHoldInterval = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Fire Sources", "HoldInterval", 0f, new ConfigDescription("长按 E 时的最小间隔(秒)。**默认 0 = 不兜底**:原版 m_holdRepeatInterval 为 0 时长按本来就不加料,本模组照原版处理(什么都不做)。\n若你确实想让长按也批量,填一个正数(如 0.35)。\n注:m_holdRepeatInterval > 0 的火源,长按一律用它自己的间隔,与本项无关。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); CfgFireRespectToggle = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Fire Sources", "RespectToggle", true, "是否保留原版的「开/关火」:当火源 m_canTurnOff=true 且燃料>0 时,点按 E 是熄火/点火,本模组不接管。\n默认 true(不改变原版行为)。若你发现某个火源点按只会被熄火、不会批量装填,把这项设成 false 即可强制批量。\n(日志里的 [火源探测] 会打印每个火源的 m_canTurnOff 实际值。)"); CfgNearbyEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Nearby Chests", "Enabled", true, "开启后,玩家背包材料不足时,会自动从附近箱子补足。\n(原 EasyNearbyFill 的开关;关掉后只从背包取料。)"); CfgRadius = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Nearby Chests", "Radius", 30f, new ConfigDescription("搜索半径(米)。以【填料机器】和【玩家】各自为圆心,落在任一个半径内的箱子都会被算作可用料源。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>())); CfgAllowed = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Nearby Chests", "AllowedContainers", "piece_chestwood,piece_chest,piece_chestblackmetal,piece_chestgrausten,piece_chestbarrel,piece_chestwarderobe", "允许取料的箱子类型(逗号分隔)。\n\n✅ **匹配已归一化:忽略大小写 + 去 piece_ 前缀 + 去所有下划线。**\n 所以下面这些写法完全等价,你随便挑一种写:\n piece_chest_blackmetal == piece_chestblackmetal == CHESTBLACKMETAL\n ⚠\ufe0f 游戏里 GameObject 名与本地化键**不是同一个字符串**(前者带下划线、后者不带),\n 而且游戏自身命名也不统一 —— 所以靠归一化比精确比对可靠得多,不用纠结写法。\n\n各箱子的归一化键(= 实际参与匹配的键):\n chestwood 宝箱(木箱)\n chest 加固型宝箱(大箱子)\n chestblackmetal 黑金宝箱\n chestgrausten 灰石箱\n chestbarrel 桶\n chestwarderobe 衣柜\n有意不在默认列表里(需要才自己加):\n chestprivate 个人宝箱(私人箱)\n chesttreasure 珍宝箱\n\n· 留空 = 不取任何普通箱子(若同时开着 IncludeCarts,则只剩板车可取)\n· 生效与否看启动自检那两行:「允许的箱子 [...]」和「生效匹配键=[...]」"); CfgIncludeCarts = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Nearby Chests", "IncludeCarts", true, "**把板车也算作取料来源**(默认开)。\n板车(Cart)与雪橇(Sled)都用 `Vagon` 组件,内部带一个容器,\n本项开启后它们会和箱子一样被自动取料(静止停放时)。\n\n几个说明:\n · 判据是「这个容器挂在板车上」(`Container.m_wagon`),**不是按名字匹配**,\n 所以不受改名/层级变化影响,也**不需要**把 Cart 加进 AllowedContainers。\n · **正在被拖行的板车会被跳过** —— 位置一直在变,而且此时所有权在拖车人手里,\n 去接管所有权有可能把车从人家手上抢掉。停下不动了才会被取料。\n · 有人正开着板车容器时也不会取(与箱子的处理一致,受 4 - Safety 控制)。\n · 取板车的东西同样会走「守卫石权限检查」与「接管所有权」流程。"); CfgRespectWards = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Nearby Chests", "RespectWards", true, "额外做一次守卫石(私人区域)权限检查:玩家没有权限的箱子不会被取料。"); CfgClaimOwnership = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Nearby Chests", "ClaimOwnership", true, "取料前先接管该箱子的 ZDO 所有权(与游戏原生'打开箱子'完全一样的机制)。这是必须的:不接管所有权的话,箱子的物品变化不会被保存,会出现'东西进了炉子但箱子里还在'的复制问题。"); CfgSkipInUse = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Safety", "SkipInUse", true, "本地标记显示箱子\"正在被使用\"(Container.IsInUse())时跳过它。\n注意:这个标记是本地字段、不写进 ZDO,所以它只能反映【本机主人】和【你自己】的状态,看不到远处客户端是否正开着这个箱子 —— 那种情况要靠下面的 SkipIfPlayerNear。"); CfgSkipIfPlayerNear = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Safety", "SkipIfPlayerNear", true, "有其他玩家站在箱子附近时跳过它(认为他正在用这个箱子)。\n这是在联机里避免\"抢走别人正开着箱子的所有权\"的唯一可行手段,因为游戏没有把箱子占用状态同步给其他客户端。"); CfgPlayerNearDistance = ((BaseUnityPlugin)this).Config.Bind<float>("4 - Safety", "PlayerNearDistance", 4f, new ConfigDescription("\"别人正在用这个箱子\"的判定距离(米)。", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 20f), Array.Empty<object>())); CfgDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Debug", "DebugLogging", false, "输出每次装填 / 取料的详细过程(选中的材料、背包数量、从哪些箱子取了多少、最终装填数)。\n注意:火源的「一次性探测日志」、「跳过原因」和「首次装填明细」**不受这个开关控制**,默认就会打印。"); FireSources.Reload(NormalizeExclude(CfgFireExclude.Value), NormalizeSources(CfgFireSources.Value), CfgFireOneAtATime.Value); FireSources.SelfReport(); StationOreFilter.Reload(CfgStationOreAllow.Value); MigrateAllowedContainers(); ReloadAllowed(); _source = new NearbyFillSource(); ExtraSource = _source; _harmony = new Harmony("easycraft.client.only"); PatchAll(); SelfCheck(); } private string NormalizeSources(string csv) { if (string.IsNullOrEmpty(csv)) { return csv; } if (string.Equals(csv.Trim(), "piece_dvergr_lantern,piece_dvergr_lantern_pole,piece_groundtorch,piece_groundtorchwood,piece_groundtorchgreen,piece_groundtorchblue,piece_firepit,piece_brazierfloor01,piece_brazierceiling01,piece_hearth", StringComparison.OrdinalIgnoreCase)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"FireSources.Sources 仍是 1.1.0 的默认白名单(10 项),已按新语义当作「留空 = 全部火源生效」处理。想恢复白名单语义请自行改写该行。"); return ""; } return csv; } private string NormalizeExclude(string csv) { if (string.IsNullOrEmpty(csv)) { return csv; } if (string.Equals(csv.Trim(), "piece_firepit", StringComparison.OrdinalIgnoreCase)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"FireSources.Exclude 仍是 1.1.2 的旧默认值(piece_firepit),但营火的真实 GameObject 名是 fire_pit —— 旧值匹配不到任何火源。已按新默认值处理:fire_pit,piece_firepit"); return "fire_pit,piece_firepit"; } return csv; } private void MigrateAllowedContainers() { string value = CfgAllowed.Value; if (string.IsNullOrEmpty(value) || !string.Equals(value.Trim(), "piece_chest_wood,piece_chest", StringComparison.OrdinalIgnoreCase)) { return; } ((BaseUnityPlugin)this).Logger.LogWarning((object)"AllowedContainers 仍是 1.0.3 的旧默认值(piece_chest_wood,piece_chest),但带下划线的名字在游戏里并不存在(真实名是 piece_chestwood 等)——旧值导致木箱等箱子一直取不了料。已自动修正为:piece_chestwood,piece_chest,piece_chestblackmetal,piece_chestgrausten,piece_chestbarrel,piece_chestwarderobe\n (若你此前手动改过这一行且恰好等于旧默认值,请重新按新名单调整。)"); try { CfgAllowed.Value = "piece_chestwood,piece_chest,piece_chestblackmetal,piece_chestgrausten,piece_chestbarrel,piece_chestwarderobe"; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("写入新默认值失败(本次仅内存生效):" + ex.Message)); } } internal static string NormalizeContainerName(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } string text = name.Trim().ToLowerInvariant(); if (text.StartsWith("$")) { text = text.Substring(1); } if (text.StartsWith("piece_")) { text = text.Substring("piece_".Length); } if (text.IndexOf('_') >= 0) { text = text.Replace("_", string.Empty); } return text; } internal static void ReloadAllowed() { AllowedSet.Clear(); AllowedRaw.Clear(); string text = ((CfgAllowed != null) ? CfgAllowed.Value : null); if (string.IsNullOrEmpty(text)) { return; } string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } string text3 = NormalizeContainerName(text2); if (text3.Length != 0) { AllowedSet.Add(text3); if (!AllowedRaw.Contains(text2)) { AllowedRaw.Add(text2); } } } } private static string[] ToSortedArray(HashSet<string> set) { if (set == null) { return new string[0]; } string[] array = new string[set.Count]; set.CopyTo(array); Array.Sort(array, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); return array; } private static string[] ToSortedArray(IEnumerable<string> seq) { if (seq == null) { return new string[0]; } List<string> list = new List<string>(seq); list.Sort(StringComparer.OrdinalIgnoreCase); return list.ToArray(); } private void PatchAll() { Type[] array = new Type[7] { typeof(Smelter_OnAddOre_Patch), typeof(Smelter_OnAddFuel_Patch), typeof(Fireplace_Interact_Patch), typeof(Container_Awake_Patch), typeof(Container_OnDestroyed_Patch), typeof(Vagon_Awake_Patch), typeof(Vagon_OnDestroy_Patch) }; int num = 0; Type[] array2 = array; foreach (Type type in array2) { try { _harmony.CreateClassProcessor(type).Patch(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("已挂载补丁:" + type.Name)); num++; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("挂载失败 " + type.Name + ":" + ex)); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} 补丁 {2}/{3} 挂载成功。", "EasyCraft", "1.0.6", num, array.Length)); } private void SelfCheck() { try { ((BaseUnityPlugin)this).Logger.LogInfo((object)Describe(typeof(Smelter), "OnAddOre")); ((BaseUnityPlugin)this).Logger.LogInfo((object)Describe(typeof(Smelter), "OnAddFuel")); ((BaseUnityPlugin)this).Logger.LogInfo((object)Describe(typeof(Fireplace), "Interact")); ((BaseUnityPlugin)this).Logger.LogInfo((object)Describe(typeof(Container), "Awake")); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("BatchSize={0};火源批量={1},生效排除表=[{2}],生效白名单=[{3}](空=全部火源),一次一个名单=[{4}],RespectToggle={5},长按兜底间隔={6:0.##}s", CfgBatchSize.Value, CfgFireEnabled.Value ? "开" : "关", FireSources.EffectiveExclude, FireSources.EffectiveWhitelist, FireSources.EffectiveOneAtATime, CfgFireRespectToggle.Value, CfgFireHoldInterval.Value)); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("就近取料={0}:半径 {1:0.#} 米,允许的箱子 {2} 种 [{3}](板车={7}),守卫石检查={4},接管所有权={5},占用保护={6}。", CfgNearbyEnabled.Value ? "开" : "关", CfgRadius.Value, AllowedRaw.Count, string.Join(",", ToSortedArray(AllowedRaw)), CfgRespectWards.Value, CfgClaimOwnership.Value, (CfgSkipInUse.Value || CfgSkipIfPlayerNear.Value) ? "开" : "关", CfgIncludeCarts.Value ? "开" : "关")); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format(" 箱子名匹配已归一化(去 piece_ 前缀 + 去所有下划线 + 忽略大小写):生效匹配键=[{0}]", string.Join(",", ToSortedArray(AllowedSet)))); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("站点进料限制:{0} 组规则;生效值=[{1}](空=不限制任何站点)。生效明细会在首次装填该站点时打印。", StationOreFilter.StationCount, string.IsNullOrEmpty(StationOreFilter.EffectiveSpec) ? "" : StationOreFilter.EffectiveSpec)); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("自检失败:" + ex.Message)); } } private string Describe(Type type, string method) { MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null); Patches val = ((methodInfo != null) ? Harmony.GetPatchInfo((MethodBase)methodInfo) : null); return $"自检:{type.Name}.{method} prefix={((val != null && val.Prefixes != null) ? val.Prefixes.Count : 0)} postfix={((val != null && val.Postfixes != null) ? val.Postfixes.Count : 0)}"; } private void OnDestroy() { try { if (ExtraSource == _source) { ExtraSource = null; } if (_harmony != null) { _harmony.UnpatchSelf(); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("卸载失败:" + ex.Message)); } } } internal static class ContainerRegistry { private static readonly List<Container> List = new List<Container>(128); internal static int Count => List.Count; internal static void Add(Container c) { if (!((Object)(object)c == (Object)null) && !List.Contains(c)) { List.Add(c); } } internal static void Remove(Container c) { if (!((Object)(object)c == (Object)null)) { List.Remove(c); } } internal static List<Container> Snapshot() { List<Container> list = new List<Container>(List.Count); for (int num = List.Count - 1; num >= 0; num--) { Container val = List[num]; if ((Object)(object)val == (Object)null) { List.RemoveAt(num); } else { list.Add(val); } } return list; } } internal static class CartRegistry { private static readonly List<Vagon> List = new List<Vagon>(8); internal static int Count => List.Count; internal static void Add(Vagon v) { if (!((Object)(object)v == (Object)null) && !List.Contains(v)) { List.Add(v); } } internal static void Remove(Vagon v) { if (!((Object)(object)v == (Object)null)) { List.Remove(v); } } internal static Vagon FindByContainer(Container c) { if ((Object)(object)c == (Object)null) { return null; } for (int num = List.Count - 1; num >= 0; num--) { Vagon val = List[num]; if ((Object)(object)val == (Object)null) { List.RemoveAt(num); } else { Container val2 = null; try { val2 = val.m_container; } catch { } if (!((Object)(object)val2 == (Object)null) && val2 == c) { return val; } } } return null; } } internal enum FillOutcome { Filled, HandledNoFill, Fallback } internal static class Engine { internal struct Take { internal string SharedName; internal string PrefabName; internal bool Cheated; internal int Total; internal int FromExtra; } private static readonly FieldInfo F_Conversion = AccessTools.Field(typeof(Smelter), "m_conversion"); private static readonly FieldInfo F_AddedOreTime = AccessTools.Field(typeof(Smelter), "m_addedOreTime"); private static readonly FieldInfo F_SmelterNview = AccessTools.Field(typeof(Smelter), "m_nview"); private static readonly MethodInfo M_GetQueueSize = AccessTools.Method(typeof(Smelter), "GetQueueSize", (Type[])null, (Type[])null); private static readonly MethodInfo M_GetFuel = AccessTools.Method(typeof(Smelter), "GetFuel", (Type[])null, (Type[])null); private static readonly MethodInfo M_SetAnimation = AccessTools.Method(typeof(Smelter), "SetAnimation", (Type[])null, (Type[])null); private static readonly MethodInfo M_IsItemAllowed = AccessTools.Method(typeof(Smelter), "IsItemAllowed", new Type[1] { typeof(ItemData) }, (Type[])null); private static readonly MethodInfo M_Message = FindMessageMethod(); private static readonly FieldInfo F_FireNview = AccessTools.Field(typeof(Fireplace), "m_nview"); private static float _lastFireFillTime = -999f; private static float _lastFireMsgTime = -999f; private static readonly HashSet<string> FillLogged = new HashSet<string>(); internal static FillOutcome FillOre(Smelter smelter, Humanoid user, ItemData item) { return FillSmelter(smelter, user, item, fuel: false); } internal static FillOutcome FillFuel(Smelter smelter, Humanoid user, ItemData item) { return FillSmelter(smelter, user, item, fuel: true); } private static FillOutcome FillSmelter(Smelter smelter, Humanoid user, ItemData item, bool fuel) { string text = (fuel ? "燃料口" : "进料口"); if ((Object)(object)smelter == (Object)null || (Object)(object)user == (Object)null) { return FillOutcome.Fallback; } ZNetView smelterNView = GetSmelterNView(smelter); if ((Object)(object)smelterNView == (Object)null || !smelterNView.IsValid()) { Note(smelter, text + ":拿不到有效的 ZNetView"); return FillOutcome.Fallback; } if (user.GetInventory() == null) { Note(smelter, text + ":GetInventory() 返回 null"); return FillOutcome.Fallback; } int value = EasyCraftPlugin.CfgBatchSize.Value; int num; string[] array; if (fuel) { string text2 = SmelterFuelName(smelter); if (string.IsNullOrEmpty(text2)) { Note(smelter, text + ":m_fuelItem 为空,读不到燃料名"); return FillOutcome.Fallback; } if (item != null && item.m_shared != null && item.m_shared.m_name != text2) { Msg(user, "$msg_wrongitem"); return FillOutcome.HandledNoFill; } num = (int)Mathf.Floor((float)smelter.m_maxFuel - GetSmelterFuel(smelter)); if (num <= 0) { Msg(user, "$msg_itsfull"); return FillOutcome.HandledNoFill; } array = new string[1] { text2 }; } else { if (item != null && !IsItemAllowed(smelter, item)) { Msg(user, "$msg_wontwork"); return FillOutcome.HandledNoFill; } num = smelter.m_maxOre - GetQueueSize(smelter); if (num <= 0) { Msg(user, "$msg_itsfull"); return FillOutcome.HandledNoFill; } array = OreSharedNames(smelter); if (array.Length == 0) { Note(smelter, text + ":m_conversion 里读不到任何可熔材料名"); return FillOutcome.Fallback; } } int num2 = Math.Min(value, num); if (num2 <= 0) { return FillOutcome.HandledNoFill; } ItemData explicitItem = ((!fuel && item != null && item.m_shared != null) ? item : null); Take r = TakeItems((Component)(object)smelter, user, explicitItem, array, num2); if (r.Total <= 0 || r.SharedName == null) { if (fuel) { Msg(user, "$msg_donthaveany " + (r.SharedName ?? SmelterFuelName(smelter))); } else { Msg(user, "$msg_noprocessableitems"); } return FillOutcome.HandledNoFill; } try { if (fuel) { for (int i = 0; i < r.Total; i++) { smelterNView.InvokeRPC("RPC_AddFuel", Array.Empty<object>()); } } else { if (string.IsNullOrEmpty(r.PrefabName)) { EasyCraftPlugin.Log.LogWarning((object)("拿不到预制体名,本已扣掉的 " + r.Total + " 个料无法下发,请把这条日志发我:" + r.SharedName)); Msg(user, "$msg_noprocessableitems"); return FillOutcome.HandledNoFill; } for (int j = 0; j < r.Total; j++) { smelterNView.InvokeRPC("RPC_AddOre", new object[2] { r.PrefabName, r.Cheated }); } } } catch (Exception ex) { EasyCraftPlugin.Log.LogError((object)("下发 RPC 失败:" + ex)); Msg(user, "$msg_noprocessableitems"); return FillOutcome.HandledNoFill; } if (!fuel) { try { if (F_AddedOreTime != null) { F_AddedOreTime.SetValue(smelter, Time.time); } if (smelter.m_addOreAnimationDuration > 0f && M_SetAnimation != null) { M_SetAnimation.Invoke(smelter, new object[1] { true }); } } catch { } } Msg(user, "$msg_added " + r.SharedName + " x" + r.Total); LogFill("站点" + text, ((Component)smelter).gameObject, r, num); return FillOutcome.Filled; } internal static float GetFireFuel(Fireplace fp) { ZNetView fireNView = GetFireNView(fp); try { if ((Object)(object)fireNView != (Object)null && fireNView.IsValid()) { return fireNView.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); } } catch { } return 0f; } internal static ZNetView GetFireNView(Fireplace fp) { if ((Object)(object)fp == (Object)null) { return null; } try { if (F_FireNview != null) { object? value = F_FireNview.GetValue(fp); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } try { ZNetView component = ((Component)fp).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { return component; } } catch { } try { return ((Component)fp).GetComponentInParent<ZNetView>(); } catch { } return null; } internal static string FireFuelName(Fireplace fp) { if ((Object)(object)fp == (Object)null || (Object)(object)fp.m_fuelItem == (Object)null) { return null; } ItemData itemData = fp.m_fuelItem.m_itemData; if (itemData == null || itemData.m_shared == null) { return null; } return itemData.m_shared.m_name; } internal static FillOutcome FillFireplace(Fireplace fp, Humanoid user, bool hold, int wantOverride = 0) { if ((Object)(object)fp == (Object)null || (Object)(object)user == (Object)null) { return FillOutcome.Fallback; } if (hold) { float num = ((fp.m_holdRepeatInterval > 0f) ? fp.m_holdRepeatInterval : EasyCraftPlugin.CfgFireHoldInterval.Value); if (num <= 0f) { FireSources.ReportBail(((Component)fp).gameObject, "长按 + m_holdRepeatInterval=0(原版长按本来就不加料,本模组照原版)"); return FillOutcome.Fallback; } if (Time.time - _lastFireFillTime < num) { return FillOutcome.HandledNoFill; } } ZNetView fireNView = GetFireNView(fp); if ((Object)(object)fireNView == (Object)null || !fireNView.IsValid()) { FireSources.ReportBail(((Component)fp).gameObject, "拿不到有效的 ZNetView"); return FillOutcome.Fallback; } if (user.GetInventory() == null) { FireSources.ReportBail(((Component)fp).gameObject, "GetInventory() 返回 null"); return FillOutcome.Fallback; } string text = FireFuelName(fp); if (string.IsNullOrEmpty(text)) { FireSources.ReportBail(((Component)fp).gameObject, "m_fuelItem 为空,读不到燃料名"); return FillOutcome.Fallback; } try { if (!fireNView.HasOwner()) { fireNView.ClaimOwnership(); } } catch { } float maxFuel = fp.m_maxFuel; int num2 = Mathf.CeilToInt(GetFireFuel(fp)); if ((float)num2 >= maxFuel) { MsgThrottled(user, FireSources.Loc("$msg_cantaddmore", text)); return FillOutcome.HandledNoFill; } int num3 = Mathf.FloorToInt(maxFuel) - num2; if (num3 <= 0) { MsgThrottled(user, FireSources.Loc("$msg_cantaddmore", text)); return FillOutcome.HandledNoFill; } int count = Math.Min((wantOverride > 0) ? wantOverride : EasyCraftPlugin.CfgBatchSize.Value, num3); Take r = TakeItems((Component)(object)fp, user, null, new string[1] { text }, count); if (r.Total <= 0) { MsgThrottled(user, "$msg_outof " + text); return FillOutcome.HandledNoFill; } _lastFireFillTime = Time.time; try { fireNView.InvokeRPC("RPC_AddFuelAmount", new object[1] { (float)r.Total }); } catch (Exception ex) { EasyCraftPlugin.Log.LogError((object)("下发 RPC_AddFuelAmount 失败:" + ex)); return FillOutcome.HandledNoFill; } Msg(user, FireSources.Loc("$msg_fireadding", r.SharedName) + " x" + r.Total); LogFill("火源", ((Component)fp).gameObject, r, num3); return FillOutcome.Filled; } private static void MsgThrottled(Humanoid user, string text) { if (!(Time.time - _lastFireMsgTime < 0.5f)) { _lastFireMsgTime = Time.time; Msg(user, text); } } internal static Take TakeItems(Component target, Humanoid user, ItemData explicitItem, string[] candidates, int count) { Take result = default(Take); if (count <= 0 || (Object)(object)user == (Object)null) { return result; } Inventory inventory = user.GetInventory(); if (inventory == null) { return result; } if (explicitItem != null && explicitItem.m_shared != null) { result.SharedName = explicitItem.m_shared.m_name; result.PrefabName = PrefabNameOf(explicitItem); result.Cheated = explicitItem.m_cheated; } else if (candidates != null) { foreach (string text in candidates) { if (!string.IsNullOrEmpty(text)) { ItemData item = inventory.GetItem(text, -1, false); if (item != null) { result.SharedName = item.m_shared.m_name; result.PrefabName = PrefabNameOf(item); result.Cheated = item.m_cheated; break; } } } } if (result.SharedName != null) { int val = inventory.CountItems(result.SharedName, -1, false); int num = Math.Min(count, val); if (num > 0) { inventory.RemoveItem(result.SharedName, num, -1, false); result.Total = num; } } if (result.Total < count) { IFillSource extraSource = EasyCraftPlugin.ExtraSource; if (extraSource != null) { string[] sharedNames = ((result.SharedName == null) ? candidates : new string[1] { result.SharedName }); string sharedName = null; string prefabName = null; bool cheated = false; int num2 = 0; try { num2 = extraSource.Provide(target, user, sharedNames, count - result.Total, out sharedName, out prefabName, out cheated); } catch (Exception ex) { EasyCraftPlugin.Log.LogError((object)("扩展料源报错:" + ex)); } if (num2 > 0) { result.FromExtra = num2; result.Total += num2; if (result.SharedName == null) { result.SharedName = sharedName; } if (result.PrefabName == null) { result.PrefabName = prefabName; } if (!result.Cheated) { result.Cheated = cheated; } } } else if (EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogInfo((object)("背包只有 " + result.Total + " 个,但没有注册扩展料源。")); } } return result; } internal static string SmelterFuelName(Smelter smelter) { if ((Object)(object)smelter == (Object)null || (Object)(object)smelter.m_fuelItem == (Object)null) { return null; } ItemData itemData = smelter.m_fuelItem.m_itemData; if (itemData == null || itemData.m_shared == null) { return null; } return itemData.m_shared.m_name; } internal static string[] OreSharedNames(Smelter smelter) { List<StationOreFilter.Option> list = new List<StationOreFilter.Option>(); if ((Object)(object)smelter == (Object)null || F_Conversion == null) { FireSources.NoteOnce("m_conversion|null", "[站点] 反射不到 Smelter.m_conversion 字段"); return new string[0]; } IEnumerable enumerable; try { enumerable = F_Conversion.GetValue(smelter) as IEnumerable; } catch (Exception ex) { EasyCraftPlugin.Log.LogWarning((object)("读取 m_conversion 失败:" + ex.Message)); return new string[0]; } if (enumerable == null) { return new string[0]; } foreach (object item in enumerable) { if (item == null) { continue; } ItemDrop itemDropField = GetItemDropField(item, "m_from"); if ((Object)(object)itemDropField == (Object)null) { continue; } ItemData itemData = itemDropField.m_itemData; if (itemData == null || itemData.m_shared == null) { continue; } string name = itemData.m_shared.m_name; if (string.IsNullOrEmpty(name)) { continue; } string prefab = null; try { prefab = (((Object)(object)((Component)itemDropField).gameObject != (Object)null) ? ((Object)((Component)itemDropField).gameObject).name : ((Object)itemDropField).name); } catch { } bool flag = false; foreach (StationOreFilter.Option item2 in list) { if (string.Equals(item2.SharedName, name, StringComparison.Ordinal)) { flag = true; break; } } if (!flag) { list.Add(new StationOreFilter.Option { SharedName = name, Prefab = prefab }); } } GameObject stationGo = null; try { stationGo = ((Component)smelter).gameObject; } catch { } return StationOreFilter.Apply(stationGo, list); } private static ItemDrop GetItemDropField(object owner, string field) { try { Type type = owner.GetType(); while (type != null) { FieldInfo field2 = type.GetField(field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { object? value = field2.GetValue(owner); return (ItemDrop)((value is ItemDrop) ? value : null); } type = type.BaseType; } } catch { } return null; } internal static string PrefabNameOf(ItemData item) { if (item == null) { return null; } try { if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } } catch { } return null; } internal static ZNetView GetSmelterNView(Smelter smelter) { try { if (F_SmelterNview != null) { object? value = F_SmelterNview.GetValue(smelter); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } try { return ((Component)smelter).GetComponent<ZNetView>(); } catch { } return null; } internal static float GetSmelterFuel(Smelter smelter) { try { if (M_GetFuel != null) { return (float)M_GetFuel.Invoke(smelter, null); } } catch { } return 0f; } internal static int GetQueueSize(Smelter smelter) { try { if (M_GetQueueSize != null) { return (int)M_GetQueueSize.Invoke(smelter, null); } } catch { } return 0; } private static bool IsItemAllowed(Smelter smelter, ItemData item) { if (item == null || item.m_shared == null) { return false; } try { if (M_IsItemAllowed != null) { return (bool)M_IsItemAllowed.Invoke(smelter, new object[1] { item }); } } catch { } return true; } private static MethodInfo FindMessageMethod() { MethodInfo result = null; int num = -1; try { MethodInfo[] methods = typeof(Character).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "Message")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 2 && parameters[0].ParameterType.IsEnum && !(parameters[1].ParameterType != typeof(string)) && parameters.Length > num) { num = parameters.Length; result = methodInfo; } } } } catch { } return result; } private static void Msg(Humanoid user, string text) { if ((Object)(object)user == (Object)null || M_Message == null) { return; } try { ParameterInfo[] parameters = M_Message.GetParameters(); object[] array = new object[parameters.Length]; array[0] = Enum.ToObject(parameters[0].ParameterType, 2); array[1] = text; for (int i = 2; i < parameters.Length; i++) { array[i] = (parameters[i].ParameterType.IsValueType ? Activator.CreateInstance(parameters[i].ParameterType) : null); } M_Message.Invoke(user, array); } catch (Exception ex) { if (EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogDebug((object)("提示消息失败:" + ex.Message)); } } } private static void Note(Smelter smelter, string reason) { string text = (((Object)(object)smelter != (Object)null) ? ((Object)((Component)smelter).gameObject).name : "<null>"); FireSources.NoteOnce("smelter|" + text + "|" + reason, "[站点跳过] " + text + " → " + reason + "(本次交回原版处理)"); } private static void LogFill(string kind, GameObject go, Take r, int capacity) { string text = FireSources.BestPrefabName(go); if (string.IsNullOrEmpty(text)) { text = "<无名>"; } if (FillLogged.Add(kind + "|" + text) || EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogInfo((object)$"[{kind}装填] {text}|{r.SharedName} 装了 {r.Total} 个(本次上限余量 {capacity}|背包 {r.Total - r.FromExtra}|箱子等外部来源 {r.FromExtra})"); } } } internal static class FireSources { internal sealed class NameSet { private readonly HashSet<string> _items; internal int Count => _items.Count; internal string Joined => string.Join(",", _items); internal NameSet(IEnumerable<string> items) { _items = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (items == null) { return; } foreach (string item in items) { if (!string.IsNullOrEmpty(item)) { _items.Add(item.Trim()); } } } internal bool Contains(string name) { if (!string.IsNullOrEmpty(name)) { return _items.Contains(name); } return false; } } internal const string DefaultExclude = "fire_pit,piece_firepit"; internal const string DefaultOneAtATime = "fire_pit,piece_firepit"; private static NameSet _exclude = new NameSet(new string[1] { "fire_pit,piece_firepit" }); private static NameSet _oneAtATime = new NameSet(new string[1] { "fire_pit,piece_firepit" }); private static NameSet _whitelist = new NameSet(new string[0]); private static readonly MethodInfo M_PrefabName = FindPrefabNameMethod(); private static readonly HashSet<string> Probed = new HashSet<string>(); private static readonly HashSet<string> Notes = new HashSet<string>(); private static MethodInfo _mZdoPrefabName; private static bool _zdoProbed; private static PropertyInfo _locInstance; private static MethodInfo _localizeArgs; private static bool _locResolved; internal static int ExcludeCount => _exclude.Count; internal static int WhitelistCount => _whitelist.Count; internal static string EffectiveExclude => _exclude.Joined; internal static string EffectiveWhitelist => _whitelist.Joined; internal static string EffectiveOneAtATime => _oneAtATime.Joined; internal static void Reload(string excludeCsv, string whitelistCsv, string oneAtATimeCsv) { _exclude = new NameSet(Split(excludeCsv)); _oneAtATime = new NameSet(Split(oneAtATimeCsv)); _whitelist = new NameSet(Split(whitelistCsv)); } private static List<string> Split(string csv) { List<string> list = new List<string>(); if (string.IsNullOrEmpty(csv)) { return list; } string[] array = csv.Split(new char[1] { ',' }); foreach (string text in array) { if (text.Trim().Length > 0) { list.Add(text.Trim()); } } return list; } internal static bool IsAllowed(GameObject go) { if ((Object)(object)go == (Object)null) { return false; } Transform val = go.transform; int num = 0; while ((Object)(object)val != (Object)null && num++ < 8) { string text = PrefabName(((Component)val).gameObject); if (!string.IsNullOrEmpty(text)) { if (_exclude.Contains(text)) { return false; } if (_whitelist.Count <= 0) { return true; } if (_whitelist.Contains(text)) { return true; } } val = val.parent; } return false; } internal static bool IsOneAtATime(GameObject go) { if ((Object)(object)go == (Object)null || _oneAtATime.Count == 0) { return false; } Transform val = go.transform; int num = 0; while ((Object)(object)val != (Object)null && num++ < 8) { string text = PrefabName(((Component)val).gameObject); if (!string.IsNullOrEmpty(text) && _oneAtATime.Contains(text)) { return true; } val = val.parent; } return false; } internal static string BestPrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } Transform val = go.transform; int num = 0; string text = null; while ((Object)(object)val != (Object)null && num++ < 8) { string text2 = PrefabName(((Component)val).gameObject); if (!string.IsNullOrEmpty(text2)) { if (text2.StartsWith("piece_", StringComparison.OrdinalIgnoreCase)) { return text2; } if (text == null) { text = text2; } } val = val.parent; } return text; } internal static void NoteOnce(string key, string message) { if (string.IsNullOrEmpty(key)) { key = "?"; } if (!Notes.Add(key)) { return; } try { EasyCraftPlugin.Log.LogInfo((object)message); } catch { } } internal static void ReportBail(GameObject go, string reason) { string text = BestPrefabName(go); if (string.IsNullOrEmpty(text)) { text = "<无名>"; } NoteOnce(text + "|" + reason, "[火源跳过] " + text + " → " + reason + "(本次交回原版处理)"); } internal static void Probe(Fireplace fp) { if ((Object)(object)fp == (Object)null) { return; } string text = BestPrefabName(((Component)fp).gameObject); if (string.IsNullOrEmpty(text)) { text = "<无名>"; } if (!Probed.Add(text)) { return; } try { ZNetView component = ((Component)fp).gameObject.GetComponent<ZNetView>(); float num = 0f; bool flag = (Object)(object)component != (Object)null && component.IsValid(); if (flag) { num = component.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); } string text2 = "<m_fuelItem 为 null>"; if ((Object)(object)fp.m_fuelItem != (Object)null) { ItemData itemData = fp.m_fuelItem.m_itemData; text2 = ((itemData != null && itemData.m_shared != null) ? itemData.m_shared.m_name : "<m_fuelItem.m_itemData.m_shared 为 null>"); } EasyCraftPlugin.Log.LogInfo((object)string.Format("[火源探测] {0}|组件在={1}|ZDO名={2}|ZNetView={3} 有效={4} 燃料={5:0.##}|可加燃料={6} 可开关={7} 无限燃料={8} 长按重复间隔={9:0.##}|上限={10:0.#} 燃料物品={11}|结论={12}", text, ((Object)((Component)fp).gameObject).name, ZdoPrefabName(component) ?? "<取不到>", ((Object)(object)component != (Object)null) ? "有" : "无", flag, num, fp.m_canRefill, fp.m_canTurnOff, fp.m_infiniteFuel, fp.m_holdRepeatInterval, fp.m_maxFuel, text2, DescribeDecision(((Component)fp).gameObject))); } catch (Exception ex) { EasyCraftPlugin.Log.LogWarning((object)("[火源探测] " + text + " 读取状态失败:" + ex.Message)); } } private static string DescribeDecision(GameObject go) { if (IsOneAtATime(go)) { return "本模组接管:一次只装 1 个(OneAtATime),料源可用"; } if (IsAllowed(go)) { return "本模组接管"; } return "交回原版(被 Exclude 排除,或不在 Sources 白名单里)"; } private static string ZdoPrefabName(ZNetView nv) { if ((Object)(object)nv == (Object)null) { return null; } if (!_zdoProbed) { _zdoProbed = true; try { Type type = AccessTools.TypeByName("ZNetView"); if (type != null) { _mZdoPrefabName = type.GetMethod("GetPrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } } catch { } } if (_mZdoPrefabName == null) { return null; } try { return (string)_mZdoPrefabName.Invoke(nv, null); } catch { return null; } } private static MethodInfo FindPrefabNameMethod() { try { Type type = AccessTools.TypeByName("Utils"); if (type == null) { return null; } return AccessTools.Method(type, "GetPrefabName", new Type[1] { typeof(GameObject) }, (Type[])null); } catch { return null; } } internal static string PrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } if (M_PrefabName != null) { try { string text = (string)M_PrefabName.Invoke(null, new object[1] { go }); if (!string.IsNullOrEmpty(text)) { return text.Trim(); } } catch { } } string text2 = ((Object)go).name; int num = text2.IndexOf("(Clone)"); if (num >= 0) { text2 = text2.Substring(0, num); } num = text2.IndexOf(' '); if (num >= 0) { text2 = text2.Substring(0, num); } return text2.Trim(); } private static void ResolveLocalization() { if (_locResolved) { return; } _locResolved = true; try { Type type = AccessTools.TypeByName("Localization"); if (type == null) { EasyCraftPlugin.Log.LogWarning((object)"未找到 Localization 类型,火源提示将退回原始键名。"); return; } _locInstance = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "Localize")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(string)) && !(parameters[1].ParameterType != typeof(string[]))) { _localizeArgs = methodInfo; break; } } } EasyCraftPlugin.Log.LogInfo((object)("本地化支持:" + ((_locInstance != null) ? "instance OK " : "instance 缺 ") + ((_localizeArgs != null) ? "Localize(string,string[]) OK" : "Localize 两参重载缺失"))); } catch (Exception ex) { EasyCraftPlugin.Log.LogWarning((object)("解析 Localization 失败:" + ex.Message)); } } internal static void SelfReport() { ResolveLocalization(); } internal static string Loc(string key, string arg) { ResolveLocalization(); try { if (_locInstance != null && _localizeArgs != null) { object value = _locInstance.GetValue(null, null); if (value != null) { return (string)_localizeArgs.Invoke(value, new object[2] { key, new string[1] { arg } }); } } } catch (Exception ex) { EasyCraftPlugin.Log.LogDebug((object)("本地化失败 " + key + ":" + ex.Message)); } return key + " " + arg; } } internal sealed class NearbyFillSource : IFillSource { private static readonly MethodInfo M_PrefabName = FindPrefabNameMethod(); private static readonly MethodInfo M_ContainerLoad = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null) ?? ResolveContainerLoad(); private static readonly FieldInfo F_ContainerNView = AccessTools.Field(typeof(Container), "m_nview"); private static readonly FieldInfo F_VagonAttachJoin = AccessTools.Field(typeof(Vagon), "m_attachJoin"); private static MethodInfo ResolveContainerLoad() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null); if (methodInfo != null) { return methodInfo; } foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(Container))) { if (declaredMethod.Name == "Load" && declaredMethod.GetParameters().Length == 0) { return declaredMethod; } } } catch { } return null; } public int Provide(Component target, Humanoid user, string[] sharedNames, int maxCount, out string sharedName, out string prefabName, out bool cheated) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) sharedName = null; prefabName = null; cheated = false; if (EasyCraftPlugin.CfgNearbyEnabled == null || !EasyCraftPlugin.CfgNearbyEnabled.Value) { return 0; } if ((Object)(object)target == (Object)null || (Object)(object)user == (Object)null) { return 0; } if (sharedNames == null || sharedNames.Length == 0) { return 0; } if (maxCount <= 0) { return 0; } if (EasyCraftPlugin.AllowedSet.Count == 0 && !EasyCraftPlugin.CfgIncludeCarts.Value) { if (EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogWarning((object)"AllowedContainers 为空、且板车未启用,没有料源可用。"); } return 0; } Vector3 position = target.transform.position; Vector3 position2 = ((Component)user).transform.position; float value = EasyCraftPlugin.CfgRadius.Value; float num = value * value; List<KeyValuePair<float, Container>> list = new List<KeyValuePair<float, Container>>(); int num2 = 0; StringBuilder stringBuilder = (EasyCraftPlugin.DebugOn ? new StringBuilder() : null); int num3 = 0; foreach (Container item2 in ContainerRegistry.Snapshot()) { if ((Object)(object)item2 == (Object)null) { continue; } Transform transform = ((Component)item2).transform; if ((Object)(object)transform == (Object)null) { continue; } Vector3 val = transform.position - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; val = transform.position - position2; float sqrMagnitude2 = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude > num && sqrMagnitude2 > num) { continue; } if (!IsAllowed(item2)) { num3++; stringBuilder?.Append("\n · [未通过白名单] ").Append(DescribeNames((Component)(object)item2)); continue; } if (!HasAccess(item2)) { num3++; stringBuilder?.Append("\n · [守卫石无权限] ").Append(DescribeNames((Component)(object)item2)); continue; } if (IsBusy(item2, user)) { num2++; stringBuilder?.Append("\n · [有人正在用] ").Append(DescribeNames((Component)(object)item2)); continue; } if (IsCart(item2)) { stringBuilder?.Append("\n · [板车被跳过] ").Append(DescribeNames((Component)(object)item2)).Append(" → ") .Append((!EasyCraftPlugin.CfgIncludeCarts.Value) ? "IncludeCarts 开关是关的" : ("被判定为'正在被拖行':" + DescribeAttachSignal(item2))); } list.Add(new KeyValuePair<float, Container>(Mathf.Min(sqrMagnitude, sqrMagnitude2), item2)); } list.Sort((KeyValuePair<float, Container> a, KeyValuePair<float, Container> b) => a.Key.CompareTo(b.Key)); int num4 = 0; int num5 = 0; foreach (KeyValuePair<float, Container> item3 in list) { if (num4 >= maxCount) { break; } Container value2 = item3.Value; Inventory inventory = value2.GetInventory(); if (inventory == null) { stringBuilder?.Append("\n · ").Append(PrefabOfComponent((Component)(object)value2)).Append(" → GetInventory() 为 null"); continue; } bool flag = false; string reason = null; if (!EnsureOwner(value2, out reason)) { stringBuilder?.Append("\n · ").Append(DescribeNames((Component)(object)value2)).Append(" → 接管所有权失败(") .Append(reason) .Append(")"); continue; } RefreshChestInventory(value2, stringBuilder); foreach (string text in sharedNames) { if (string.IsNullOrEmpty(text)) { continue; } ItemData item = inventory.GetItem(text, -1, false); if (item == null) { continue; } int val2 = inventory.CountItems(text, -1, false); int num7 = Math.Min(maxCount - num4, val2); if (num7 > 0) { inventory.RemoveItem(text, num7, -1, false); num4 += num7; num5++; flag = true; if (!VerifyOwnerAfterTake(value2)) { EasyCraftPlugin.Log.LogWarning((object)("[就近料源] " + DescribeNames((Component)(object)value2) + " 取走 " + num7 + " 个 " + text + " 后仍未取得 ZDO 写权 —— 这次改动可能不会被保存,箱子里可能仍在(刷新即可看到回弹)。请把这条日志发给我。")); } if (sharedName == null) { sharedName = ((item.m_shared != null) ? item.m_shared.m_name : text); prefabName = PrefabOfItem(item); cheated = item.m_cheated; } break; } } if (stringBuilder != null && !flag) { stringBuilder.Append("\n · ").Append(DescribeNames((Component)(object)value2)).Append(" → 不含目标材料,箱内共 ") .Append(DescribeChest(inventory)); } } if (num4 > 0) { EasyCraftPlugin.Log.LogInfo((object)string.Format("[就近料源] {0}|从 {1} 个箱子取了 {2} 个 {3}(半径 {4:0.#}m,候选箱 {5} 个,跳过占用中 {6} 个)", ((Object)target.gameObject).name, num5, num4, sharedName ?? "-", value, list.Count, num2)); } else if (EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogInfo((object)string.Format("[就近料源] {0}|没取到料(半径 {1:0.#}m,候选箱 {2} 个,半径内被拦 {3} 个,跳过占用中 {4} 个,场上板车 {7} 辆)|要的是 [{5}]{6}", ((Object)target.gameObject).name, value, list.Count, num3, num2, string.Join(",", sharedNames), (stringBuilder != null && stringBuilder.Length > 0) ? stringBuilder.ToString() : "(半径内没有任何容器)", CartRegistry.Count)); } return num4; } private static string DescribeChest(Inventory inv) { if (inv == null) { return "<无库存>"; } List<string> list = new List<string>(); try { List<ItemData> allItems = inv.GetAllItems(); if (allItems == null || allItems.Count == 0) { return "<空箱>"; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ItemData item in allItems) { if (item == null || item.m_shared == null) { continue; } string name = item.m_shared.m_name; if (!string.IsNullOrEmpty(name) && hashSet.Add(name)) { list.Add(name + "×" + inv.CountItems(name, -1, false)); if (list.Count >= 8) { list.Add("…"); break; } } } } catch (Exception ex) { return "<读取失败:" + ex.Message + ">"; } if (list.Count != 0) { return string.Join(" ", list.ToArray()); } return "<空箱>"; } private static bool IsBusy(Container c, Humanoid me) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (EasyCraftPlugin.CfgSkipInUse.Value) { try { if (c.IsInUse()) { return true; } } catch { } } if (EasyCraftPlugin.CfgSkipIfPlayerNear.Value) { float value = EasyCraftPlugin.CfgPlayerNearDistance.Value; float num = value * value; Vector3 position = ((Component)c).transform.position; List<Player> list = null; try { list = Player.GetAllPlayers(); } catch { } if (list != null) { foreach (Player item in list) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)me) && !((Object)(object)item == (Object)(object)Player.m_localPlayer)) { Vector3 val = ((Component)item).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude <= num) { return true; } } } } } return false; } private static bool IsAllowed(Container c) { Vagon val = null; try { val = c.m_wagon; } catch { } if ((Object)(object)val == (Object)null) { try { val = CartRegistry.FindByContainer(c); } catch { } } if ((Object)(object)val != (Object)null) { if (!EasyCraftPlugin.CfgIncludeCarts.Value) { return false; } try { if (val.IsAttached()) { return false; } } catch { } return true; } string text = PrefabOfComponent((Component)(object)c); if (string.IsNullOrEmpty(text)) { return false; } return EasyCraftPlugin.AllowedSet.Contains(EasyCraftPlugin.NormalizeContainerName(text)); } private static string DescribeAttachSignal(Container c) { Vagon val = null; try { val = c.m_wagon; } catch { } if ((Object)(object)val == (Object)null) { try { val = CartRegistry.FindByContainer(c); } catch { } } if ((Object)(object)val == (Object)null) { return "取不到 Vagon"; } bool flag = false; try { flag = F_VagonAttachJoin != null && F_VagonAttachJoin.GetValue(val) != null; } catch { } bool flag2 = false; try { ZNetView component = ((Component)val).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { flag2 = component.GetZDO().GetBool("attachJoint", false); } } catch { } if (flag && flag2) { return "本地关节 m_attachJoin 非空 + ZDO 标志为真(真的连着)"; } if (flag) { return "本地关节 m_attachJoin 非空(本机看得到实体关节 —— 真的连着)"; } if (flag2) { return "只有 ZDO 标志为真、本机没有关节(别人在拖,或标志残留)"; } return "两条子信号都是假 —— 但 IsAttached() 返回了 true(异常,请把这行发我)"; } private static bool IsCart(Container c) { if ((Object)(object)c == (Object)null) { return false; } try { if ((Object)(object)c.m_wagon != (Object)null) { return true; } } catch { } try { return (Object)(object)CartRegistry.FindByContainer(c) != (Object)null; } catch { } return false; } private static bool HasAccess(Container c) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (!EasyCraftPlugin.CfgRespectWards.Value) { return true; } try { return PrivateArea.CheckAccess(((Component)c).transform.position, 0f, false, true); } catch { return true; } } private static ZNetView ResolveNView(Container c) { if ((Object)(object)c == (Object)null) { return null; } try { if (F_ContainerNView != null) { object? value = F_ContainerNView.GetValue(c); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } } catch { } try { ZNetView rootObjectOverride = c.m_rootObjectOverride; if ((Object)(object)rootObjectOverride != (Object)null) { return rootObjectOverride; } } catch { } try { return ((Component)c).GetComponent<ZNetView>(); } catch { } return null; } private static bool EnsureOwner(Container c, out string reason) { reason = null; ZNetView val = ResolveNView(c); if ((Object)(object)val == (Object)null) { reason = "本物体与其根对象上都没有 ZNetView"; return false; } if (!val.IsValid()) { reason = "ZNetView 无效(物体未初始化)"; return false; } if (val.IsOwner()) { return true; } if (!EasyCraftPlugin.CfgClaimOwnership.Value) { reason = "所有权不在本机,且 ClaimOwnership 开关已关"; if (EasyCraftPlugin.DebugOn) { EasyCraftPlugin.Log.LogWarning((object)("箱子 " + PrefabOfComponent((Component)(object)c) + " 的所有权不在本机,且已关闭自动接管,跳过。")); } return false; } try { val.ClaimOwnership(); } catch (Exception ex) { reason = "ClaimOwnership 抛异常:" + ex.Message; return false; } return true; } private static bool VerifyOwnerAfterTake(Container c) { try { ZNetView val = ResolveNView(c); if ((Object)(object)val == (Object)null || !val.IsValid()) { return false; } if (val.IsOwner()) { return true; } return val.IsOwner(); } catch { return false; } } private static void RefreshChestInventory(Container c, StringBuilder diag) { if ((Object)(object)c == (Object)null) { return; } try { if (M_ContainerLoad == null) { diag?.Append("(无法反射 Container.Load,跳过刷新)"); return; } object obj = M_ContainerLoad.Invoke(c, null); diag?.Append((obj is bool && (bool)obj) ? "(已刷新库存)" : "(库存未变化)"); } catch (Exception ex) { diag?.Append("(刷新库存失败:" + ex.Message + ")"); } } private static MethodInfo FindPrefabNameMethod() { try { Type type = AccessTools.TypeByName("Utils"); if (type == null) { return null; } return AccessTools.Method(type, "GetPrefabName", new Type[1] { typeof(GameObject) }, (Type[])null); } catch { return null; } } private static string PrefabOfComponent(Component c) { if ((Object)(object)c == (Object)null) { return null; } if (M_PrefabName != null) { try { return (string)M_PrefabName.Invoke(null, new object[1] { c.gameObject }); } catch { } } string text = ((Object)c.gameObject).name; int num = text.IndexOf("(Clone)"); if (num >= 0) { text = text.Substring(0, num); } return text.Trim(); } private static string DescribeNames(Component c) { if ((Object)(object)c == (Object)null) { return "<null>"; } string text = null; string text2 = null; string text3 = null; string text4 = null; try { text = ((Object)c.gameObject).name; } catch { } try { text = PrefabOfComponent(c); } catch { } try { text4 = EasyCraftPlugin.NormalizeContainerName(text); } catch { } try { Container val = (Container)(object)((c is Container) ? c : null); ZNetView val2 = (((Object)(object)val != (Object)null) ? ResolveNView(val) : c.GetComponent<ZNetView>()); if ((Object)(object)val2 != (Object)null && val2.IsValid()) { text2 = val2.GetZDO().GetString("prefab", (string)null) ?? "<按哈希存,无字符串>"; } } catch { } try { Piece component = c.GetComponent<Piece>(); if ((Object)(object)component != (Object)null) { text3 = component.m_name; } } catch { } string text5 = "否"; try { if ((Object)(object)CartRegistry.FindByContainer((Container)(object)((c is Container) ? c : null)) != (Object)null) { text5 = "是(Vagon登记表)"; } else { Container val3 = (Container)(object)((c is Container) ? c : null); if ((Object)(object)val3 != (Object)null && (Object)(object)val3.m_wagon != (Object)null) { text5 = "是(Container.m_wagon)"; } } } catch { } return "GameObject[" + (text ?? "-") + "] 归一化[" + (text4 ?? "-") + "] 本地化键[" + (text3 ?? "-") + "] ZDO[" + (text2 ?? "-") + "] 板车[" + text5 + "]"; } private static string PrefabOfItem(ItemData item) { if (item == null) { return null; } try { if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } } catch { } return null; } } [HarmonyPatch(typeof(Container), "Awake")] internal static class Container_Awake_Patch { [HarmonyPostfix] private static void Postfix(Container __instance) { ContainerRegistry.Add(__instance); } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class Container_OnDestroyed_Patch { [HarmonyPrefix] private static void Prefix(Container __instance) { ContainerRegistry.Remove(__instance); } } [HarmonyPatch(typeof(Vagon), "Awake")] internal static class Vagon_Awake_Patch { [HarmonyPostfix] private static void Postfix(Vagon __instance) { CartRegistry.Add(__instance); } } [HarmonyPatch(typeof(Vagon), "OnDestroy")] internal static class Vagon_OnDestroy_Patch { [HarmonyPrefix] private static void Prefix(Vagon __instance) { CartRegistry.Remove(__instance); } } [HarmonyPatch(typeof(Smelter), "OnAddOre")] internal static class Smelter_OnAddOre_Patch { [HarmonyPrefix] private static bool Prefix(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { if ((Object)(object)__instance == (Object)null || (Object)(object)user == (Object)null) { return true; } if (!EasyCraftPlugin.IsActive) { return true; } if (!(user is Player)) { return true; } FillOutcome fillOutcome = Engine.FillOre(__instance, user, item); if (fillOutcome == FillOutcome.Fallback) { return true; } __result = fillOutcome == FillOutcome.Filled; return false; } } [HarmonyPatch(typeof(Smelter), "OnAddFuel")] internal static class Smelter_OnAddFuel_Patch { [HarmonyPrefix] private static bool Prefix(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { if ((Object)(object)__instance == (Object)null || (Object)(object)user == (Object)null) { return true; } if (!EasyCraftPlugin.IsActive) { return true; } if (!(user is Player)) { return true; } FillOutcome fillOutcome = Engine.FillFuel(__instance, user, item); if (fillOutcome == FillOutcome.Fallback) { return true; } __result = fillOutcome == FillOutcome.Filled; return false; } } [HarmonyPatch(typeof(Fireplace), "Interact")] internal static class Fireplace_Interact_Patch { [HarmonyPrefix] private static bool Prefix(Fireplace __instance, Humanoid user, bool hold, bool alt, ref bool __result) { if ((Object)(object)__instance == (Object)null || (Object)(object)user == (Object)null) { return true; } if (!EasyCraftPlugin.IsActive) { return true; } if (!(user is Player)) { return true; } GameObject gameObject = ((Component)__instance).gameObject; FireSources.Probe(__instance); bool flag = FireSources.IsOneAtATime(gameObject); if (EasyCraftPlugin.CfgBatchSize.Value <= 1 && !flag) { FireSources.ReportBail(gameObject, "BatchSize <= 1"); return true; } if (!EasyCraftPlugin.CfgFireEnabled.Value) { FireSources.ReportBail(gameObject, "FireSources.Enabled = false"); return true; } if (!FireSources.IsAllowed(gameObject) && !flag) { FireSources.ReportBail(gameObject, "被 Exclude 排除,或不在 Sources 白名单里"); return true; } if (!__instance.m_canRefill) { FireSources.ReportBail(gameObject, "m_canRefill = false(原版也不允许往里加燃料)"); return true; } if (__instance.m_infiniteFuel) { FireSources.ReportBail(gameObject, "m_infiniteFuel = true(这个火不需要燃料)"); return true; } if (EasyCraftPlugin.CfgFireRespectToggle.Value && __instance.m_canTurnOff && !hold && !alt && Engine.GetFireFuel(__instance) > 0f) { FireSources.ReportBail(gameObject, "点按 = 原版开/关火(m_canTurnOff=true 且燃料>0;要批量加料请把 RespectToggle 设成 false)"); return true; } FillOutcome fillOutcome = Engine.FillFireplace(__instance, user, hold, flag ? 1 : 0); if (fillOutcome == FillOutcome.Fallback) { return true; } __result = fillOutcome == FillOutcome.Filled; return false; } } internal static class StationOreFilter { internal struct Option { internal string SharedName; internal string Prefab; } internal const string DefaultSpec = "charcoal_kiln=$item_wood"; private static Dictionary<string, List<string>> _rules = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> Logged = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static string EffectiveSpec { get; private set; } internal static int StationCount => _rules.Count; internal static void Reload(string spec) { EffectiveSpec = spec ?? string.Empty; Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrEmpty(spec)) { string[] array = spec.Split(new char[1] { ';' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf('='); if (num <= 0) { EasyCraftPlugin.Log.LogWarning((object)("站点进料限制:这一组缺少 '=',已忽略 → " + text)); continue; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Length == 0) { continue; } List<string> list = new List<string>(); if (text3.Length > 0) { string[] array2 = text3.Split(new char[1] { ',' }); for (int j = 0; j < array2.Length; j++) { string text4 = array2[j].Trim(); if (text4.Length > 0) { list.Add(text4); } } } dictionary[text2] = list; } } _rules = dictionary; Logged.Clear(); } internal static string[] Apply(GameObject stationGo, List<Option> options) { List<string> list = new List<string>(); if (options != null) { foreach (Option option in options) { if (!string.IsNullOrEmpty(option.SharedName) && !list.Contains(option.SharedName)) { list.Add(option.SharedName); } } } List<string> list2 = FindRule(stationGo); if (list2 == null || list2.Count == 0 || list.Count == 0) { return list.ToArray(); } List<string> list3 = new List<string>(); foreach (Option option2 in options) { if (!string.IsNullOrEmpty(option2.SharedName) && !list3.Contains(option2.SharedName) && Matches(list2, option2)) { list3.Add(option2.SharedName); } } string text = StationName(stationGo); if (list3.Count == 0) { if (Logged.Add(text + "|nomatch")) { EasyCraftPlugin.Log.LogWarning((object)string.Format("站点进料限制:{0} 的规则 [{1}] 与该站点可用材料对不上,本次**未做限制**(会按原版装填)。\n 该站点实际可用材料:{2}\n 请把上面任一个名字($item_xxx / item_xxx / 预制体名 都认)填进 1 - Batch Fill → StationOreAllow", text, string.Join(",", list2.ToArray()), string.Join(",", list.ToArray()))); } return list.ToArray(); } if (Logged.Add(text)) { EasyCraftPlugin.Log.LogInfo((object)string.Format("站点进料限制生效:{0} 只允许装填 [{1}](该站点可用共 {2} 种:{3})", text, string.Join(",", list3.ToArray()), list.Count, string.Join(",", list.ToArray()))); } return list3.ToArray(); } private static List<string> FindRule(GameObject go) { if ((Object)(object)go == (Object)null || _rules.Count == 0) { return null; } Transform val = go.transform; int num = 0; while ((Object)(object)val != (Object)null && num++ < 8) { string text = FireSources.PrefabName(((Component)val).gameObject); if (!string.IsNullOrEmpty(text)) { if (_rules.TryGetValue(text, out var value)) { return value; } if (_rules.TryGetValue("piece_" + text, out value)) { return value; } string text2 = Normalize(text); if (text2 != null) { foreach (KeyValuePair<string, List<string>> rule in _rules) { if (string.Equals(Normalize(rule.Key), text2, StringComparison.Ordinal)) { return rule.Value; } } } } val = val.parent; } return null; } private static string Normalize(string name) { if (string.IsNullOrEmpty(name)) { return null; } string text = name.Trim().ToLowerInvariant(); if (text.StartsWith("piece_", StringComparison.Ordinal)) { text = text.Substring(6); } return text.Replace("_", string.Empty); } private static string StationName(GameObject go) { string text = FireSources.BestPrefabName(go); if (!string.IsNullOrEmpty(text)) { return text; } return "?"; } private static bool Matches(List<string> rule, Option o) { string text = o.SharedName ?? string.Empty; string b = (text.StartsWith("$", StringComparison.Ordinal) ? text.Substring(1) : text); string text2 = o.Prefab ?? string.Empty; foreach (string item in rule) { if (item.Length != 0) { string a = (item.StartsWith("$", StringComparison.Ordinal) ? item.Substring(1) : item); if (string.Equals(item, text, StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase)) { return true; } if (text2.Length > 0 && string.Equals(item, text2, StringComparison.OrdinalIgnoreCase)) { return true; } if (text2.Length > 0 && string.Equals(a, text2, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } } }