Nukkit Brynhildr 函数式编程例子

Nukkit Brynhildr 开发过程中,经常需要对集合进行操作,以往我们都会运用大量的循环。Java 8 提供函数式编程的框架后,我们终于可以用类似于函数的方法去操作集合,来简化我们的代码,并且在多线程优化后能较好的利用CPU资源。

这篇文章中,我们将以几个简单的代码为例子,讲讲函数式编程在 Nukkit Brynhildr 中的期望写法。

功能代码块

权限门

如果玩家有权限,那么在点击时门才会打开,否则提示信息。

体现了这样的写法:

  1. 绑定方块和TaskOwner,实现插件停用时取消钩子和事件
  2. 添加自动重建钩子,解绑系统动作
  3. 添加事件,并且屏蔽短时间内重复发生的事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//绑定方块和taskowner到Gates。Gates是操作门和栅栏门的通用工具类。
Gates.settleBlockAt(world, blockPos)
.forTaskOwner(plugin.asTaskOwner())
//解绑系统动作。这样你点击门它就不会开(会没反应)。包的问题nk帮你处理好了
.unhookSystemBehavior()
//如果不可用(比如门被破坏了),那就自动重新搭建。也就是说这行代码加了,门会固若金汤
.rebuildWhenInvalid()
//添加事件!
.onEvent(
//玩家尝试开门
Events.playerActivate(world, blockPos)
//而且连续触发的事件会被屏蔽
//这样的话两秒内多次开门只会触发一次事件,剩下的不会动作
.onceBetween(Duration.ofSeconds(2)),
//执行动作
(sd,p,w,pos) -> {
//判断权限,发信息
if(MyFunctionHasPerm(p,w,pos)) sd.toggleOpened();
else p.sendChat("没权限还敢开门?你tm逗我");
}
);

报时器

每小时给玩家整点报时。

体现了这样的写法:

  1. 创建新Task并添加约束条件,规定Task在什么时候进行,什么时候取消,规定Task为循环进行的Task。
  2. 遍历所有玩家的集合
  3. 多语言消息的使用

里面的某些代码在实际运用中比较鸡肋,这里只是说明有这样的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Tasks.forSystemOwner(system,owner)
//next 循环并延时,直到满足条件时继续
.next(() -> LocalTime.now().getLong(ChronoField.MINUTE_OF_HOUR) // 分钟数为0
//every 重复执行(满足next条件后,执行任务,等待every时间)
.every(Duration.ofHours(1)) // 延时一小时
//until 任务结束时间。设置后任务会在特定时间自动取消
.until(Instant.now().plusSeconds(60*60*24)) //一天后取消
//running 执行的任务
.running(() -> {
long hour = LocalTime.now().getLong(ChronoField.HOUR_OF_DAY); //获取小时数
// 发给所有玩家
PlayerSystem.forEachPlayerIn(ps, (p) ->
p.sendChat(MyI18NMessage.TELL_TIME_TO_PLAYER.withParam(hour)));
})
//启动任务!
.start();

全自动吸矿机

不应该叫挖矿机,因为它直接”吸”掉一个范围内的所有矿物方块并放进箱子里。

体现了这样的写法:

  1. 绑定牌子,操作牌子文字,绑定牌子相关事件
  2. 使用BlockStream操作大量方块,替换方块,过滤方块,取区域
  3. 使用映射来将一类物品的每一个以给定的法则映射到另一类
  4. 绑定箱子,使用简单的方法向箱子的Inventory放东西
  5. 使用实体工具生成掉落的物品
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//绑定牌子
SettledSign ss = Signs.settleSignAt(world, blockPos).forTaskOwner(plugin.asTaskOwner())
.unhookSystemBehavior()
.rebuildWhenInvalid();
//牌子事件!对于每个玩家点击的事件,两秒内once
ss.onEvent(Events.playerTouch(world, blockPos).onceBetween(Duration.ofSeconds(2)),
(ss,p,w,pos) -> {
//所有挖到的原矿对应的矿物物品,比如钻石矿对应1个钻石,青金石矿对应随机个青金石
List<ItemUnique> got = world.blockStream() //这时候BlockStream不会直接读取所有方块信息
.inSection(blockSection) //取区域
.filter(Minerals::isMineral) //过滤,剩下的全都是原矿方块
.collectAndCompute(b -> WorldOperations.replaceBlock(b, Id.BLOCK_AIR)) //替换成空气
.map(Minerals::toProduct) //原矿对应矿物物品
.collect(Collections::toList); //收集成List
//物品放箱子。返回箱子如果满了放不进箱子的物品
List<ItemUnique> remaining =
Chests.lazyChestAt(world, chestBlockPos).putAndRemaining(got); //啊哈
//调用实体工具,把放不进箱子的生成掉落的物品实体。
Entities.dropItemAt(world, chestBlockPos.asEntityPosition(), remaining);
//设置牌子文字
ss.setText1("== 自动挖矿机 ==");
ss.setText2(String.format("挖到了 %d 个矿物", got.size()));
ss.setText3("矿物已放入箱子中");
});

等效代码

开门

1
2
3
4
5
//只需要开门。如果门无效(根本不是门或没有方块等),那不会动作
Gates.lazyGateAt(world, blockPos).setGateOpened(true);
//更简单的写法,和上面等效
Gates.setGateOpenedAt(world, blockPos, true);

删除世界内方块

1
2
3
4
5
//删除方块。以下写法等效
Blocks.lazyBlockAt(world, blockPos).delete();
Blocks.deleteBlockAt(world, blockPos);
world.deleteBlockAt(blockPos);
world.setBlockIdAt(blockPos, Identifiers.BLOCK_AIR);

不断更新

by lmlstarqaq