泰拉瑞亚Mod(地狱直通车)物品开发日志

1、实现思路

        (1)定义坐标startX和startY

        (2)计算背包最大镐力

        (3)定义挖掘宽度和深度

        (4)清理通道中的液体、宝箱

        (5)清理通道中的物品(tile),通过反射调用函数判断是否能破坏物品

        (6)替换左右边缘的物块为石砖

        (7)替换背景墙为石砖墙

2、关键函数介绍

        (1)清理液体:tile.Clear(TileDataType.Liquid);

        (2)清理物块:WorldGen.KillTile(x, y, false, false, false);

        (3)获取物块:Tile tile = Main.tile[x , y];

        (4)判断镐力:bool canKill = WorldGen.CanKillTile(x, y, out blockDamaged);

        (5)放置物块:WorldGen.PlaceTile(int i, int j, int Type, bool mute = false, bool forced = false, int plr = -1, int style = 0)

        (6)同步物块:NetMessage.SendTileSquare(-1, x, y, 1);

3、问答

        Q:为什么采用反射计算镐力

        A:因为本人的代码水平过菜,仅找到了一个可以判断镐力的方法

private unsafe int GetPickaxeDamage(int x, int y, int pickPower, int hitBufferIndex, Tile tileTarget)

        Q:液体为什么清理不掉?

        A:清理物块和清理液体属于不同的函数,如要清理液体请参考以下函数

public unsafe void Clear(TileDataType types)
{
	if ((types & TileDataType.Tile) != (TileDataType)0)
	{
		*this.type = 0;
		this.active(false);
		*this.frameX = 0;
		*this.frameY = 0;
	}
	if ((types & TileDataType.Wall) != (TileDataType)0)
	{
		*this.wall = 0;
		this.wallFrameX(0);
		this.wallFrameY(0);
	}
	if ((types & TileDataType.TilePaint) != (TileDataType)0)
	{
		this.ClearBlockPaintAndCoating();
	}
	if ((types & TileDataType.WallPaint) != (TileDataType)0)
	{
		this.ClearWallPaintAndCoating();
	}
	if ((types & TileDataType.Liquid) != (TileDataType)0)
	{
		*this.liquid = 0;
		this.liquidType(0);
		this.checkingLiquid(false);
	}
	if ((types & TileDataType.Slope) != (TileDataType)0)
	{
		this.slope(0);
		this.halfBrick(false);
	}
	if ((types & TileDataType.Wiring) != (TileDataType)0)
	{
		this.wire(false);
		this.wire2(false);
		this.wire3(false);
		this.wire4(false);
	}
	if ((types & TileDataType.Actuator) != (TileDataType)0)
	{
		this.actuator(false);
		this.inActive(false);
	}
}

        Q:能否调换代码顺序?

        A:请务必确保在放置火把之前,已经清理液体

        Q:为什么设置默认镐力为35而不是0?

        A:如果为零且背包没有镐子,那么将导致清理物品失败,因此给了保底

        Q:为什么不直接使用Main.tile[x , y]进行修改?

        A:它是只读的(气死了o.o)

        

        Q:为什么在调用代码放置物品时,发现和预想的不一样?

        A:tile和item属于两个系统,tile.TileType和item继承自Entity的entityId以及ItemID类不互通,因此不能将内部id视为tile的TileType

        Q:关于放置物品函数WorldGen.PlaceTile的最后一个参数,有什么说法?

        A:有的兄弟,有的,但由于本人没有吃透泰拉瑞亚的原版代码,因此以下代码只能解决部分问题。当然,你可以通过硬编码解决,但是这样只适用于小规模改动,例如法狗中的快速房屋就是通过硬编码解决的样式问题

