跳至正文

1.4 生成源信息IEntitySource的用法

在以前的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.bosstrue

查询源码后,不难发现 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一部分的火星飞碟部件也会受到这个增益:

WTF

这点是因为并没有具体区分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();

《1.4 生成源信息IEntitySource的用法》有3个想法

  1. Pingback: 基础物品 — 武器和工具 - 裙中世界

  2. Pingback: Global系列-全局操作 - 裙中世界

发表回复