跳至正文

ModPlayer-自定义玩家属性

我看了一眼,原来旧教程有这个啊,那我不写了

经过Global系列的探索,你们应该也大概猜到 ModPlayer 是长什么样子得了,本篇教程就来详细介绍一下这个类里的常用函数,以及如何自定义玩家属性达到特殊目的(重点)。

那么就新建文件,让你的类继承 ModPlayer 吧。

常用函数

善用补全哦~

注:在继承了 ModPlayer 的类中,你可以像类似于 ModXXX 那样,直接用 Player 就能获取当前玩家实例。当然你在这里写静态方法的话那一样是不能用这个的。

修改玩家的初始物品

有两个函数, AddStartingItemsModifyStartingInventory,前者更早执行。

// 这个函数可以给玩家添加初始物品
// 这里给的参数是说玩家是否是中核难度
// 因为中核玩家死亡时物品栏会被清空然后重置为初始开局物品
public override IEnumerable<Item> AddStartingItems(bool mediumCoreDeath)
{
    // 返回一个集合,不管是list还是数组都可以
    return new[]
    {
        // 给玩家9999个木头作为初始物品
        new Item(ItemID.Wood, 9999),
        // 再附赠1个香蕉
        new Item(ItemID.Banana)
    };
}
// 这个函数用于修改玩家的初始物品,可以把原版自带的东西删掉
public override void ModifyStartingInventory(IReadOnlyDictionary<string, List<Item>> itemsByMod, bool mediumCoreDeath)
{
    // Terraria为原版的键,用它就能拿到原版提供的初始物品
    // 把原版提供的铜镐删掉
    itemsByMod["Terraria"].RemoveAll(item => item.type == ItemID.CopperPickaxe);
    // 把刚刚我们的Mod提供的木头也删掉(乐
    itemsByMod[Mod.Name].RemoveAll(item => item.type == ItemID.Wood);
    // 这里的键名即是Mod的内部名
}
你好,你的香蕉

当玩家进入世界

完完全全的翻译,它叫 OnEnterWorld

public override void OnEnterWorld()
{
    // 这个是随机传送药水的效果
    // 也就是当玩家进入世界的时候就会被扔到一个怪地方
    Player.TeleportationPotion();
}
开局即溺水

一大堆更新

下面按照更新先后顺序。可用性指是否推荐用于更新自定义属性及其加成。