//该函数只能覆盖到一部分映射,这是由于博主太懒导致的
public static int CalculateTileStyle(ushort tileType, short frameX, short frameY , Tile trackCache)
        {
            switch (tileType)
            {
                //-- 植物类 (使用 frameY 除22) --
                case 4:   // 草/蘑菇等植物
                case 33:  // 花盆
                case 135: // 草药
                case 137: // 草药
                    return frameY / 22;

                //-- 工作台类 (使用 frameY 除18) --
                case 19:  // 熔炉/织布机等
                case 380: // 酒桶
                    return frameY / 18;

                //-- 容器类 (使用 frameX 除54) --
                case 21:  // 宝箱
                case 88:  // 冰雪宝箱
                case 467: // 衣柜
                    return frameX / 54;

                //-- 家具类 (默认 frameX 除18) --
                case 13:  // 罐子
                case 178: // 椅子
                case 227: // 灯笼
                case 419: // 机械装置
                case 420: // 压力板
                    return frameX / 18;

                //-- 树木类 --
                case 5:   // 普通树木
                case 323: // 棕榈树
                    return tileType == 323 ? frameX / 34 : frameX / 18;

                //-- 矿物类 (使用 frameX 除18) --
                case 6:   // 铁矿
                case 7:   // 铜矿
                case 8:   // 金矿
                case 9:   // 银矿
                    return frameX / 18;

                //-- 轨道类 (特殊处理) --
                case 314: // 轨道
                    return GetTrackStyle(trackCache); // 调用轨道 Style 计算逻辑

                //-- 节日装饰类 --
                case 171: // 圣诞树
                    return frameX / 18;

                //-- 电线装置 --
                case 423: // 逻辑传感器
                    return frameY / 18;

                //-- 液体类 (无Style) --
                case 0:   // 空气
                case 1:   // 石
                case 2:   // 土
                    return 0;

                //-- 特殊类型 --
                case 225: // 蜂窝 (生成蜜蜂逻辑)
                    return frameX / 18;

                case 239: // 旗帜
                    return frameX / 18;

                case 650: // 混合材料块
                    return frameX / 18;

                //-- 默认规则 --
                default:
                    // 优先尝试 frameX/18 (覆盖70%以上家具)
                    if (Main.tileFrameImportant[tileType])
                        return frameX / 18;

                    // 次选 frameY/18 (覆盖部分墙饰)
                    return frameY / 18;
            }
        }

        // 轨道 Style 计算逻辑
        public static int GetTrackStyle(Tile trackCache)
        {
            // 使用反射调用私有方法
            var method = typeof(Minecart).GetMethod("GetTrackItem", BindingFlags.NonPublic | BindingFlags.Static);
            if (method != null)
            {
                return (int)method.Invoke(null, new object[] { trackCache });
            }

            // 如果反射失败,返回默认值
            return 2340; //普通轨道
        }

        Q:tile中的frameX和frameY是什么?

        A:类似于帧,用于计算上一个问题中的样式参数

        Q:为什么空指针了?好怪

        A:这可能是空气导致的tile空引用

4、代码展示

using System;
using System.Reflection;
using Microsoft.Xna.Framework;
using Terraria;
using Terraria.Audio;
using Terraria.DataStructures;
using Terraria.GameContent.Creative;
using Terraria.ID;
using Terraria.ModLoader;

namespace ZiYong.Items
{
	public class DigDown : ModItem
	{
        public override void SetStaticDefaults(){}

        public override void SetDefaults()
        {
            Item.width = 32;
            Item.height = 32;
            Item.maxStack = 10;//最大堆叠数
            Item.useTime = 10;
            Item.useAnimation = 10;
            Item.useStyle = ItemUseStyleID.HoldUp;
            Item.value = Item.sellPrice(gold: 20);
            Item.rare = ItemRarityID.Blue;
            Item.consumable = true; //设置为消耗品
	    }

        public override void AddRecipes()
        {
            base.CreateRecipe(1).
                AddIngredient(167, 135). /*135个雷管 = 3000高度*7宽度破坏力*/
                AddIngredient(8, 400)./*3000米通道需要消耗的火把数量*/
                AddIngredient(129, 9750)./*3000米通道需要6000石砖通道+3750石砖做成背景墙*/
                AddTile(18)./*在工作台制作*/
                Register();
        }

        // 定义反射方法,获取镐力
        private static Func<int, int, int, int, Tile, int> GetPickaxeDamageReflected;

        // 初始化反射
        private static void InitializeReflection()
        {
            // 获取 Player 类型
            Type playerType = typeof(Player);

            // 获取 GetPickaxeDamage 方法
            MethodInfo method = playerType.GetMethod("GetPickaxeDamage", 
                BindingFlags.NonPublic | BindingFlags.Instance, 
                null, 
                new Type[] { typeof(int), typeof(int), typeof(int), typeof(int), typeof(Tile) }, 
                null);

            // 创建委托
            GetPickaxeDamageReflected = (Func<int, int, int, int, Tile, int>)Delegate.CreateDelegate(
                typeof(Func<int, int, int, int, Tile, int>), 
                null, 
                method);
        }

