跳至正文

Global系列-全局操作

小案例

Global系列的教程咕了114514191810天终于是开始写了(x

虽然但是,这些东西用起来并没有你们想象中的那么难,只要记住执行顺序是位于对应钩子之后,并且多个Global系列会一起生效(嗯我的意思是不要全写一个类里面)。

想当年1.3的时代,堆叠上限还是999,这真是让人非常不爽啊,没有四位数难受死了——

常规开局,新建一个类,继承 GlobalItem

还记得物品属性是在哪里设置的吗?Yes,就是内个被用烂的 SetDefaults,那么找到它。

注意到了吗,相较于继承了 ModItemSetDefaults ,这里的钩子给了一个 Item 实例。拿到这个,咱们就可以开始进行判定了。

public override void SetDefaults(Item entity)
{
    if (entity.maxStack == 9999)
    {
        // 四位数也满足不了了!
        entity.maxStack = 99999;
    }
}
好耶,堆叠上限变成99999了!

当然,可不要不进行判定就直接写 entity.maxStack = 99999,不然导致武器饰品之类的东西也可以堆叠就太怪了(x)

我们还可以改改别的属性,比如伤害和暴击率。

public override void SetDefaults(Item entity)
{
    /*if (entity.maxStack == 9999)
    {
        entity.maxStack = 99999;
    }*/
    // 使用type来判定物品的类型,此处判定为天顶剑
    if (entity.type == ItemID.Zenith)
    {
        // 物品伤害翻100倍
        entity.damage *= 100;
        // 物品百爆
        entity.crit = 100;
    }
}
超模头子,小子!

啊哈,搁这离谱()你还可以让所有“武器”伤害都翻一百倍。

public override void SetDefaults(Item entity)
{
    /*if (entity.maxStack == 9999)
    {
        entity.maxStack = 99999;
    }
    if (entity.type == ItemID.Zenith)
    {
        entity.damage *= 100;
        entity.crit = 100;
    }*/
    // 判定:物品有伤害 且 不是饰品 且 不是弹药
    if (entity.damage > 0 && !entity.accessory && entity.ammo == AmmoID.None)
    {
        entity.damage *= 100;
    }
}
离谱的小曲儿

乐,不过这样子写判定会把工具(镐斧锤)也判进去,怎么让它们不被判到就交给你们了。


Ok,以上是一个对物品设置基础属性进行全局修改的例子,你感受到Global系列的Power了吗?


Global系列有很多,本篇只介绍4个常用的。正常来说相当够用辣~

Buff,物品,NPC,弹幕,都是老熟人了。

GlobalBuff

对于buff来说,常见的情况是,用这个来给原版buff添加额外的效果。

还记得吗,buff的更新。(忘了就给我去复习自定义Buff教程>>>

和先前修改物品的基本属性一样,我们只要对type进行判定,让你的目标buff获得额外的效果即可。

public override void Update(int type, Player player, ref int buffIndex)
{
    // 如果这个正在生效的buff是黑曜石肤 
    if (type == BuffID.ObsidianSkin)
    {
        // 给玩家增加10防御和10%减伤
        player.statDefense += 10;
        player.endurance += 0.1f;
    }
}

不过这样的话,咱们就得把buff描述也改一改,不然没有属性面板的话玩家都不知道还有额外效果。

public override void ModifyBuffText(int type, ref string buffName, ref string tip, ref int rare)
{
    // 换行,加入新的描述,本地化文件自己写嗷
    tip += "\n" + Language.GetTextValue($"Mods.{Mod.Name}.MyBuffs.ObsidianSkin");
}
芝士哪里bug了?

看看,上边忘记什么了?

在修改描述的时候没有判定buff类型

if (type == BuffID.ObsidianSkin)

然后你还可以重写 ReApply 来让所有Buff的时间都可以被叠加,这里不再演示。


GlobalItem

基础

yysy我觉得到这里你们应该该用的都会用了,还有必要再讲吗(

来点恶心玩家的东西吧——

public override bool CanUseItem(Item item, Player player)
{
    // 玩家没有鱼鳃效果 且 在水里 就 不能使用物品
    if (!player.HasBuff(BuffID.Gills) && player.wet)
    {
        return false;
    }
    // 玩家没有黑曜石肤 且 在岩浆里游泳 就 不能使用物品
    if (!player.HasBuff(BuffID.ObsidianSkin) && player.lavaWet)
    {
        return false;
    }
    // 其它情况进行物品的正常使用判定
    return true;
}

或是让你在干活(迫真)的时候无视小怪的攻击——

// 这个函数是当物品处于选中状态的时候执行的(快捷栏选中或者被抓在鼠标上)
public override void HoldItem(Item item, Player player)
{
    // 如果玩家当前选中物品是工具(拥有 镐力/斧力/锤力)
    if (item.axe > 0 || item.pick > 0 || item.hammer > 0)
    {
        // 就增加1000防御力
        player.statDefense += 1000;
    }
}
注意被选中的物品防御力变化

还可以这么玩——

// 这个函数是物品在背包中就会执行的
public override void UpdateInventory(Item item, Player player)
{
    // 如果这个物品是材料,就给玩家提供1%全伤害
    if (item.material)
    {
        player.GetDamage(DamageClass.Generic) += 0.01f;
    }
}
看数据面板的变动

或者更加离谱一些,让堆叠也作为加成——

// 这个函数是物品在背包中就会执行的
public override void UpdateInventory(Item item, Player player)
{
    // 如果这个物品是材料,那么每一份都会给玩家提供1%全伤害
    if (item.material)
    {
        player.GetDamage(DamageClass.Generic) += item.stack * 0.01f;
    }
}
你滴数值超框辣

进阶——带有冷却的附加效果

下面来介绍一个进阶用法。在此之前你们也许得了解一下计时器的概念>>>

我们来制作一个,让武器打到NPC后,对NPC额外造成一次十倍伤害(冷却10s)的效果。

第一步,声明一个变量作为计时器:(推荐把声明写在开头,以防你的变量声明散得到处都是)

public class MyItems : GlobalItem
{
    public int EffectCD;
    //...其他内容
}

第二步,写效果:(注意这是物品,物品,对射出的弹幕没用!)

public override void OnHitNPC(Item item, Player player, NPC target, NPC.HitInfo hit, int damageDone)
{
    // 打到NPC时,不在冷却就发动效果并进入冷却(10s)
    if (EffectCD == 0)
    {
        target.SimpleStrikeNPC(damageDone * 10, 0, hit.Crit);
        EffectCD = 600;
    }
}

第三步,给冷却写更新:

这里使用 HoldItem,只有物品处于选中状态才会进行冷却更新。

public override void HoldItem(Item item, Player player)
{
    if (EffectCD > 0)
    {
        EffectCD--;
    }
}

OK,生成并重载Mod,进游戏康康!

——那么不出意外的话就要出意外了(

遇到报错不要慌,来看看它说了什么

报错的翻译
InstancePerEntity的注释

这下明白了,原因是我们声明的实例字段,也就是那个用于做CD的计时器。但标记为静态字段意味着所有武器共享这个CD,如果我们想每个物品都是独立的CD,那么按照提示——

public class MyItems : GlobalItem
{
    public int EffectCD;
    public override bool InstancePerEntity => true;
    //...其他内容
}

这下没问题了。来康康效果吧。

视频中的冷却是3秒

Cool!你学会了吗?


GlobalProjectile

基础

不知道大家有没有在天顶世界被坠星砸死过?

public class MyProj : GlobalProjectile
{
    public override void SetDefaults(Projectile entity)
    {
        if (entity.type == ProjectileID.FallingStar)
        {
            entity.hostile = true;
        }
    }
}

只要这么做就能让你在正常世界也会被坠星砸死(

我们可以让所有弹幕在特定情况下不造成伤害。

// 这个函数控制弹幕是否可以对目标造成伤害
public override bool? CanHitNPC(Projectile projectile, NPC target)
{
    // 如果弹幕的击退力很高(20就是满值了),强制允许攻击
    if (projectile.knockBack >= 15)
    {
        return true;
    }
    // 非魔法弹幕不可以攻击处于水中的NPC
    if (projectile.DamageType != DamageClass.Magic && target.wet)
    {
        return false;
    }
    // 箭矢类弹幕不可攻击静止不动的NPC
    if (projectile.arrow && target.velocity.Length() == 0)
    {
        return false;
    }
    // 其他情况 return null 进行正常判定
    return null;
}

这是针对NPC,同类型的钩子还有几个:

CanCutTiles 是说可否摧毁易碎物块,比如地上的草、腐地血地的荆棘、蜘蛛网、罐子等。

CanDamage 是这一类的总控,包括摧毁易碎物块和对任何实体造成伤害(不论玩家和NPC)。

CanHitPlayer 就是可否攻击玩家(非Pvp),再下一个就是特指Pvp启动时的判定。

再来试试修改所有弹幕的碰撞箱:

// 这个函数用于修改弹幕的碰撞箱
public override bool? Colliding(Projectile projectile, Rectangle projHitbox, Rectangle targetHitbox)
{
    // 这个是默认的碰撞判定
    // 当弹幕的碰撞箱 与 目标碰撞箱 相交,视为碰撞
    /*if (projHitbox.Intersects(targetHitbox))
    {
        return true;
    }*/
    // 当 目标碰撞箱的中心 距离弹幕中心 不超过十个物块的距离(一个16像素)
    // 即目标处在距离弹幕半径160的圆内,就视为碰撞了
    if (targetHitbox.Center().Distance(projectile.Center) <= 160)
    {
        return true;
    }
    // 其他情况进行默认判定
    return null;
}
太过离谱()

啊这可不是圆形碰撞箱哈,不要弄混了。这是两点距离,但是圆形碰撞箱是圆形与矩形相交。

进阶——生成源的花活

我相信你们等这个很久了()

生成源是1.4新加的东西,顾名思义这就是实体生成的来源。通过对来源的特判,我们就可以让特定弹幕执行额外效果。

不过生成源只能在 OnSpawn 这个函数里拿到,所以我们得拿一个变量来存储你的判定。

也就是说,这里也要让 InstancePerEntity 的返回值为 true

这里就做一个,让所有消耗了弹药才射出来的弹幕,一路火花带闪电,并且死亡时会炸出3-5个魔晶弹幕。

重写 OnSpawn 函数,这里的参数 source 就是弹幕的生成源,接下来就使用这个东西来进行判定。

这里就非常需要用补全了!输入entitysource,看看这里都有什么牛马。

我靠,这么多

嗬,小问题,把这个看一遍就完事了。生成源介绍与用法>>>

然后你应该会发现这些:

那么来试着找找吧——

注意不要使用On或IL前缀的

很好,想必就是这个类型了,那么用模式匹配把提供的生成源转过去吧。

可以猜测一下,这个源里面应该有记录了使用的弹药的属性,毕竟它是一个使用了弹药的源类型。

看样子就是这个,翻译一下这串英文就是被使用的弹药的物品ID

那么根据先前生成源用法的介绍,我们只需要判定这个属性是大于0即可,这意味着这个弹幕生成使用了某种弹药。

public override void OnSpawn(Projectile projectile, IEntitySource source)
{
    // 判定 这个弹幕的生成是因为 消耗了任意弹药进行发射
    if (source is EntitySource_ItemUse_WithAmmo use && use.AmmoItemIdUsed > 0)
    {
        // 将标记设为true
        usedAmmo = true;
    }
}

接着只要判定这个标记的值来启动效果:

public override void AI(Projectile projectile)
{
    if (usedAmmo)
    {
        Dust.NewDustDirect(projectile.Center, projectile.width, projectile.height, DustID.WitherLightning);
    }
}
public override void Kill(Projectile projectile, int timeLeft)
{
    if (usedAmmo)
    {
        for (int i = 0; i < Main.rand.Next(3, 6); i++)
        {
            // 生成源是 弹幕死亡
            Projectile.NewProjectile(projectile.GetSource_Death(), projectile.Center,
                Vector2.UnitX.RotatedByRandom(MathHelper.TwoPi) * 10f, ProjectileID.CrystalStorm,
                (int)(projectile.damage / 5f), 2, projectile.owner);
        }
    }
}
泰裤辣

GlobalNPC

经过前几部分的展示,常规函数的用法你们应该都明白了,所以这里就介绍一些 GlobalNPC 独有的函数。

修改自然刷怪

有三个与NPC生成相关的函数。

下面就来详细介绍一下。

第一个 EditSpawnPool,这个函数可以用来修改可以生成的NPC种类及生成权重。用断点可以看到生成池 pool 中有一个默认的类型为0权重为1的元素,那个是默认的原版自然刷怪,正常情况下你都不用理它,直接往池子里加入你想要的刷怪即可。给的第二个参数 spawnInfo 存储了一些生成信息,可用作是否将特定条目加入生成池的判定,它都提供了什么信息你们可以导航进那个结构体里面看一看。

权重不要写太大,这个平衡你们自己试试。一般来说 0.2f 就挺大了。

public override void EditSpawnPool(IDictionary<int, float> pool, NPCSpawnInfo spawnInfo)
{
    // 当目标玩家在海洋环境
    if (spawnInfo.Player.ZoneBeach)
    {
        // 往生成池加入很多哥布林侦察兵
        pool.Add(NPCID.GoblinScout, 1f);
    }
}
听说你是稀有生物

第二个 EditSpawnRange,用于修改刷怪的最大范围和最小范围。除了用于判定的 player 参数外,剩下四个参数都带有 ref,直接修改即可。

/// <param name="player">player实例,用于判定特殊条件</param>
/// <param name="spawnRangeX">横向最大生成范围</param>
/// <param name="spawnRangeY">纵向最大</param>
/// <param name="safeRangeX">横向最小</param>
/// <param name="safeRangeY">纵向最小</param>
public override void EditSpawnRange(Player player, ref int spawnRangeX, ref int spawnRangeY,
    ref int safeRangeX, ref int safeRangeY)
{
            
}

第三个 EditSpawnRate,用于修改刷怪速率 spawnRate 和最大刷怪数量 maxSpawns。下边展示的是源码中几个buff对此做出的修改。

嗯哼,看起来这个 spawnRate 越大就越难刷怪捏。不过特别的,如果这个 spawnRate 被设为了0,那么就会完全压制刷怪。另外,注意他是一个 int 类型的值,可不要想极限刷怪然后设置到0了。

添加全局掉落

这个函数可以添加全局掉落,用法跟 ModifyNPCLoot 是一样的

修改商店

ModifyShop,一般用于修改原版NPC的商店。嗯,不过不推荐修改只推荐添加。

对于1.4.4版本来说,NPC商店经过一波大概更加规范了,来看看现在要怎么向商店添加商品。

他要的是 Entry 实例,让我们通过导航了解一下这个类型。

Entry的构造函数

嗯哼,很明白,一个物品,以及不限定数量的条件,这个所谓条件你们应该在添加合成表已经认识过了。

这里的条件参数是 params,意思是可以传入的数量不做限制,你既可以留空,也可以连着写(而不用写个数组,这是当这个 params 的参数是方法的最后一个参时可以用的方式)。

public override void ModifyShop(NPCShop shop)
{
    // 如果这个商店绑定的NPC类型是机械师 且 商店的索引是 Shop
    if (shop.NpcType == NPCID.Mechanic && shop.Name == "Shop")
    {
        // 实例化一个物品,这个是无尽箭袋
        Item shopItem = new(ItemID.EndlessQuiver)
        {
            // 设置特别的售卖价格,这个默认是null
            // 但如果这个属性被设置了就会使用这个特别设置的价格来售卖
            // 不设置即使用物品本身的价值
            //shopCustomPrice = Item.buyPrice(0, 10);
        };
        // 向商店添加物品,条件是 处于For The Worthy秘密种子地图 且 在血月期间
        shop.Add(new Entry(shopItem, Condition.ForTheWorthyWorld, Condition.BloodMoon));
    }
}

上边有一个叫商店名的索引,原版所有商店的这个索引都是 Shop。特别的,油漆工有第二个商店,它的索引是 Decor,你可以用商店查询Mod—— Shop Lookup 来查询索引,当然源码也可以不过自己找去。

油漆工的两个商店&索引

SetupTravelShop,用于修改旅商的商店,因为原版旅商写的依托答辩所以这还是1.4.3.6版本写法。

public override void SetupTravelShop(int[] shop, ref int nextSlot)
{
    //设置商品
    shop[nextSlot] = ItemID.StarCloak;
    //切换索引到下一栏,否则你的NPC只能卖一件物品
    nextSlot++;
    // 你也可以这么写
    //shop[nextSlot++] = ItemID.StarCloak;
}

噢,有个东西忘了说,当你有对应实例的时候,你可以用 实例.GetGlobalXXX<你的Global类>() 来获取对应的绑定Global实例。比如对于弹幕就是 Projectile实例.GetGlobalProjectile<MyProj>() (其中 MyProj 就是继承了 GlobalProjectile 的类)。拿到这个实例后,你可以访问你在其中声明的变量

本篇教程到此就结束啦,主要是让你们认识一下Global系列,以及常规的用法,其实都很简单。

更多骚操作可以自己多探索探索,不要局限在教程介绍的东西里,这样是不能得到提升的。

练习

基础

  1. 如何实现把原版战斗buff改成与极限战争buff(该buff效果为刷怪快到离谱,刷怪上限特别高)差不多的效果?答案
    // 写到GlobalNPC
    public override void EditSpawnRate(Player player, ref int spawnRate, ref int maxSpawns)
    {
    	if (player.HasBuff(BuffID.Battle))
    	{
    		spawnRate = 1;
    		maxSpawns = 1000;
    	}
    }
    
  2. 如何实现所有弹幕都变快一些?答案
    // 写到GlobalProjectile
    public override void SetDefaults(Projectile entity)
    {
    	entity.extraUpdates += 1;
    }
    
  3. 如何实现让玩家无法拾取坠星?答案
    // 写到GloBalItem
    public override bool CanPickup(Item item, Player player)
    {
    	return item.type != ItemID.FallenStar;
    }
    
  4. 如何实现在骷髅亡死亡时发生一点有趣的事情?答案
    // 写到GloBalNPC
    public override bool CheckDead(NPC npc)
    {
    	//杀死骷髅亡(超难的波斯)后发送消息
    	if (npc.type == NPCID.SkeletronHead)
    	{
    		Main.NewText("我很生气你能击败我。", new Color(0, 255, 255));
    		Main.NewText("毋庸置疑,你是一位真正的Cheater。", new Color(0, 255, 255));
    		Main.NewText("这样一来,我的使命就完蛋了。", new Color(0, 255, 255));
    		Main.NewText("愿EZ诅咒你。", new Color(0, 255, 255));
    	}
    	return base.CheckDead(npc);
    }
    // 它可能没什么用,但很有趣(笑)
    // 当然你也可以自己写点有趣的,比如杀死骷髅亡会永久提升坠星掉率或是会掉落300颗坠星
    

进阶

  1. 如何实现当玩家装备蜂巢背包时,该玩家所发射的巨型蜜蜂将会有33%几率对NPC造成中毒debuff,通过此种方法造成的中毒debuff掉血量会翻倍,且该NPC携带此中毒debuff时将百分百不掉落红心?
标签:

《Global系列-全局操作》有1个想法

  1. Pingback: Modifiers系列-修改受击伤害 - 裙中世界

发表回复