0%

公告

由于静态博客的局限性,该网站已停止维护,原有的文章将迁移至该位置

文章说明

下文中

  • (op)表示暂时使用的参数名
  • [op]表示可选操作或参数
  • op1/op2表示并列的必需操作或参数
  • [op1]/[op2]表示并列的可选操作或参数

指令格式

指令名:/summoningex (op)(其中op的类型为areasummon

area

提供了对召唤区域(Summoning Area)的操作
格式:/summoningex area (op)
(其中op的类型为definelistqueryremoveshowhide

define

用于定义和命名召唤区域
格式:/summoningex area define (x1), (y1), (z1), (x2), (y2), (x2) as #(name) [colored (colorop) (colorval) [in (dim)]]/[in (dim)]

x1, y1, z1和x2, y2, z2

定义了召唤区域的位置(以(x1, y1, z1)与(x2, y2, z2)两个方块坐标(BlockPos)为顶点)
注意x1, y1, z1, x2, y2, z2为整数且x1, x2之间,y1, y2之间与z1, z2之间没有严格的大小限制(也就是说x1既可以大于x2,又可以等于或小于x2)
x1与x2,y1与y2,z1与z2都相同时表示了一个但方块的召唤区域

name

定义了召唤区域的名称(指令中的”#”号不可省略不能为中文

colorop(可以传入custom或preset)及colorval

当color传入custom时,colorval应传入一个[0, 16777215]间的十进制整数,表示了召唤区域的RGB颜色值
当color传入preset时,colorval应传入原版的一种染料颜色(DyeColor),该颜色表示了召唤区域的颜色(但这个颜色的饱和度最大值被限制在了88%)
若不指定颜色则会随机为召唤区域分配一个颜色

相关代码
染料颜色的获取(getDyeColor(CommandContext, String)静态方法返回输入的染料颜色):

1
2
3
4
5
6
7
8
9
10
11
public static int getIntDyeColor(CommandContext<CommandSourceStack> context, String name) {
int textColor = getDyeColor(context, name).getTextColor();
float[] hsbvals = new float[3];
Color.RGBtoHSB(textColor >> 16 & 0xFF, textColor >> 8 & 0xFF, textColor & 0xFF, hsbvals);
// Restricts the saturation
hsbvals[1] = Math.min(0.88f, hsbvals[1]);
int color = Color.HSBtoRGB(hsbvals[0], hsbvals[1], hsbvals[2]);
// Make sure the color value is positive
color &= 0xffffff;
return color;
}

颜色的随机分配

1
2
3
4
5
6
7
8
9
private static int randomColor(RandomSource random) {
// Randomly selects hue from 0 to 0.95 (step=0.05)
float hue = 0.05f * random.nextInt(20);
// Two types of saturation - low(0.5) and high(0.8)
float saturation = random.nextBoolean() ? 0.5f : 0.8f;
// Randomly selects hue from 0.5 to 1 (step=0.125)
float brightness = 0.5f + 0.125f * random.nextInt(5);
return Color.HSBtoRGB(hue, saturation, brightness);
}
dim

定义了召唤区域所在的维度
若不指定维度则会使用当前所在维度

举例

/summoningex area define 0 100 0 10 110 10 as #Example colored custom 16777215 in minecraft:overworld
在主世界定义了一个名为Example,两个顶点分别为(0,100,0),(10,110,10)两个方块坐标,颜色为白色的召唤区域
/summoningex area define 20 100 20 25 90 25 as #Example2
在指令使用者所在维度定义了一个名为Example2,两个顶点分别为(20,100,20),(25,90,25),颜色随机的召唤区域

list

用于列出指定维度内所有的召唤区域
格式:/summoningex area list [in (dim)]

dim

定义了被用来列出召唤区域的维度
若不指定维度则会使用当前所在维度

举例

/summoningex area list in minecraft:the_nether
列出了下界所有的召唤区域

query

用于在指定维度内查询指定名称的召唤区域
格式:/summoningex area query local/global (op)
(op将在下文中说明)

local与global

当使用local时,op为召唤区域名,用于在指令使用者所在维度内查询指定名称的的召唤区域
当使用global时,op为维度+召唤区域名,用于在输入的维度内查询指定名称的的召唤区域
注意使用global时不会在输入召唤区域名时提供建议(Suggestion)

举例

/summoningex area query local #Example
在指令使用者所在维度查询了名为Example的召唤区域
/summoningex area query global minecraft:overworld #Example2
在主世界查询了名为Example2的召唤区域

remove

用于在指定维度内移除指定名称的召唤区域
格式:/summoningex area remove all/global/local (op)
(op将在下文中说明)

all

此时op为completelyin (dim),也可以省略op
当op为completely时,将会移除所有维度内所有的召唤区域
当op为in (dim)时(dim为维度),将会移除指定维度内所有的召唤区域
当op省略时,将会移除指令使用者所在维度内所有的召唤区域

local与global

local与global的含义与query中大致相同,此处不再阐述

举例

/summoningex area remove local #Example
在指令使用者所在维度移除了名为Example的召唤区域
/summoningex area remove all in minecraft:the_end
移除了末地所有的召唤区域

show

以长方体边框显示所有的召唤区域(召唤区域默认隐藏)
当在使用指令/summoningex area show后,如果添加了新的维度,则可能需要重新使用指令

hide

隐藏所有的召唤区域

summon

提供了在召唤区域(长方体)的底面召唤指定数量实体的操作(召唤的实体将在召唤区域的底面随机分布)
格式:/summoningex summon (entity) inside #(name) [count (cnt) [nbt (nbt)]]

entity

指定了所召唤实体的类型(同原版/summon指令的第一个参数)

name

召唤区域的名称(这里只能使用指令使用者所在维度的召唤区域)

cnt

指定了召唤实体的数量(最大为2000)
若不指定则默认召唤一个实体

nbt

指定了召唤的实体的NBT(对每个召唤的实体使用该NBT的副本)
与原版/summon指令相同,当传入NBT时,Mob类型实体的finalizeSpawn方法将不会被调用

举例

/summoningex summon minecraft:pig inside #Example count 10
在指令使用者所在维度中名为Example的召唤区域内召唤了10只猪
/summoningex summon minecraft:iron_golem inside #Example2 count 5 nbt {Health:1}
在指令使用者所在维度中名为Example2的召唤区域内召唤了5个生命值为1的铁傀儡

新的实体选择器

本Mod提供了一个新的实体选择器(IEntitySelectorType)(默认为@b,可在配置文件内修改)
该实体选择器可以选中所有(或满足一定条件的)与指定召唤区域相交(指碰撞箱相交)的实体

格式

/@b(#(name))[op]

name

召唤区域的名称(这里只能使用指令使用者所在维度的召唤区域)

[op]

与原版实体选择器@e相同

举例

/kill @b(#Example)
杀死指令使用者所在维度中名为Example的召唤区域(下文简记为召唤区域Example)内的所有实体
/effect give @b(#Example)[type=!minecraft:player] minecraft:wither 45
将45秒的凋零效果应用于召唤区域Example内所有不为玩家的实体

注意事项

  • 与原版/summon指令相同,Mod的所有指令的执行权限为2

后记

高中学习压力较大(高一的暑假就只放30多天,这个Mod是暑假抽时间做的),并且两周回家一次,因此开学后可能不会频繁地维护Mod,但我会尽量不停更,也许也会为Mod增加一些内容233

引入

在世界边境附近调试Mod实体时,偶然发现了这样的问题(图中激光渲染的位置出现异常偏移)

这是怎么回事呢?

常用技巧

在Minecraft的Mod开发中,开发者们可能常使用以下的代码辅助世界的渲染:

1
2
Vector3d projectedView = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);

这样做的好处显而易见——可以在渲染中用绝对坐标替换相对坐标。
不过使用这样的方式,有时也会导致一些问题,先从float和double的有效数字分析

Java中的浮点数

下表为float和double的一些属性比较

属性 float double
符号位/bit 1 1
指数位/bit 8 11
尾数位/bit 23 52
占用内存空间 4 Byte (32bit) 8 Byte (64bit)

由此可知float的有效数字位数为6~7位,而double则为15~16位

float的局限性

MatrixStack的translate方法

以下是MatrixStack类中translate方法的源代码

1
2
3
4
public void translate(double x, double y, double z) {
MatrixStack.Entry entry = this.poseStack.getLast();
entry.pose.multiply(Matrix4f.createTranslateMatrix((float) x, (float) y, (float) z));
}

可以发现,此方法内做了一个强制类型转换,将双精度的double转换为了单精度的float!

世界的大小

在World(SRG名,1.16.5)类中,有如下的私有静态方法

1
2
3
private static boolean isInWorldBoundsHorizontal(BlockPos pos) {
return pos.getX() >= -30000000 && pos.getZ() >= -30000000 && pos.getX() < 30000000 && pos.getZ() < 30000000;
}

由此可知,MC的世界是60000001x60000001格的,这会使得当坐标足够大时,用float表示坐标会使float的小数部分丢失,从而发现了问题所在

问题发生的原因

在未解决bug时,由于直接在translate中传入世界真实坐标,导致强制类型转换,进而导致double后面的小数部分丢失

问题的解决

translate时,传入激光起始位置的坐标与玩家坐标的差,使数字变小,以确保float的小数部分保留

1
2
3
4
5
Vector3d cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
double x = src.x - cameraPos.x;
double y = src.y - cameraPos.y;
double z = src.z - cameraPos.z;
matrixStack.translate(x, y, z);

最后效果如下图所示

激光渲染的位置的异常偏移消失了!

总结

  1. float不精确,开发中无性能需求应尽量使用double(虽然也不是100%精确)
  2. Minecraft的世界坐标(x和z)的绝对值上限为30000000而不能无限增大,一定程度上与double的精度限制有关(毕竟每个世界坐标都是double的,但主要还是因为int能表示的范围有限)
  3. 原版下实体的生命上限为1024,这也在一定程度上与float的精度限制有关

一切的开始

希望以后能多分享一些经验吧~

1
2
3
public static void main(String[] args) {
System.out.println("233333333333333333333333333333333333");
}
1
2
3
4
int main() {
cout << "233333333333333333333333333333333333" << endl;
return 0;
}
1
print(233333333333333333333333333333333333)