Skip to content

Commit

Permalink
complete
Browse files Browse the repository at this point in the history
  • Loading branch information
friendlyhj committed Mar 31, 2022
1 parent c496fc6 commit b388869
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 122 deletions.
242 changes: 144 additions & 98 deletions advanced/common-errors.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# 编写脚本时的常见错误

## 前言
# 常见错误

在编写魔改脚本时,可能会出现一些常见的,有通用解的错误,在此列出名称、起因。

语法错误最好在 /ct syntax 检查出来,在游戏加载时加载脚本可能脚本有语法错误,却依旧尝试编译,导致又臭又长的也看不懂的字节码错误。有时候一个错误会导致很多别的错误。比如没导包的错误,可能会导致之后一堆 `any type is not supported` 错误。但是只有第一条错误 `cannot find XXX` 才是最重要的。而游戏聊天栏显示数量有限,第一条报错就顶掉了,所以报错请第一时间看日志。
语法错误最好在 /ct syntax 检查出来,在游戏加载时加载脚本可能脚本有语法错误,却依旧尝试编译,导致又臭又长的也看不懂的字节码错误。有时候一个错误会导致很多别的错误。比如没导包的错误,可能会导致之后一堆 `any type is not supported` 错误。但是只有第一条错误 `cannot find XXX` 才是最重要的。而游戏聊天栏显示数量有限,第一条报错就顶掉了,所以报错请第一时间看日志。解决报错也请按照日志报错的顺序一个个解决,因为有可能后面的脚本是因为所依赖的脚本(如跨脚本引用)出了问题才报错,实质上没有问题,当前面的报错解决了,后面的报错也有可能一起解决了。

## 报错格式

程序报错不是只是告诉你「我出问题了!」,而会或多或少告诉你哪里出错了以及报错的原因。若要成为一个合格的魔改人,学会看报错是一门必修课。报错分为编译时错误和运行时错误。

### 编译时错误

该类错误一般可用 `/ct syntax` 发现,且报错相对比较简短。

`[INITIALIZATION][CLIENT][ERROR] script.zs:2 > ) expected`

人工整理,可能有加较多纰漏,欢迎纠错。
`[游戏运行阶段][游戏运行端][错误] 脚本名.zs:行数 > 报错原因`

## 语法错误
告诉你哪个脚本的哪一行出现了什么错误。对于缺分号等语法错误,这个行数仅供参考,你应该在该行附近检查问题。

### 语法错误

缺分号,括号不对称等。

Expand All @@ -27,7 +37,7 @@ recipes.remove(<minecraft:apple>)
[INITIALIZATION][CLIENT][ERROR] [crafttweaker | SIDE_CLIENT]: Error parsing blah_blah_blah.zs:2 -- ; expected
```

## 导包错误
### 导包错误

导入的包不存在。

Expand All @@ -45,7 +55,7 @@ import crafttweaker.itemIItemStack;
[INITIALIZATION][CLIENT][ERROR] no_easter_egg_for_you.zs:1 > Not a valid type
```

## 缺少导包
### 缺少导包

除了基本数据类型,其他所有出现 `as Type` 的语句,比如声明变量,函数的参数与返回值,强制转型等,都需要导入对应的类。