        // 调用反射方法
        private static int GetPickaxeDamage(int x, int y, int pickPower, int hitBufferIndex, Tile tile)
        {
            if (GetPickaxeDamageReflected == null)
            {
                InitializeReflection();
            }
            return GetPickaxeDamageReflected(x, y, pickPower, hitBufferIndex, tile);
        }

        public override bool? UseItem(Player player)
        {
            // 获取玩家的当前位置
            int startX = (int)(player.position.X / 16);
            int startY = (int)(player.position.Y / 16) + 3; //定位到脚下

            // 计算玩家背包中最大的镐力
            int maxPickPower = 35;
            foreach (Item item in player.inventory)
            {
                if (item.pick > maxPickPower) maxPickPower = item.pick;
            }
            //Main.NewText("最大稿力:" + maxPickPower);

            // 定义挖掘的宽度和深度
            int width = 7;
            int depth = Main.maxTilesY - 200;

            for (int x = startX - width / 2; x < startX + width / 2 + 1; x++)
            {
                for (int y = startY; y < depth; y++)
                {
                    if (x < 0 || x >= Main.maxTilesX || y < 0 || y >= Main.maxTilesY)
                        continue;

                    Tile tile = Main.tile[x , y];
                    tile.Clear(TileDataType.Liquid);
                    if(tile != null && tile.TileType == 21){//清理箱子
                        tile.ClearTile();
                        tile.ClearEverything();
                    }
                }
            }

            // 挖掘物块(带镐力验证)
            for (int x = startX - width/2; x < startX + width/2 + 1; x++)
            {
                for (int y = startY; y < depth; y++)
                {
                    if (x < 0 || x >= Main.maxTilesX || y < 0 || y >= Main.maxTilesY)
                        continue;

                    Tile tile = Framing.GetTileSafely(x, y);
                    if (!tile.HasTile) 
                        continue;

                    // 调用反射方法获取伤害值
                    int hitBufferIndex = 0; // 根据实际需求设置,若无特殊需求可设为0
                    int damage = GetPickaxeDamage(x, y, maxPickPower, hitBufferIndex, tile);
                    
                    // 检查镐力是否足够(伤害>0)和其他破坏条件
                    bool blockDamaged;
                    bool canKill = WorldGen.CanKillTile(x, y, out blockDamaged);
                    
                    if (damage > 0 && canKill)
                    {
                        WorldGen.KillTile(x, y, false, false, false);
                        NetMessage.SendTileSquare(-1, x, y, 1);
                    }
                }
            }

            // 替换左右边缘的物块为石砖
            for (int y = startY; y < depth; y++)
            {
                // 左侧边界
                int leftX = startX - width/2;
                if (Main.tile[leftX , y]!= null && leftX >= 0 && leftX < Main.maxTilesX)
                {
                    WorldGen.PlaceTile(leftX, y, 38, false, false , -1 ,0);//石砖
                    if(y % 8 == 0)WorldGen.PlaceTile(leftX + 1, y, 4, false, false , -1 ,0);//火把
                }

                // 右侧边界
                int rightX = startX + width/2;
                if (Main.tile[rightX , y]!= null && rightX >= 0 && rightX < Main.maxTilesX)
                {
                    WorldGen.PlaceTile(rightX, y, 38, false, false , -1 ,0);//石砖
                    if(y % 8 == 0)WorldGen.PlaceTile(rightX - 1, y, 4, false, false , -1 ,0);//火把
                }
            }
            

            // 替换背景墙体为石砖墙(不需要镐力验证)
            for (int x = startX - width/2 + 1; x < startX + width/2; x++)
            {
                for (int y = startY; y < depth; y++)
                {
                    if (x < 0 || x >= Main.maxTilesX || y < 0 || y >= Main.maxTilesY)
                        continue;
                    WorldGen.KillWall(x, y);
                    WorldGen.PlaceWall(x, y, 5); //编号5代表石砖
                    if(Main.tile[x , y].WallType != 5){
                        Main.tile[x , y].WallType = 5;
                    }
                }
            }
            return true;
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值