列表 / 统计函数
列表函数是 IM 脚本里最容易出坑的部分——list_get 返回深拷贝。本章把读取、修改、统计三类函数放在一起,并集中讲深拷贝的正确姿势。
列表读取
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
list_size(list) | List | Integer | 元素个数 |
list.size() | — | Integer | 方法形式,等价于 list_size |
list.length() | — | Integer | 最大有效下标(size - 1);空列表返回 -1 |
length(list) | List | Integer | 同 list.length()(v1.3.0+) |
list_get(list, index) | List, Integer | Any | 取第 index 个元素(支持负索引,越界抛错) |
list_contains(list, item) | List, Any | Boolean | 是否包含 |
list_join(list, sep) | List, String | String | 拼接为字符串 |
list_index(list, value) | List, Any | Integer | 查找值的索引,不存在返回 -1 |
size vs length
size()/list_size()返回元素个数。空列表返回0。length()返回最大有效下标(size - 1)。空列表返回-1。
循环边界时大多数情况都用 size():for i in 0..list_size(arr) - 1。
负索引
| 索引 | 含义 |
|---|---|
0 | 第一个元素 |
-1 | 最后一个元素 |
-2 | 倒数第二个 |
yaml
arr = [10, 20, 30, 40, 50]
list_get(arr, 0) # 10
list_get(arr, -1) # 50
list_get(arr, -2) # 40列表修改
所有修改函数都返回新列表,原列表不变。必须用 = list_xxx(...) 接住返回值。
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
list_add(list, item) | List, Any | List | 末尾追加 |
list_insert(list, idx, item) | List, Integer, Any | List | 插入到指定位置 |
list_set(list, idx, item) | List, Integer, Any | List | 替换第 idx 个 |
list_remove(list, idx) | List, Integer | List | 删除第 idx 个 |
list_remove_value(list, value) | List, Any | List | 删除第一个匹配值 |
list_clear(list) | List | List | 清空(返回空列表) |
list_copy(list) | List | List | 深拷贝 |
yaml
arr = [1, 2, 3]
arr = list_add(arr, 4) # [1, 2, 3, 4]
arr = list_insert(arr, 1, 99) # [1, 99, 2, 3, 4]
arr = list_set(arr, 0, 100) # [100, 99, 2, 3, 4]
arr = list_remove(arr, 1) # [100, 2, 3, 4]
arr = list_remove_value(arr, 3) # [100, 2, 4]
副本 = list_copy(arr) # 完全独立的副本list_get 的深拷贝陷阱
对 ItemStack 元素,list_get() 返回 .clone() 深拷贝。对副本的任何修改都不会影响原列表,也不会影响原始变量。
yaml
# ❌ 典型错误
材料列表 = [材料1, 材料2, 材料3]
数量列表 = [10, 20, 30]
for i in 1..材料列表.size() {
副本 = list_get(材料列表, i-1) # 深拷贝!
副本.lore.append("&7需要: {数量}") # 修改的是副本
}
slot.set("s1", 材料1) # ❌ 材料1 没变三种正确模式:
yaml
# ✓ 模式 1:用 list_set 写回列表
for i in 1..材料列表.size() {
副本 = list_get(材料列表, i-1)
副本.lore.append("&7需要: 10")
材料列表 = list_set(材料列表, i-1, 副本)
}
slot.set("s1", list_get(材料列表, 0))
# ✓ 模式 2:直接用修改后的副本
for i in 1..材料列表.size() {
副本 = list_get(材料列表, i-1)
副本.lore.append("&7需要: 10")
slot.set("s" + i, 副本)
}
# ✓ 模式 3:不用列表中转,直接操作原变量
材料1.lore.append("&7需要: 10")
材料2.lore.append("&7需要: 20")
slot.set("s1", 材料1)
slot.set("s2", 材料2)列表存的是值,不是变量名
yaml
物理攻击 = 1.5
生命力 = 4.5
# ❌ 你以为存的是 ["物理攻击", "生命力"]
属性列表 = [物理攻击, 生命力]
# 实际存的是 [1.5, 4.5]
for i in 1..属性列表.size() {
属性 = list_get(属性列表, i-1)
lore.replace("{属性}", "旧", "新")
# 执行的是 lore.replace("1.5", ...),找不到含 "1.5" 的行
}
# ✓ 用字符串字面量
属性名列表 = ["物理攻击", "生命力"]
旧值列表 = [当前攻击, 当前生命]
新值列表 = [新攻击, 新生命]
for i in 1..属性名列表.size() {
属性名 = list_get(属性名列表, i-1)
旧值 = list_get(旧值列表, i-1)
新值 = list_get(新值列表, i-1)
lore.replace("{属性名}", "{旧值}", "{新值}")
}槽位组的列表操作
槽位组(multiple=true)在脚本里就是 List<ItemStack>,空位为 null:
yaml
slots:
F: input(id=food, multiple=true, match="lore.contains('可被吞噬')")
execute: |
饲料数 = count_filled(@food) # 有物品的个数
for i in 0..list_size(@food)-1 {
物品 = list_get(@food, i)
if 物品 != null {
log("第", i, "格:", 物品.name)
}
}统计函数
| 函数 | 参数 | 返回 | 说明 |
|---|---|---|---|
count_filled(slots...) | 槽位引用... | Integer | 有物品的槽位数(支持单个和槽位组) |
count_on(toggles...) | 开关引用... | Integer | 开启的开关数 |
sum(values...) | Number... | Number | 求和 |
sum_extract(items, pattern, group) | List<Item>, 正则, Integer | Number | 从 Lore 正则提取数值求和 |
sum_attr(items, attrName) | List<Item>, String | Number | 累加多物品的属性值 |
yaml
# 槽位组统计
已放入 = count_filled(@food)
总攻 = sum_attr(@food, "攻击力")
总伤 = sum_extract(@food, "伤害:\\s*\\+(\\d+)", 1)
# 开关统计
开启数 = count_on(@lock1, @lock2, @lock3)
# 普通数值求和
总数 = sum(a, b, c, d)sum_attr 等价于"先用 @input.attr() 逐个取值再求和",但写法简洁得多,优先用它代替 for 循环。
何时避免 list_get / for?
- 累加属性:用
sum_attr(@food, "攻击力"),不要for循环。 - 统计非空槽位:用
count_filled,不要手动循环判断。 - 检查列表元素:用
list_contains,不要for循环比较。 - Lore 批量替换:用
lore.random_replace/lore.sort,详见 Lore 动作。