Expand All @@ -68,7 +78,7 @@ function foo(item as IItemStack) {
[INITIALIZATION][CLIENT][ERROR] did_you_miss_me.zs:4 > any values not yet supported
```

## 导入语句未写在脚本头部
### 导入语句未写在脚本头部

所有 import 必须写在一起,且在脚本的头部。

Expand All @@ -87,7 +97,7 @@ val apple as IItemStack = <item:minecraft:apple>;
[INITIALIZATION][CLIENT][ERROR] [crafttweaker | SIDE_CLIENT]: Error parsing hello_mister_freeman.zs:3 -- Invalid expression, last token: import
```

## 方法传入参数错误
### 方法传入参数错误

示例:

Expand Down Expand Up @@ -115,87 +125,6 @@ addShaped(string, ZenTypeNative: crafttweaker.item.IItemStack, ZenTypeNative: cr
addShaped(ZenTypeNative: crafttweaker.item.IItemStack, ZenTypeNative: crafttweaker.item.IIngredient[][], Optional ZenTypeNative: crafttweaker.recipes.IRecipeFunction, Optional ZenTypeNative: crafttweaker.recipes.IRecipeAction)
```

## 使用加号连接字符串与数字

示例:

```csharp
print(3 + "and");
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] [crafttweaker]: Error executing {[0:crafttweaker]: i_just_wanna_dance.zs}: For input string: "and"
java.lang.NumberFormatException: For input string: "and"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at I_just_wanna_dance.__script__(i_just_wanna_dance.zs:1)
at __ZenMain__.run(I_just_wanna_dance)
......
```

请使用飘号 (`~`) 连接字符串。

### 数组越界

数组越界错误有若干成因,在此使用最常见的索引超出数组长度问题举例。

示例:

```csharp
import crafttweaker.item.IItemStack;

// 数组长度为 2,最大索引为1
val toRemove as IItemStack[] = [
<minecraft:apple>,
<minecraft:stick>
];


// 鉴于 ZenScript 支持遍历非整数类型数组
// 更好的写法其实是 for remove in toRemove {}
for i in 0 .. 3 {
// 0 .. 3 实际返回为 [0, 1, 2]
recipes.remove(toRemove[i]);
}
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] [crafttweaker]: Error executing {[0:crafttweaker]: ladies_and_gentlemen_we_got_him.zs}: 2
java.lang.ArrayIndexOutOfBoundsException: 2
at Ladies_and_gentlemen_we_got_him.__script__(ladies_and_gentlemen_we_got_him.zs:15)
at __ZenMain__.run(Ladies_and_gentlemen_we_got_him)
at crafttweaker.runtime.CrTTweaker.loadScript(CrTTweaker.java:240)
at crafttweaker.runtime.CrTTweaker.loadScript(CrTTweaker.java:105)
at crafttweaker.mc1120.events.CommonEventHandler.registerRecipes(CommonEventHandler.java:71)
......
```

### 空指针

// ...

### 字符串落双引号

缺少双引号的 string 会被识别为变量关键字,但在声明 map 的 key 中例外,变量会被当成字符串处理。

示例:

```csharp
// 缺少双引号,按变量关键字处理
print(itshouldbeastringbutitsnot);
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] hey_my_name_is_gary.zs:1 > could not find itshouldbeastringbutitsnot
```

### 没有对应方法

示例:
Expand Down Expand Up @@ -231,10 +160,6 @@ print(<minecraft:pineapple>.commandString);
[INITIALIZATION][CLIENT][ERROR] what_the_hell_is_a_pineapple.zs:1: Could not resolve <minecraft : pineapple>
```

### CoT 脚本内声明物品需 item: 开头

