本文来源于《服务器上线与策划》,作者Ganxiaozhe (MCAdmin),按照原网站 CC BY-SA 4.0 协议搬运,对原文略有改动。我想近期搭建一个我的世界公益服务器(大概率是基岩版)的想法,因此把这篇文章搬运了过来备查。
https://www.mcadmin.cn/start/develop-plan.html

你需要准备什么?

  • 🧠 一个清晰的大脑

一切就绪,让我们开始吧!什么,你还没准备好?那赶快先去睡一觉吧!

当今服务器局势

在编写该文章的 2020年4月5日 来看,服务器玩法距今为止其实没有特别大的改变,都可被分为下列阵营:

  • 原版生存 - 通常加入了 命令函数、数据包 以增加趣味性
  • 插件生存 - 通常在原版基础上加入了 圈地、地皮、多世界资源 或 怪物增强等
  • 模组生存 - 通常加入了 科技、魔法、冒险、农业、装饰 等拓展性模组
  • 创造服 - 通常以 地皮插件 开发
  • 小游戏服 - 通常以 起床战争、空岛战争、饥饿游戏、职业战争、建筑比赛、躲猫猫、跑酷、战墙、占点 为代表
  • 角色扮演 - 纯净服代表《盘灵古域》、《School of Witchcraft and Wizardry》

上诉内容其开发难度也是从简到繁。

此外,并非所有多人小游戏的开发都需要插件,你仍可以用一个好的优化命令系统开发多人小游戏。并且在目前的原版开发发展趋势上,官方逼死模组指日可待。

小游戏几乎是被大服垄断了,其实很多服务器都开发出了自己创新的小游戏,可就输在曝光率并不强,或者是输在了开 G 的浪潮。

小蔗主观觉得生存服是一直挺香的,不过原版生存服还是太太太过时了,除非你玩的是快照版服务器,但这也是无法获得长期活性的,大家玩到头也就结束了。况且如果遇到的是老玩家,他们除了想忆回青春,否则几乎不会再玩原版生存服。

而 RPG 生存类型的就比较不错,可以从轻度魔改的原版生存开始,完成后进入到下一阶段。用多世界或群组子服区分不同阶段玩家,且支持下阶段回到上阶段世界。既能给予上阶段玩家新奇感,也给予下阶段玩家一定成就感。

轻度魔改是什么意思呢?比如 原版口渴值、僵尸生存(暴露在阳光下会燃烧)、特殊化的怪物、特殊炼药或以原版装饰物品为基础开发的道具等。在设计时务必考虑平衡性及多人下的可行性。

除了道具、怪物的创新,建筑也一定不能落下。好比在生存你第一次发现沙漠遗迹、女巫小屋或村庄时一定比第一次发现僵尸、骷髅或蜘蛛时兴奋多了吧。

更多关于原版开发实例请参见后续的:丰富纯净服内容

第一步 - 确立主题与服务器配置

这一步决定着后面的许多建设的对象,比如是要搭建一个原版服、插件服还是模组服等,又或是生存、小游戏还是 RPG 等,以及是原版生存、空岛生存还是其他。

通常你还得按需(人数、服类型等)给服务器配置一个合适的内存,这里建议是 10G 起步,小于等于 4G 的话开开私服就好惹。

如果服务器配置欠缺(小于10G),又想尽可能容纳多一些玩家,那么超平坦或海岛生存就更为合适。不同于默认世界的世界生成算法、不会出现地下矿洞、更少的方块数据等都将节省计算资源。

此外,插件服请务必选择 Paper 核心:

  • Paper 与 99.99% 的 Spigot 插件兼容
  • Paper 进行了许多改进和优化,从而显着提高了性能
  • Paper 还包括 Timings 的下一个版本,使您能够快速找出导致服务器性能下降的原因

MOD 服请使用 Sponge 核心。

  • Sponge 有助于性能提高
  • Sponge 使用 MIT 许可,生态圈活跃且开放
  • 大多数基于 SpongeAPI 的插件应能跨多个 Minecraft 版本使用

