在以前的tML版本里,如果生成一个实体,很难去区分它的生成源。比如要判断由玩家使用物品发射的射弹,使其多生成两个,但又不想要其他射弹也有这个效果,这是很难做到的。因为敌怪生成射弹、玩家使用物品生成射弹、机关生成射弹等等都是共用一个 Projectile.NewProjectile
的方法,做不到区分这个射弹到底是如何生成的。
而1.4tML提供了一个新东西—— IEntitySource
专门用于记录实体的生成源,你可以通过它来判断该实体由什么生成、因什么生成,以便你添加修改内容。你现在可以区分实体的生成源,不用怕“殃及”其他的实体生成。 比如如果你想给所有Boss生成的小怪上个100倍血量、或者你想给火把神的射弹分裂出来好几个做弹幕地狱,都可以通过 IEntitySource
简单地完成,而如果Mod作者正确地设置了 IEntitySource
,这些修改也可以应用到其它Mod中。
本篇文章就简要说明一下这玩意怎么用。另外我建议好好地用,别填个 null
就不管了(
(注意tML1.4经常改这玩意,本文章最后更新于2022/6/3,如果有过期内容请联系我(群里Cyril)更改)
用法
这里列出了各种场景下需要 IEntitySource
的地方应该怎么填写。如果你使用的重写函数中已经包括一个 IEntitySource
参数,一般情况下都是直接使用那个参数
如果你有一定的原版源码查询基础,你可以在1.4tML源码中的 Entity.TML.Sources.cs 文件找到几乎所有 IEntitySource
,可供你自行选择对应的来填参数(登不上Github的话可以拉到本文最底部看该文件源码)
注:基本上每个 IEntitySource
都包含一个 context
参数,是选填的,如果你有附加信息的话可以填进去,不然留着就好。
通用
这些 IEntitySource
一般对于原版 Entity 实例中的两种或以上(即NPC/Player/Projectile/Item等)都通用
注:根据实际情况请将文章里写的 entity
换成对应的 npc/player/projectile
- [NPC/Projectile] 在AI执行中生成的(比如怪物射射弹、Boss生成小弟)使用
entity.GetSource_FromAI()
- [NPC/Player] 因实体死亡生成的(比如死亡掉钱)使用
entity.GetSource_Death()
- [NPC/Player] 受伤害生成的(比如日耀套护盾的受伤爆炸效果)使用
entity.GetSource_OnHurt()
- [NPC/Player/Projectile] 击中其他实体生成的(比如幽灵吸血套吸血射弹)使用
entity.GetSource_OnHit()
- [NPC/Item] 因摇树生成的,使用
new EntitySource_ShakeTree(树顶物块X坐标, 树顶物块Y坐标)
- [Projectile/Item] 由火把神事件生成的(事件完成奖励、事件中火把生成射弹,使用
new EntitySource_TorchGod(目标实体实例, 具体信息)
,这个具体信息最好填一下,生成物品用"TorchGod_FavorLoot"
,生成射弹用"TorchGod_Projectile"
- [NPC/Item/Projectile] 因通电生成的(比如雕像通电)使用
Wiring.GetXXXSource()
,XXX根据你生成实体的类型自填 - [NPC/Item/Projectile] 由特定事件生成的(比如生成四柱、在地牢门口生成邪教徒)使用
new EntitySource_WorldEvent()
- [NPC/Item/Projectile] 钓鱼钓出来的(原版是钓物品和钓怪物,你想的话也可钓射弹)使用
new EntitySource_FishedOut()
- 实体变成物品的(比如木箭有几率掉落成物品形式回收),使用
entity.GetSource_DropAsItem()
- 其他由实体本体生成的(比如星尘套的星尘守卫、悠悠球手套的额外掷球)使用
entity.GetSource_FromThis()
或者Entity.InheritSource()
都行,二者基本上是等效的。注意后者是Entity类中的静态(static)方法 - 因联机同步而生成的,使用
new EntitySource_Sync()
- 使用指令生成的,使用
new EntitySource_DebugCommand()
- [备选方案] 感觉哪种都不合适的,使用
entity.GetSource_Misc(自填字符串类型来源)
,建议参考一下源码中的各类SourceID。如无必要请别选择这一项 - [极其不推荐] 如果你实在想不出来了或者要摆烂,直接填
null
或者Entity.GetSource_None()
即可。不过我建议你用entity.GetSource_Misc(自填字符串类型来源)
玩家
- 由开袋子类物品(宝藏袋)生成的,使用
player.GetSource_OpenItem(物品ID)
- 在
ModItem.Shoot
方法中由物品射击生成的,使用方法提供的source
字段。或者是player.GetSource_ItemUse_WithPotentialAmmo(发射物品ID, 使用子弹物品ID)
,注意这里虽然写着WithPotentialAmmo
,但是实际上不管这个武器需不需要弹药,只要是在Shoot
里生成的(使用物品射出射弹)用的都是这个Source,只不过不需要弹药的话使用子弹物品ID
为0 - 非射击但因物品使用生成的(如镰刀割草生成干草块、用水桶返还空桶)使用
player.GetSource_ItemUse(被使用物品的实例)
- 装备了某样饰品而生成的(比如孢子囊、星星斗篷)使用
player.GetSource_Accessory(饰品的实例)
- 因玩家拥有Buff而生成的(宠物、坐骑)使用
player.GetSource_Buff(Buff在数组中的索引)
- 玩家放出某个NPC生成的(比如使用小动物物品)使用
player.GetSource_ReleaseEntity()
- 玩家抓住某个NPC生成的(比如虫网抓小动物生成小动物物品)使用
player.GetSource_CatchEntity()
- 玩家与物块互动生成的(比如钥匙召唤大宝箱怪、筛矿)使用
player.GetSource_TileInteraction(物块X坐标, 物块Y坐标)
- 玩家坐骑生成的(比如机械矿车打怪)使用
new EntitySource_Mount(坐在坐骑上的玩家实例, 坐骑ID)
- 玩家召唤Boss生成NPC,使用
NPC.GetBossSpawnSource(玩家索引即player.whoAmI)
NPC
- NPC送给你的(比如渔夫任务、染料商兑换染料)使用
npc.GetSource_GiftOrReward()
- NPC掉落的,使用
npc.GetSource_Loot()
(注意1.4应尽量使用ExampleMod中展示的重置后掉落系统来修改掉落物,以将其显示在图鉴或其他地方,而不是自己写生成物品) - 自然生成出来的一般NPC,使用
Entity.GetSource_NaturalSpawn()
- 自然生成出来的城镇NPC,使用
Entity.GetSource_TownSpawn()
- 旧日军团生成出来的NPC(一般由旧日传送门生成)使用
new EntitySource_OldOnesArmy()
- 随世界生成一并生成的(比如开局向导)使用
new EntitySource_WorldGen()
- 你如果真不知道该填啥但还是生成了NPC,使用
new EntitySource_SpawnNPC()
(建议查询其他部分找到合适的Source) - 因1.4新增复仇系统而生成的(即敌怪捡走一定量钱后不会永久消失,而是会显示在小地图上,等玩家走进后再次生成),使用
new EntitySource_RevengeSystem()
- 小电影(制作人员名单)生成的NPC,使用
new EntitySource_Film()
Tile
在Player中提到的 eneity.GetSource_TileInteraction(物块X坐标, 物块Y坐标) 也可以给其他实体使用,如果你那实体会和物块互动的话
- 破坏物块生成的(物块掉落成物品形式)使用
new EntitySource_TileBreak(物块X坐标, 物块Y坐标)
- 由TileEntity生成的(比如傀儡NPC,就那个DPS测试器)使用
new EntitySource_TileEntity(TE实例)
- 由物块的随机更新生成的,使用
new EntitySource_TileUpdate(物块X坐标, 物块Y坐标)
,原版里暂时没有用到过这个
修改例
①修改Boss小弟血量
首先,让我们来实现我开头提出的 Boss生成的小怪100倍血量 功能吧
所以在我们的 GlobalNPC
类中重写 OnSpawn
函数:
public override void OnSpawn(NPC npc, IEntitySource source) { }
想想我们判断该NPC属于Boss小弟的条件,首先它的 IEntitySource
得是根据 GetSource_FromAI()
这个方法得来的,其次我们得判定生成它的 Entity
得是 NPC
实例并且属于Boss(即 npc.boss
为 true
)
查询源码后,不难发现 GetSource_FromAI()
方法返回的是一个 EntitySource_Parent
类(源码全文我会在最后给出)
public IEntitySource GetSource_FromAI(string? context = null) => new EntitySource_Parent(this, context);
条件充足了,接下来在我们的 OnSpawn
重写函数中写上:
public override void OnSpawn(NPC npc, IEntitySource source) { // 如果这个source属于EntitySource_Parent if (source is EntitySource_Parent) { // 在这里先创建好一个EntitySource_Parent实例,方便后面调用 EntitySource_Parent parentSource = source as EntitySource_Parent; // 如果生成这个source的是一个NPC // 而且这个NPC是一个Boss // parentSource.Entity as NPC 用于获取这个NPC,这样我们才能用boss这个字段 if (parentSource.Entity is NPC && (parentSource.Entity as NPC).boss) { // 将该NPC(即被生成出来的这个小弟)的血量和最大血量均改为原来的100倍 npc.lifeMax *= 100; npc.life *= 100; } } }
注:AAA is BBB
用于判断这个类是否是另一个类,如果能确定是,则可以用 AAA as BBB
获取它的实例
在原版中,NPC/Projectile/Player/Item
等均属于 Entity
类
那么让我们进游戏看看效果吧!
相比之下,克脑就显得弱势很多了
然而由于目前还不太完善,会出现一些Bug,比如作为Boss一部分的火星飞碟部件也会受到这个增益:
这点是因为并没有具体区分GetSource_FromAI()
和其他方法的Source,而一并全用了 EntitySource_Parent
类 。因此无法区分到底是不是在战斗过程中生成的,这点只能等tML完善了。目前1.4tML还在测试版,正式版出来后应该没有这种问题
②玩家射击生成多个射弹
改不了小弟,我还不能改别的?试试开头说到的让玩家射击多生成两个射弹好了
因为生成的是射弹,这回用到的就是 GlobalPojectile
了,依然是 OnSpawn
重写函数
public override void OnSpawn(Projectile projectile, IEntitySource source) { base.OnSpawn(projectile, source); }
在上方查阅并结合源码,可以看到使用物品射出射弹用的是 EntitySource_ItemUse_WithAmmo
(没有源码的话,善用VS补全)
public IEntitySource GetSource_ItemUse_WithPotentialAmmo(Item item, int ammoItemId, string? context = null) => new EntitySource_ItemUse_WithAmmo(this, item, ammoItemId, context);
我们希望在玩家发射射弹的时候,另外生成两个角度不同的相同射弹,并且使这两个射弹的速度更慢,于是可以这么写:
public override void OnSpawn(Projectile projectile, IEntitySource source) { // 如果这个射弹是由物品发射生成 if (source is EntitySource_ItemUse_WithAmmo) { // 这里切记不要用重写函数提供的source,而应当用另一个 // 如果用回source,那么新生成的射弹的生成源类型也是EntitySource_ItemUse_WithAmmo,会再次生成两个射弹 // 无限循环,然后游戏就快乐地崩了 var newSource = projectile.GetSource_FromThis(); for (int i = -1; i <= 1; i += 2) { // 旋转8°并将速度设为80%,然后生成射弹 var velocity = projectile.velocity.RotatedBy(MathHelper.ToRadians(8f * i)) * 0.8f; Projectile.NewProjectile(newSource, projectile.position, velocity, projectile.type, projectile.damage, projectile.knockBack, projectile.owner); } } }
效果:
还不够?再多来点
如你所见,对于近战武器这类不需要弹药的武器也是有用的
当然对鞭子和矛这些也有用,这点需要特殊判断了,或者可以期待一下以后tML继续细分
还有更多使用方法,这玩意出来之后虽然从1.3转过去要改很多地方,但是是真的方便好用
小作业
一般来说某种东西加上“小”就会看起来可爱点,然而这并不适用于作业(
写这篇的时候从群里看到的,那就留个十分简单的作业:
如果敌怪带灵液Debuff,则给它发射的所有射弹都附加一个对玩家造成同样秒数的灵液Debuff的效果
源码参考
其实tModLoader有在Github开源,这个就是,如果Github连不上你也可以试试这个镜像链接
查阅一下 Entity
类里面每个 GetSource
方法对应的实例,来自Entity.TML.Sources.cs(2022/6/3)
// Common public IEntitySource GetSource_FromThis(string? context = null) => new EntitySource_Parent(this, context); public IEntitySource GetSource_FromAI(string? context = null) => new EntitySource_Parent(this, context); public IEntitySource GetSource_DropAsItem(string? context = null) => new EntitySource_DropAsItem(this, context); public IEntitySource GetSource_Loot(string? context = null) => new EntitySource_Loot(this, context); public IEntitySource GetSource_GiftOrReward(string? context = null) => new EntitySource_Gift(this, context); // Item Use / Equipment Effects public IEntitySource GetSource_Accessory(Item item, string? context = null) => new EntitySource_ItemUse(this, item, context); public IEntitySource GetSource_OpenItem(int itemType, string? context = null) => new EntitySource_ItemOpen(this, itemType, context); public IEntitySource GetSource_ItemUse(Item item, string? context = null) => new EntitySource_ItemUse(this, item, context); public IEntitySource GetSource_ItemUse_WithPotentialAmmo(Item item, int ammoItemId, string? context = null) => new EntitySource_ItemUse_WithAmmo(this, item, ammoItemId, context); // Damage / Death public IEntitySource GetSource_OnHit(Entity victim, string? context = null) => new EntitySource_OnHit(this, victim, context); public IEntitySource GetSource_OnHurt(Entity attacker, string? context = null) => new EntitySource_OnHit(attacker, this, context); public IEntitySource GetSource_Death(string? context = null) => new EntitySource_Death(this, context); // Etc public IEntitySource GetSource_Misc(string context) => new EntitySource_Misc(/*this,*/ context); public IEntitySource GetSource_TileInteraction(int tileCoordsX, int tileCoordsY, string? context = null) => new EntitySource_TileInteraction(this, tileCoordsX, tileCoordsY, context); public IEntitySource GetSource_ReleaseEntity(string? context = null) => new EntitySource_Parent(this, context); public IEntitySource GetSource_CatchEntity(string? context = null) => new EntitySource_Parent(this, context); // Common - Static public static IEntitySource? GetSource_None() => null; public static IEntitySource? InheritSource(Entity entity) => entity != null ? entity.GetSource_FromThis() : GetSource_None(); // Spawning - Static public static IEntitySource GetSource_NaturalSpawn() => new EntitySource_SpawnNPC(); public static IEntitySource GetSource_TownSpawn() => new EntitySource_SpawnNPC();
好!
Pingback: 基础物品 — 武器和工具 - 裙中世界
Pingback: Global系列-全局操作 - 裙中世界