生物生成限制器(SpawnLimiter)

基本信息

属性 值
类型 服务端事件拦截器(LivingSpawnEvent.CheckSpawn)
事件优先级 EventPriority.LOWEST(最后裁决)
触发条件 每个生物实体的生成尝试(CheckSpawn)
配置文件 <configDir>/AMDI/MobSpawnLimits.xml
配置类 eu.usrv.amdiforge.xml.spawnlimit.SpawnLimitsConfig
工厂类 SpawnLimitsConfigFactory
状态侧限制 仅服务端生效(FMLCommonHandler.instance().getEffectiveSide().isClient() 直接 return)

工作原理

LivingCheckSpawnEventHandler 注册到 MinecraftForge.EVENT_BUS,对每个 LivingSpawnEvent.CheckSpawn:

  1. 客户端事件直接 return。
  2. 非 EntityLiving 子类直接 return。
  3. 若已有 Result.DENY 直接 return(让其他拦截器先裁决)。
  4. 在已加载的 LimitedEntity 列表中按类名匹配实体(支持两种匹配模式,见下)。
  5. 若全局白名单命中 → 允许并加入 EntityCounter 计数。
  6. 否则在匹配实体的 SpawnLimitWorld 列表中查找当前维度:
    • 维度白名单命中 → 允许。
    • 否则对比当前实体数与 MaximumSpawnCount:
      • 已达上限 → DENY。
      • 未达上限 → 维持原 Result(既不强制允许也不强制拒绝,让其他拦截器决定)。
  7. 实体完全不在配置列表时:
    • TraceAndExportUnknownEntities = true → 自动写入配置并落盘。
    • 否则静默放行,由 Forge 原生规则裁决。

实体匹配规则

LimitedEntity 提供两种匹配模式,通过 ExactNameMatch 属性切换:

属性 模式 匹配方式
ExactNameMatch="true" 精确 类的 getCanonicalName() 完全相等(忽略大小写)。
ExactNameMatch="false"(缺省) 子串 类的 getCanonicalName() 包含配置的子串(忽略大小写)。例:配置 Skeleton 可命中 net.minecraft.entity.monster.EntitySkeleton、EntitySkeleton騎士 等所有 Skeleton 子串类。

注:源码注释「"Skeleton" matches all entities with Skeleton in their classname」。

XML 配置结构

根节点 <LimitedEntities>:

<LimitedEntities>
  <LimitedEntity EntityClassName="net.minecraft.entity.monster.EntityCaveSpider" ExactNameMatch="true" GlobalWhitelisted="false">
    <World DimensionID="0" MaximumSpawnCount="50" AlwaysAllowSpawn="false"/>
    <World DimensionID="-1" MaximumSpawnCount="20" AlwaysAllowSpawn="true"/>
  </LimitedEntity>
</LimitedEntities>
字段 必填 含义
EntityClassName 是 Java 类名(精确或子串)
ExactNameMatch 否 true=精确;缺省/其他值=子串
GlobalWhitelisted 否 true=所有维度强制生成,跳过 MaximumSpawnCount
World/@DimensionID 是 维度 ID(Overworld=0、Nether=-1、End=1)
World/@MaximumSpawnCount 否 维度内最大并发数量(-1=不限)
World/@AlwaysAllowSpawn 否 该维度白名单:忽略计数强制放行

初始配置

首次加载时若 MobSpawnLimits.xml 不存在,调用 InitSampleConfig() 写入种子配置:

  • EntityCaveSpider(精确匹配)→ 维度 0 一条无 AlwaysAllowSpawn、未设上限的规则。

随后 SaveSpawnLimits() 通过 JAXB 落盘。

实体计数(EntityCounter)

每次 EntityCounter 单例的定时任务(RunnableManager 每 10 秒触发一次)会遍历所有 WorldServer.loadedEntityList,按 (类名, 维度ID) 汇总当前在线数量;同时 LivingSpawnEvent 通过 trackSpawnEvent 在 Result.ALLOW 时递增一次计数(注意这是事件计数,与实际存活数存在偏差,仅用于 SpawnLimiter 的简单判断)。计数存储在内存中 _mEntities: List<EntityDef>,通过 ReentrantReadWriteLock 保护并发读写。

⚠️ 实现细节:run() 的循环中 tFoundWorld = true 在命中现有 WorldDef 后未重置,所以同一实体只会计入第一个 WorldDef。已知 Bug,对正确性影响有限。

配置项关联(AMDIConfig)

主配置项 默认 取值范围 作用
SpawnLimiter.TraceAndExportUnknownEntities false bool 是否将未见过的实体自动追加到 XML
SpawnLimiter.SpawnEventReportLevel 0 0-2 0=不报告;1=仅报告被拒绝;2=报告被拒绝+允许
UseMySQL true bool 是否启用 MySQL(关闭则跳过数据库初始化)

注:源码中存在对 DoDebugMessages 字段的引用,但 AMDIConfig 未声明该字段——读取时实际为 false,不会打印诊断日志。

配置加载失败保护

ReloadLootGroups() 抛异常 → 触发回退:

  • 警告日志 [SpawnLimits] Configuration File seems to be damaged, nothing will be loaded!
  • 通过 IngameErrorLog.AddErrorLogOnAdminJoin 把错误推送给下次登录的管理员
  • 调用 InitSampleConfig() 写入默认配置以保证系统可继续运行

第三方 mod 集成

  • 无直接外部 mod 集成。LivingCheckSpawnEventHandler 仅消费 Forge 自身 LivingSpawnEvent,不调用任何第三方 mod API。

相关条目