BungeeCord 群组请使用 Waterfall,以防后面需要跳转至 Forge 服而重新设置。

服务器 VPS 选择的话小蔗推荐阿里云,新用户活动力度挺大的:https://www.aliyun.com/minisite/goods?userCode=34mjlgxu,及服务器专场 https://www.aliyun.com/daily-act/ecs/activity_selection?userCode=34mjlgxu,使用该链接购买你和小蔗都能获得优惠(生化环材注:此处指原作者)。通常情况下,高频单核、大 RAM 内存与足够宽带即可。你也可以使用 服务器配置人数预算 工具来进行评估。

第二步 - 服务器主城建筑及选址

首先你需要选择一个允许商业使用或允许公益服使用主城建筑地图,当然你也可以选择自己搭建,火柴盒也是有春天哒!

其次就是将主城放在哪,这也得是有风水考究的。不然轻则占用更多资源,重则卡成 PPT。

对于纯净服的主城只需将主城远离世界出生点区块(WorldSpawnChunks),而大多数服主都是正好将其放在世界出生点区块了,所以是一脸懵逼的被卡。

通常世界出生点区块由 16*16 个区块组成,除外还有 17*17 个区块的 LazyLoadChunks 包含了世界出生点区块,其中出生点区块(SpawnChunks)是一块围绕世界出生点的区域中的一个区块。世界出生点区域中区块的特殊性在于它不会被从内存中卸载,因此不管距离玩家多远,只要有玩家在主世界中,这个区块内的东西都会正常更新。

所以一般服务器中都会把命令方块系统区域设置在世界出生点区块内,以使命令方块保持运行。但这样对于客户端来说,正常渲染加上 SpawnChunks 的事件加载,意味着更多的数据包传输,相比在 SpawnChunks 外会更加的卡顿。

为了解决这一问题,可以在新人玩家一进入服务器后就将其 tp 至远离世界出生点区块的主城,并将其出生点设置在新主城位置。这里的距离大概是 500 格以外,小蔗建议是 1000 格。

比如世界出生点在 0 60 0,循环以下代码(记得在服务器配置文件中启用命令方块):

Text

tp @a[tag=!mcadmin-joinInit] 2000 60 2000
spawnpoint @a[tag=!mcadmin-joinInit] 2000 60 2000

tag @a[tag=!mcadmin-joinInit] add mcadmin-joinInit

插件服或 MOD 服则建议是新开一个虚空多世界用于置放主城,且设置世界边界将其包裹。群组服也是如此。

第三步 - 新人礼包及引导

永远不要让玩家一头雾水地进来,否则他们就真可能只是进来看看了。 —— 鲁迅

新手物品一定要给!给够!但要控制好不要一下就把物品栏给挤满了,不然可能适得其反。

小蔗建议你使用下列物品:

Text

