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;
}
}
}