这个 `/ct syntax` 查不出来的(

### 找不到变量

报错 `can not find xxx`
Expand Down Expand Up @@ -287,13 +212,30 @@ val output as IItemStack = <item:minecraft:stone>;

你也可以意识到代码缩进的必要性了,可以很清晰的分清包括变量作用域的代码层次结构。

## 对 val 变量重新赋值
### 字符串落双引号

缺少双引号的 string 会被识别为变量关键字,但在声明 map 的 key 中例外,变量会被当成字符串处理。

示例:

```csharp
// 缺少双引号,按变量关键字处理
print(itshouldbeastringbutitsnot);
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] hey_my_name_is_gary.zs:1 > could not find itshouldbeastringbutitsnot
```

### 对 val 变量重新赋值

报错 `value cannot be changed`

如果需要重新赋值,请使用 `var` 来声明变量。

## 对函数参数重新赋值
### 对函数参数重新赋值

报错 `not a valid lvalue`

Expand All @@ -316,7 +258,7 @@ function foo(bar as int) {
}
```

## 匿名函数内对外部变量进行赋值
### 匿名函数内对外部变量进行赋值

匿名函数只能重新赋值函数内部的局部变量,不能重新赋值外部变量。事件什么的都是匿名函数的说。不过更新数组、Map 的元素是可以的。

Expand All @@ -331,5 +273,109 @@ recipes.addShapeless("nether_recipe",<minecraft:netherrack>,
return info.player.world.dimension == -1 ? output : null;
},
null);
```

报错 `not a valid lvalue`

### CoT 脚本内声明物品需 item: 开头

这个 `/ct syntax` 查不出的(

## 运行时错误

除了以上编译时错误之外,有些错误只会在脚本运行时才出现,一般是脚本遇到处理不了的情况,这种错误一般无法用 `/ct syntax` 发现。魔改时请务必注意。大部分运行时错误由 java 异常形式体现,你可以通过异常的 stack trace 查看哪里出问题了。

```log
[INITIALIZATION][CLIENT][ERROR] 发生什么事了
异常名: 异常描述
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) // 内部 java 代码
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at I_just_wanna_dance.__script__(i_just_wanna_dance.zs:1) // 报错时所运行的脚本及函数,认准 zs 即可
at __ZenMain__.run(I_just_wanna_dance) // 所有脚本的入口
......
```

### 使用加号连接字符串与数字

示例:

```csharp
print(3 + "and");
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] [crafttweaker]: Error executing {[0:crafttweaker]: i_just_wanna_dance.zs}: For input string: "and"
java.lang.NumberFormatException: For input string: "and"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at I_just_wanna_dance.__script__(i_just_wanna_dance.zs:1)
at __ZenMain__.run(I_just_wanna_dance)
......
```

请使用飘号 (`~`) 连接字符串。

### 数组越界

数组越界错误有若干成因,在此使用最常见的索引超出数组长度问题举例。

示例:

```csharp
import crafttweaker.item.IItemStack;

// 数组长度为 2,最大索引为1
val toRemove as IItemStack[] = [
<minecraft:apple>,
<minecraft:stick>
];


// 鉴于 ZenScript 支持遍历非整数类型数组
// 更好的写法其实是 for remove in toRemove {}
for i in 0 .. 3 {
// 0 .. 3 实际返回为 [0, 1, 2]
recipes.remove(toRemove[i]);
}
```

报错:

```log
[INITIALIZATION][CLIENT][ERROR] [crafttweaker]: Error executing {[0:crafttweaker]: ladies_and_gentlemen_we_got_him.zs}: 2
java.lang.ArrayIndexOutOfBoundsException: 2
at Ladies_and_gentlemen_we_got_him.__script__(ladies_and_gentlemen_we_got_him.zs:15)
at __ZenMain__.run(Ladies_and_gentlemen_we_got_him)
at crafttweaker.runtime.CrTTweaker.loadScript(CrTTweaker.java:240)
at crafttweaker.runtime.CrTTweaker.loadScript(CrTTweaker.java:105)
at crafttweaker.mc1120.events.CommonEventHandler.registerRecipes(CommonEventHandler.java:71)
......
```

### 空指针

一些 Getter 可能会返回 null,而试图对 null 使用任何方法、Getter 会抛出空指针异常(`NullPointerException`,简称 NPE)。你需要事先用 isNull 函数判断其是否为 null,不是则跳过操作。比如,你需要获取副手的物品的 id,如果和指定的相同,则进行接下的操作

```csharp
var offItem as IItemStack = player.offHandHeldItem;
if (offItem.definition.id == "minecraft:sand") {
...
}
```

但是这样,若玩家副手没有物品,offItem 就是 null,对 null 使用方法会抛出异常。你需要用 isNull 函数

```csharp
var offItem as IItemStack = player.offHandHeldItem;
if (!isNull(offItem) && offItem.definition.id == "minecraft:sand") {
...
}
```

`&&` 是短路逻辑和,若前面的运算结果为 false,结果将直接为 false,后面的运算不再运算。(前文的 `||` 同理)

在这个例子中,先判断 isNull 函数,如果 offItem 为null,isNull 函数结果为 true,取反后为 false,(运算优先级:非大于和大于或)结果直接判断为 false,后面的`offItem.definition.id == "minecraft:sand"` 运算跳过。只有 offItem 不为 null,才会检测其 ID,从而规避空指针异常。
24 changes: 0 additions & 24 deletions advanced/event-overview/tips.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
# 一些忠告

## **isNull函数规避NPE异常**

一些Getter可能会返回null,而试图对null使用任何方法、Getter会抛出NPE异常。你需要事先用isNull函数判断其是否为null,不是则跳过操作。比如,你需要获取副手的物品的id,如果和指定的相同,则进行接下的操作

```csharp
var offItem as IItemStack = player.offHandHeldItem;
if (offItem.definition.id == "minecraft:sand") {
...
}
```

但是这样,若玩家副手没有物品,offItem就是null,对null使用方法会抛出异常。你需要用isNull函数

```csharp
var offItem as IItemStack = player.offHandHeldItem;
if (!isNull(offItem) && offItem.definition.id == "minecraft:sand") {
...
}
```

&&是短路逻辑和,若前面的运算结果为false,结果将直接为false,后面的运算不再运算。(前文的`||`同理)

在这个例子中,先判断isNull函数,如果offItem为null,isNull函数结果为true,取反后为false,(运算优先级:非大于和大于或)结果直接判断为false,后面的`offItem.definition.id == "minecraft:sand"`运算跳过。只有offItem不为null,才会检测其ID,从而规避NPE异常。

## **!world.remote保证事件只在服务端处理**

MC分为服务端和客户端,服务端用来处理事件、存储存档,而客户端用来渲染、向服务端发送数据包。大部分事件**只能**在服务端内执行,客户端不能执行。单人游戏同样存在内置服务端和客户端。
Expand Down

0 comments on commit b388869

Please sign in to comment.