函数名描述调用顺序备注可用性
PreUpdate预更新在所有东西更新之前禁止
ResetEffects重设效果最早的一项更新用于每帧重置自定义变量禁止
PreUpdate
Buffs
预更新Buff在玩家身上的Buff更新之前此时玩家的几乎所有
“某效果是否启动”的字段
都是False
不推荐
PostUpdateBuffsBuff更新后在玩家身上的Buff更新之后现在很多效果都已经启动了不推荐
Update
Equips
装备更新乱七八糟的装在身上装备
绑定的效果bool都启动之后
你可以在这里判定玩家是否
拥有某个效果
不推荐
PostUpdate
Equips
装备更新后装备的啥啥加成都计算好了之后原版该应用的正经加成都已经计算推荐
PostUpdate
MiscEffects
杂项效果
更新后
其他莫名其妙的加成更新反正够乱的,你的自定加成系统
放这里就也没关系了
推荐
Update
BadLifeRegen
更新负面
生命回复
已经计算好所有加成
开始更新生命值回复
这里才是正经的操作玩家
lifeRegen的地方,通过特判
来操作负面生命回复
针对
如果你的Mod
添加了类似于
Update
LifeRegen
更新正面
生命回复
先负再正很合理毕竟
负了的话这儿就不用管辣
同上,但是操作正面回复生命值/魔法值
这样的属性
Natural
LifeRegen
(ref float regen)
更新自然
生命回复
正面的也算好后,来修改
回复倍率的,所以插在这
可以修改自然回复的效力
比如篝火(regen *= 1.1f
正在移动(regen *= 0.5f
你就可以
在这里写
PostUpdate
RunSpeeds
你给路达哟
更新后
同样是已经算好了所有东西后
开始更新移动
此时玩家的位置还没有更新
可以用来修改 maxRunSpeed
accRunSpeedrunAcceleration
等移速相关变量
针对
移动
PreUpdate
Movement
预更新移动接下去就会更新玩家位置了
在这之前调用
可以直接在这里修改玩家速度
一般来说你想写玩家冲刺效果
就要放在这里
针对
冲刺
PostUpdate最终更新这是最后一个了有什么想要全部计算完
才被更新的东西就放这儿
推荐吗?
看情况用

攻击目标相关

就那一堆 ModifyHitOnHitOnHurt 什么什么的,之前的教程看过的话都认识,不在赘述。

来自物品的函数的玩家版本

基本物品那章讲过的函数这边基本都有一份复刻版,用法都一样的。

其他

还有一些绘制相关的,零散的东西,可以用补全自己康康,重在探索啊(x


自定义玩家属性

讲完了常用函数,接下来就是学会运用它们。

顺带一提 ModPlayer 不需要重写那个叫 InstancePerEntity 的玩意。

本篇会以如下内容作为例子。

  1. 玩家每次攻击NPC时,会附加一个有2秒内置冷却的额外伤害
  2. 用物品造成效果是100%冷却,但弹幕只有50%
  3. 这个伤害的大小是造成伤害的实体的伤害的50%
  4. 但某些装备/Buff可以让这个伤害被加成
  5. 还有某些装备/Buff可以降低这个效果的冷却时间
  6. 弹幕暴击时让冷却减少10帧
  7. NPC受到三次这个效果之后会被持续附加灵液Debuff

看起来很复杂,不过这些都是对Global系列和ModPlayer的综合运用。

其实你可以先自己试着写一遍,然后看看跟教程有什么区别。以上并没有什么标准规范写法,不必纠结。

ModPlayer 中:

public class SkirtPlayer : ModPlayer
{
    // 声明变量 附加伤害修正
    public StatModifier ExtraDmg = new(0.5f, 1f);
    // 冷却计数器
    public int cd = 0;
    // 冷却倍率
    public float cdMult = 1;
    public override void ResetEffects()
    {
        // 把额外伤害修正重置为加算区0.5f,乘算区1f
        ExtraDmg = new(0.5f, 1f);
        // 重置冷却倍率
        cdMult = 1;
        // 更新冷却时间
        if (cd > 0)
        {
            cd--;
        }
    }
    /// <summary>
    /// 封装函数,用于调用效果
    /// </summary>
    private void AddExtraDmg(float damage, NPC target, ref NPC.HitModifiers modifiers, bool proj)
    {
        // 这个判定没必要用 cd == 0 真没必要
        if (cd <= 0)
        {
            // 重置CD
            float scale = proj ? 0.5f : 1;
            cd = (int)(120 * cdMult * scale);
            // 计算并附加伤害
            float dmg = ExtraDmg.CombineWith(modifiers.FinalDamage).ApplyTo(damage);
            modifiers.FlatBonusDamage += dmg;
            // 获取NPC的效果受击计数,把它+1
            target.GetGlobalNPC<SkirtNPC>().effectCount++;
        }
    }
    public override void ModifyHitNPCWithItem(Item item, NPC target, ref NPC.HitModifiers modifiers)
    {
        AddExtraDmg(item.damage, target, ref modifiers, false);
    }
    public override void ModifyHitNPCWithProj(Projectile proj, NPC target, ref NPC.HitModifiers modifiers)
    {
        AddExtraDmg(proj.damage, target, ref modifiers, false);
    }
}

GlobalNPC 中:

public class SkirtNPC : GlobalNPC
{
    public override bool InstancePerEntity => true;
    // 声明 受击效果计数器
    public int effectCount = 0;
    public override void AI(NPC npc)
    {
        // 受到三次这个效果以上
        if (effectCount >= 3)
        {
            // 持续附加灵液Debuff
            npc.AddBuff(BuffID.Ichor, 2);
        }
    }
}

GlobalProjectile 中:

public class SkirtProj : GlobalProjectile
{
    public override void OnHitNPC(Projectile projectile, NPC target, NPC.HitInfo hit, int damageDone)
    {
        if (hit.Crit)
        {
            Player player = Main.player[projectile.owner];
            SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>();
            skp.cd -= 10;
        }
    }
}

GlobalItem 中:

public class SkirtItem : GlobalItem
{
    public override void UpdateAccessory(Item item, Player player, bool hideVisual)
    {
        // 获取ModPlayer实例
        SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>();
        // 用ref偷懒
        ref float mult = ref skp.cdMult;
        ref StatModifier extra = ref skp.ExtraDmg;
        // 因为是展示,此处仅为原版饰品附加效果
        switch (item.type)
        {
            // 泰坦手套 让伤害率提高10%
            case ItemID.TitanGlove:
                extra += 0.1f;
                break;
            // 强力手套 让伤害率提高15%
            case ItemID.PowerGlove:
                extra += 0.15f;
                break;
            // 消防手套或狂战士手套,均让伤害率提高20%
            case ItemID.FireGauntlet:
            case ItemID.BerserkerGlove:
                extra += 0.2f;
                break;
            // 魔力花 让冷却减少10%
            case ItemID.ManaFlower:
                mult -= 0.1f;
                break;
            // 天界手铐 让冷却减少20%
            case ItemID.CelestialCuffs:
                mult -= 0.2f;
                break;
        }
    }
    public override void UpdateEquip(Item item, Player player)
    {
        // 获取ModPlayer实例
        SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>();
        // 日耀和星璇头盔能让额外伤害翻倍
        if (item.type is ItemID.SolarFlareHelmet or ItemID.VortexHelmet)
        {
            skp.ExtraDmg *= 2;
        }
        // 星云和星辰头盔能减半冷却
        else if (item.type is ItemID.NebulaHelmet or ItemID.StardustHelmet)
        {
            skp.cdMult /= 2f;
        }
    }
}

GlobalBuff 中:

public class SkirtBuff : GlobalBuff
{
    public override void Update(int type, Player player, ref int buffIndex)
    {
        // 获取ModPlayer实例
        SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>();
        // 原版提高10%伤害的愤怒药水效果
        if (type == BuffID.Wrath)
        {
            // 减少10%冷却
            skp.cdMult -= 0.1f;
        }
        // 这个是魔力再生药水
        else if (type == BuffID.ManaRegeneration)
        {
            // 提高5%伤害率
            skp.ExtraDmg += 0.05f;
        }
        // 魔力病
        else if (type == BuffID.ManaSickness)
        {
            int buffTime = player.buffTime[buffIndex];
            // 魔力病的剩余时间每秒导致1%的伤害率降低
            skp.ExtraDmg -= buffTime / 60f * 0.01f;
        }
    }
}

OK,以上就是个简单的使用例了。也许看着很复杂,但是相对来说确实很基础。


数据的保存与读取

先前我们讲的都是用完就扔的变量。不过有些时候这些数据需要被保存下来,比如你写了能“永久”增加玩家的基础魔力的东西(StatManaMax,魔力上限基准)。
这时候就要用到数据的保存和读取了——位于 ModPlayerSaveDataLoadData。(其实别的地方也有比如 ModNPCModItem,以及他们对应的Global系列,请根据实际需要选择使用)

TagCompound,是个没见过的类型呢。

于是乎这里放一段很接地气但是看不懂的介绍(

System.IO.TagCompound 是一个 C# 中用于序列化和反序列化数据的类。它通常用于保存和加载游戏中的数据或配置文件。它提供了一种有序的、有层次结构的数据存储方式。可以存储各种类型的数据,如整数、浮点数、字符串等。

嗯,正如上文所说,他可以接收很多种类型的参数。

写法就是这么个写法,在save中把数据存进去,用文字索引,然后在load用索引把他拿出来。

就这么简单。

顺便再(夹带私货)介绍一个东西,可以用这个来看存储的数据。NBTExplore简要介绍>>>

发表回复