give @p minecraft:wooden_sword{Unbreakable:1b,display:{Name:”[\“\\u00a7a基础木剑\“]“,Lore:[“[\“\“]“,”[\“\\u00a77日常练习的基础木剑\“]“,”[\“\\u00a77每个部落都会下发哦\“]“]},HideFlags:63b,Enchantments:[{id:”minecraft:smite”,lvl:1s}],AttributeModifiers:[{AttributeName:”generic.attackDamage”,Name:”act”,Amount:5d,Operation:0,UUIDMost:3917881349168270L,UUIDLeast:3917881349168270L,Slot:mainhand}]} 1
give @p minecraft:iron_pickaxe{display:{Name:”[\“\\u00a7a铁镐\“]“,Lore:[“[\“\“]“,”[\“\\u00a77部落里老矿工传下来的矿镐\“]“]},HideFlags:63b} 1
give @p minecraft:torch{display:{Name:”[\“\\u00a76火把\“]“,Lore:[“[\“\“]“,”[\“\\u00a77世间万物皆有裂痕\“]“,”[\“\\u00a77那是光照进来的地方\“]“]},HideFlags:63b} 32
give @p minecraft:bread{display:{Name:”[\“\\u00a7a荞麦面包\“]“,Lore:[“[\“\“]“,”[\“\\u00a77为新人准备的食物\“]“,”[\“\\u00a77健康不油腻\“]“]}} 16
give @p minecraft:sweet_berries{display:{Name:”[\“\\u00a7a甜浆果\“]“,Lore:[“[\“\“]“,”[\“\\u00a77汁水甜美的浆果\“]“,”[\“\\u00a77用来涂在面包上再好不过了\“]“]},HideFlags:63b} 32
give @p minecraft:leather_helmet{Unbreakable:1b,display:{Name:”[\“\\u00a77皮革护甲\“]“,Lore:[“[\“\\u00a77为新人准备的护甲\“]“,”[\“\“]“,”[\“\\u00a7a保护自然,人造皮革\“]“],color:444661},Enchantments:[{id:”minecraft:projectile_protection”,lvl:1s}]} 1
give @p minecraft:leather_chestplate{Unbreakable:1b,display:{Name:”[\“\\u00a77皮革护甲\“]“,Lore:[“[\“\\u00a77为新人准备的护甲\“]“,”[\“\“]“,”[\“\\u00a7a保护自然,人造皮革\“]“],color:36095}} 1
give @p minecraft:leather_leggings{Unbreakable:1b,display:{Name:”[\“\\u00a77皮革护甲\“]“,Lore:[“[\“\\u00a77为新人准备的护甲\“]“,”[\“\“]“,”[\“\\u00a7a保护自然,人造皮革\“]“],color:1863828}} 1

give @p minecraft:leather_boots{Unbreakable:1b,display:{Name:”[\“\\u00a77皮革护甲\“]“,Lore:[“[\“\\u00a77为新人准备的护甲\“]“,”[\“\“]“,”[\“\\u00a7a保护自然,人造皮革\“]“],color:3521525}} 1

给物品加上一定介绍,更能赢得玩家好感甚至归属感噢。

其次就是玩家的引导,可以是让其找主城一个 NPC 对话、找一个物品、传递一个信物等等来向玩家介绍这整个世界,让其有个大致的了解。而非是开局一套装,其他全照旧。

第四步 - 完善功能性设施

这时玩家已经可以正常开始生存了,但作为服主,你还暂时不能歇着。

服务器还需要功能性的设施来使其像个服务器,而不是局域网联机。比如你也许需要:

  • 安全 - 主要是针对熊孩子的一些措施,如高频杀死所有点燃的可爆炸物
  • 菜单 - 原版可采用书本形式,插件一般使用箱子 UI 来做菜单
  • 银行 - 提供存款、取现、转账或购物等消费。原版用计分板,插件一般使用 Vault 前置
  • 领地 - 玩家可以圈地保护自己的家园
  • 商城 - 提供物品出售或回收
  • 传送 - 各地主城直接传送及玩家间传送邀请
  • 奖励 - 在线时长、邀请新人、加群等行为可获得奖励
  • ……

在原版服(Minecraft:Java 1.15.2)中,高频循环以下代码,即可禁止爆炸物并全服提示:

Text

tag @e[type=minecraft:tnt_minecart,nbt={TNTFuse:79}] add mcadmin-explosive
tag @e[type=minecraft:tnt] add mcadmin-explosive
execute at @e[tag=mcadmin-explosive] run tellraw @a [{“text”:”[MCAdmin] “,”color”:”gold”,”bold”:true,”clickEvent”:{“action”:”open_url”,”value”:”https://www.mcadmin.cn/?from=mcGame"}},"\\u00a7c\\u00a7l探测到爆炸物并清除。玩家 “,{“selector”:”@a[distance=..7]“,”color”:”dark_red”,”bold”:”true”},” \u00a7c\u00a7l在爆炸物附近!”]

kill @e[type=!minecraft:player,tag=mcadmin-explosive]

它大概是这样:

为了日后的查询,还可以创建一个计分板以记录玩家放置爆炸物的次数(以下代码在对话框中执行即可):

Text

scoreboard objectives add mcadmin-useTnt minecraft.used:minecraft.tnt [“放置TNT次数”]

scoreboard objectives add mcadmin-useTntM minecraft.used:minecraft.tnt_minecart [“放置TNT矿车次数”]

查询使用侧边栏显示:

Text

scoreboard objectives setdisplay sidebar mcadmin-useTnt

scoreboard objectives setdisplay sidebar mcadmin-useTntM

或者将其显示在 [TAB] 的列表中:

Text

scoreboard objectives setdisplay list mcadmin-useTnt

scoreboard objectives setdisplay list mcadmin-useTntM

清空显示给一个空值即可:

Text

scoreboard objectives setdisplay sidebar

scoreboard objectives setdisplay list

如果是防止苦力怕爆炸、火球炸毁方块、小黑搬方块、末影龙摧毁停车场等怪物破坏方块,修改一下游戏规则就好啦:

gamerule mobGriefing false

关于其他具体的功能实现请参考:丰富纯净服内容

第五步 - 完善趣味性设施

在这一步你就可以随意发挥脑洞惹,从常规的抽奖、彩票、怪物增强、技能道具等等到魔改严重的世界,你甚至可以通过代码来制定新的游戏规则。

这些设施也并非是单纯的趣味性,它们通常与整体功能性相辅相成,比如用服务器货币进行抽奖来赢得特殊物品道具等。

下面就让小蔗来带你在原版服中完成一个抽奖机的制作(Minecraft:Java 1.15.2),这个抽奖机要能够实现以下功能:

  • 稳定 - 再多人游戏中能够稳定运行
  • 效应 - 花费 抽奖卷 抽奖一次,抽奖卷可以通过金币购买或活动赠送
  • 动画 - 抽奖时会有老虎机一样的动画效果
  • 公示 - 将抽奖结果全服通告

具体流程如下:

Text

玩家消耗金币购买抽奖卷
玩家消耗抽奖卷进行抽奖,每次只能一个玩家进行抽奖。在有玩家抽奖时,关闭抽奖入口即可
抽奖机动态随机显示奖品时,抽奖者具有 mcadmin-lottery-ready 标签
随机显示一定次数后,将最后一次随机物品作为奖品,通告并发放给抽奖者

!注意处理抽奖者突然下线的问题

完成后效果图:

那么,我们正式开始吧!首先是可让玩家通过金币购买抽奖卷,我们需要创建虚拟计分板来代表金币:

scoreboard objectives add mcadmin-money dummy [“金币”]

然后编辑抽奖卷物品,这里小蔗就用纸来做了:

give @p minecraft:paper{mcadminLottery:1b,display:{Name:”[\“\\u00a76抽奖卷\“]“,Lore:[“[\“\“]“,”[\“\\u00a77用于抽奖机抽奖\“]“]}} 1

其中 mcadminLottery 为自定义 NBT,后续我们判断是否为抽奖卷时只需判断物品是否有该 NBT 即可。

购买则用告示牌商店,利用其 clickEvent 的 run_command 可以无权限运行指令。告示牌每一行都可以有一个 clickEvent ,所以最多能运行四个指令。一般情况下是足够做物品出售了,但这里小蔗为了优化交互体验,增加了一些提示,所以需要在下方放置命令方块接替告示牌没做完的工作。

告示牌主要负责为操作者添加标签及金币不足的提示,为操作者添加标签是为了交接给命令方块后不至于让命令方块一头雾水的 @p 而导致可能发生的错误选择。因为在多人游戏中,离系统站得最近的可能并非是执行购买操作的玩家,所以 @p 选择器并非是一个好选择。而借助告示牌,我们可以使用 @s 选择器来精确地选中执行操作的玩家。

告示牌中指令如下:

Text

#给无 mcadmin-money 计分项的玩家一个具体值,供后续检测
scoreboard players add @s mcadmin-money 0
tellraw @s[scores={mcadmin-money=..49}] [“\u00a7b\u00a7l[MCAdmin] \u00a7c您的余额不足,购买失败。\n\u00a7e- 该商品需要 \u00a7e\u00a7l50\u00a7e 金币,您当前仅有 “,{“score”:{“name”:”@s”,”objective”:”mcadmin-money”},”color”:”yellow”,”bold”:”true”},” \u00a7e金币”]
tag @s[scores={mcadmin-money=50..}] add mcadmin-canBuy-lottery
#激活命令方块组,注意相对位置的契合

data merge block ~1 ~-3 ~ {auto:1b}

告示牌完整代码如下,你也可以用 MCAdmin 提供的工具进行生成: 告示牌生成器

give @p minecraft:oak_sign{BlockEntityTag:{Text1:’[{“text”:”\\u00a74\\u00a7l[商铺]“,”clickEvent”:{“action”:”run_command”,”value”:”/scoreboard players add @s mcadmin-money 0”}}]‘,Text2:’[{“text”:”\\u00a7e出售 | 抽奖卷*1”,”clickEvent”:{“action”:”run_command”,”value”:”/tellraw @s[scores={mcadmin-money=..49}] [\"\\u00a7b\\u00a7l[MCAdmin] \\u00a7c您的余额不足,购买失败。\\\n\\u00a7e- 该商品需要 \\u00a7e\\u00a7l50\\u00a7e 金币,您当前仅有 \",{\"score\":{\"name\":\"@s\",\"objective\":\"mcadmin-money\"},\"color\":\"yellow\",\"bold\":\"true\"},\" \\u00a7e金币\"]“}}]‘,Text3:’[{“text”:”\\u00a7b价格 | 金币*50”,”clickEvent”:{“action”:”run_command”,”value”:”/tag @s[scores={mcadmin-money=50..}] add mcadmin-canBuy-lottery”}}]‘,Text4:’[{“text”:”\\u00a7a[MCAdmin.cn]]“,”clickEvent”:{“action”:”run_command”,”value”:”/data merge block ~1 ~-3 ~ {auto:1b}”}}]‘}} 1

接着是命令组中指令:

Text

data merge block ~ ~ ~ {auto:0b}
scoreboard players remove @a[distance=..20,tag=mcadmin-canBuy-lottery] mcadmin-money 50
give @a[distance=..20,tag=mcadmin-canBuy-lottery] minecraft:paper{mcadminLottery:1b,display:{Name:”[\“\\u00a76抽奖卷\“]“,Lore:[“[\“\“]“,”[\“\\u00a77用于抽奖机抽奖\“]“]}} 1
execute as @a[distance=..20,tag=mcadmin-canBuy-lottery] run tellraw @s [“\u00a7b\u00a7l[MCAdmin] \u00a7a成功购买\u00a7e\u00a7l抽奖卷*1\u00a7a,花费\u00a76\u00a7l 50 \u00a76 金币\u00a7a,当前余额: “,{“score”:{“name”:”@s”,”objective”:”mcadmin-money”},”color”:”gold”,”bold”:”true”}]

tag @a[tag=mcadmin-canBuy-lottery] remove mcadmin-canBuy-lottery

命令组按照上图中所示依次摆放即可,无需条件制约,将脉冲方块设置为红石激活。

至此,我们完成了抽奖卷的购买操作,下一步我们先暂时略过验证玩家是否持有抽奖卷,而是考虑如何做抽奖动画以及物品发放。

在物品动画上,小蔗选择以物品框中的变化来实现,为此要注意处理玩家可能将物品框破坏或将物品打出。通常给予物品展示框无敌数据标签 Invulnerable:1b 即可(物品展示框是实体),该无敌效果对创造模式玩家无效。如果玩家在生存模式下还需要对展示框附着方块进行保护。

通过 /data get 我们可以发现展示框中物品也是由 Item:{} 数据值控制。所以用 /data merge entity 即可对其展示物品进行变更。

例如用下述指令我们就可以面朝 Z+ 轴放置一个朝向正确的物品展示框:

Text

#Facing 为展示框朝向,须在实际运用中进行调整;ItemRotation 用于初始化物品朝向;ItemDropChance:0f 则是将物品掉落率设置为0,即使被破坏也不会掉落其中物品

summon minecraft:item_frame ~ ~ ~ {Tags:[“lotteryFrame”],Facing:2b,Invulnerable:1b,ItemRotation:0b,ItemDropChance:0f}

小蔗这里是要在物品展示框(抽奖页面)出现后关闭抽奖入口,所以准备直接用展示框覆盖掉原先的抽奖入口(按钮、拉杆或告示牌等),直到抽奖结束后再替换回来。

替换展示框中物品的代码如下,给物品 Name 属性值是为了当玩家准星放在展示框上时显示其名字。

data merge entity @e[tag=lotteryFrame,limit=1] {Item:{id:”minecraft:grass”,Count:1b,tag:{display:{Name:”[\“\\u00a7a草\“]“}}},ItemRotation:0b}

接下来,我们只要让他从物品池里随机显示即可。这里小蔗建议使用选择器实现随机,例如1.13之前的 @r 或1.13之后的 @e[sort=random,limit=1]。

具体就是将许多实体(marker)放置在不同命令方块(命令组)上,通过随机选择一个 marker 激活它下边的命令方块,如图:

这里小蔗是用盔甲架来当作 marker 以方便展示,在大数量级上还是用药水云以获得更好的性能。盔甲架召唤代码:

summon minecraft:armor_stand ~ ~ ~ {CustomName:”[\“lotteryMarker\“]“,CustomNameVisible:1b,PersistenceRequired:1b,Tags:[“lotteryMarker”],Small:true,Marker:true}

利用放置红石块的方式进行激活,代码如下:

execute at @e[tag=lotteryMarker,sort=random,limit=1] run setblock ~ ~ ~ redstone_block replace

重置系统只需将所有 marker 处方块替换为空气:

execute at @e[tag=lotteryMarker] run setblock ~ ~ ~ air replace

随机激活的命令组其中三组的指令如下:

Text

#第一组
data merge entity @e[tag=lotteryFrame,limit=1] {Item:{id:”minecraft:leather_helmet”,Count:1b,tag:{display:{Name:”[\“\\u00a78远古的\\u00a7e皮革护甲\“]“,color:444661}}},ItemRotation:0b}
give @a[tag=mcadmin-lottery-done] minecraft:leather_helmet{display:{Name:”[\“\\u00a78远古的\\u00a7e皮革护甲\“]“,Lore:[“[\“\“]“,”[\“\\u00a77远古祭祀洗礼过的皮革护甲\“]“,”[\“\\u00a77似乎拥有古老的\\u00a73魔法\“]“],color:444661},Enchantments:[{id:”minecraft:projectile_protection”,lvl:3s},{id:”minecraft:respiration”,lvl:1s}],AttributeModifiers:[{AttributeName:”generic.luck”,Name:”lucky”,Amount:0.25d,Operation:1,UUIDMost:6527969787195994L,UUIDLeast:6527969787195994L,Slot:head}]} 1
execute as @a[tag=mcadmin-lottery-done] run tellraw @a [“\u00a76\u00a7l[抽奖机] \u00a7e恭喜 “,{“selector”:”@s”,”bold”:”true”},” \u00a7e在抽奖机中获得了 \u00a7f[\u00a78远古的\u00a7e皮革护甲\u00a7f] \u00a7e!”]
#第二组
data merge entity @e[tag=lotteryFrame,limit=1] {Item:{id:”minecraft:feather”,Count:1b,tag:{display:{Name:”[\“\\u00a7c异兽的\\u00a7f羽毛\“]“}}},ItemRotation:0b}
give @a[tag=mcadmin-lottery-done] minecraft:feather{display:{Name:”[\“\\u00a7c异兽的\\u00a7f羽毛\“]“,Lore:[“[\“\“]“,”[\“\\u00a77被羽毛攻击者将昏昏欲睡\“]“]},Enchantments:[{id:”minecraft:looting”,lvl:1s}],AttributeModifiers:[{AttributeName:”generic.attackDamage”,Name:”act”,Amount:4d,Operation:0,UUIDMost:8712244862932496L,UUIDLeast:8712244862932496L,Slot:mainhand}]} 1
execute as @a[tag=mcadmin-lottery-done] run tellraw @a [“\u00a76\u00a7l[抽奖机] \u00a7e恭喜 “,{“selector”:”@s”,”bold”:”true”},” \u00a7e在抽奖机中获得了 \u00a7f[\u00a7c异兽的\u00a7f羽毛\u00a7f] \u00a7e!”]
#第三组
data merge entity @e[tag=lotteryFrame,limit=1] {Item:{id:”minecraft:air”},ItemRotation:0b}

execute as @a[tag=mcadmin-lottery-done] run tellraw @a [“\u00a76\u00a7l[抽奖机] \u00a7e很遗憾, “,{“selector”:”@s”,”bold”:”true”},” \u00a7e在抽奖机中什么也没抽中…”]

突然出现的 mcadmin-lottery-done 是否让你觉得有亿点跟不上?没关系,请先接着往下看。

接着稍微复杂的一环就是循环系统的控制了。需在开始后循环定时替换一次物品,在循环多少次后停止并发放相应物品给玩家。

这里我们在替换物品后加上发放物品并通告的指令,就无需再另行处理,直接在循环替换前给抽奖者添加 mcadmin-lottery-done 标签即可。

Text

#>创建计分板,只需执行一次
scoreboard objectives add _gs dummy
#>初始化,需要在循环开始前执行一次
scoreboard players set mcadmin-lotteryTick _gs 4
scoreboard players set mcadmin-ticked _gs 0
scoreboard players set mcadmin-doneTick _gs 40
#>循环开始
scoreboard players add mcadmin-tick _gs 1
execute if score mcadmin-ticked _gs >= mcadmin-doneTick _gs run tag @a[tag=mcadmin-lottery-ready] add mcadmin-lottery-done
execute if score mcadmin-ticked _gs >= mcadmin-doneTick _gs run tag @a[tag=mcadmin-lottery-done] remove mcadmin-lottery-ready
execute if score mcadmin-ticked _gs >= mcadmin-doneTick _gs run scoreboard players operation mcadmin-tick _gs = mcadmin-lotteryTick _gs
execute if score mcadmin-tick _gs >= mcadmin-lotteryTick _gs run execute at @e[tag=lotteryMarker,sort=random,limit=1] run setblock ~ ~ ~ redstone_block replace
execute if score mcadmin-tick _gs >= mcadmin-lotteryTick _gs run execute at @e[tag=lotteryMarker] run setblock ~ ~ ~ air replace
execute if score mcadmin-tick _gs >= mcadmin-lotteryTick _gs run scoreboard players add mcadmin-ticked _gs 1
execute if score mcadmin-tick _gs >= mcadmin-lotteryTick _gs run scoreboard players set mcadmin-tick _gs 0
#当抽奖者意外下线或抽奖结束时执行后续操作(X3 Y3 Z3)并停止循环(X2 Y2 Z2):
execute unless entity @a[tag=mcadmin-lottery-ready] run data merge block X3 Y3 Z3 {auto:1b}

execute unless entity @a[tag=mcadmin-lottery-ready] run data merge block X2 Y2 Z2 {auto:0b}

X3 Y3 Z3 处命令组:一个红石中继器以做延迟,避免提前移除玩家 tag 导致发放物品失败

Text

data merge block ~ ~ ~ {auto:0b}
tag @a remove mcadmin-lottery-ready
tag @a remove mcadmin-lottery-done
kill @e[tag=lotteryFrame,limit=1]

setblock X4 Y4 Z4 minecraft:oak_wall_sign[facing=south]{Text1:”[{\“text\“:\“[抽奖机]\“,\“color\“:\“dark_red\“,\“bold\“:\“true\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“tag @s[nbt={Inventory:[{id:\\"minecraft:paper\\",tag:{mcadminLottery:1b}}]}] add mcadmin-canLottery\“}}]“,Text2:”[{\“text\“:\“随机奖品,祝你好运\“,\“color\“:\“gold\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“tellraw @s[tag=!mcadmin-canLottery] [\\"\\\u00a7b\\\u00a7l[MCAdmin] \\\u00a7c您需要一个抽奖卷用于抽奖。\\"]\“}}]“,Text3:”[{\“text\“:\“消耗 | 抽奖卷*1\“,\“color\“:\“aqua\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“execute as @s[tag=mcadmin-canLottery] run data merge block X1 Y1 Z1 {auto:1b}\“}}]“,Text4:”[{\“text\“:\“[MCAdmin.cn]\“,\“color\“:\“green\“}]“}

最后我们再回过来处理验证玩家是否持有抽奖卷,并将所有系统对接。

用 Inventory 即可检测玩家背包中是否含有指定物品:

@s[nbt={Inventory:[{id:”minecraft:paper”,tag:{mcadminLottery:1b}}]}]

然后再利用 tag 反选做出提示,这一部分小蔗也建议放在告示牌中:

Text

tag @s[nbt={Inventory:[{id:”minecraft:paper”,tag:{mcadminLottery:1b}}]}] add mcadmin-canLottery
tellraw @s[tag=!mcadmin-canLottery] [“\u00a7b\u00a7l[MCAdmin] \u00a7c您需要一个抽奖卷用于抽奖。”]
#激活后续执行抽奖的命令组

execute as @s[tag=mcadmin-canLottery] run data merge block X1 Y1 Z1 {auto:1b}

整合在告示牌中代码如下:

give @p minecraft:oak_sign{BlockEntityTag:{Text1:”[{\“text\“:\“[抽奖机]\“,\“color\“:\“dark_red\“,\“bold\“:\“true\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“tag @s[nbt={Inventory:[{id:\\"minecraft:paper\\",tag:{mcadminLottery:1b}}]}] add mcadmin-canLottery\“}}]“,Text2:”[{\“text\“:\“随机奖品,祝你好运\“,\“color\“:\“gold\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“tellraw @s[tag=!mcadmin-canLottery] [\\"\\\u00a7b\\\u00a7l[MCAdmin] \\\u00a7c您需要一个抽奖卷用于抽奖。\\"]\“}}]“,Text3:”[{\“text\“:\“消耗 | 抽奖卷*1\“,\“color\“:\“aqua\“,\“clickEvent\“:{\“action\“:\“run_command\“,\“value\“:\“execute as @s[tag=mcadmin-canLottery] run data merge block X1 Y1 Z1 {auto:1b}\“}}]“,Text4:”[{\“text\“:\“[MCAdmin.cn]\“,\“color\“:\“green\“}]“}} 1

接着是预处理抽奖的命令组:

Text

data merge block ~ ~ ~ {auto:0b}
#防止上次抽奖玩家突然掉线后依然存在 ready 标签
tag @a remove mcadmin-lottery-ready
tag @p[tag=mcadmin-canLottery] add mcadmin-lottery-ready
clear @a[tag=mcadmin-lottery-ready] minecraft:paper{mcadminLottery:1b} 1
tag @a[tag=mcadmin-lottery-ready] remove mcadmin-canLottery
setblock X4 Y4 Z4 air replace
summon minecraft:item_frame X4 Y4 Z4 {Tags:[“lotteryFrame”],Facing:3b,Invulnerable:1b,ItemRotation:0b,ItemDropChance:0f}
#初始化循环
scoreboard players set mcadmin-lotteryTick _gs 4
scoreboard players set mcadmin-ticked _gs 0
scoreboard players set mcadmin-doneTick _gs 40
#启动循环

data merge block X2 Y2 Z2 {auto:1b}

整个系统鸟瞰图: