为了能让那些有志于参与活动策划的伙伴们更好的了解活动组织的全过程,今后每次举办活动后,我都会在这里更新活动开发笔记,记录活动从想法到实现的每一步,借此来与大家交流学习。
策划阶段
活动主题的确定
圣诞节活动应是一年中的大型活动,本应由喵窝管理组组织。然而美羽最近忙于加班猛汉王,于是和卷商量了之后就和兽兽把这锅接下来了。大家可以理解为卷把活动外包给了我和兽兽来制作。
在讨论活动主题时,我们最先想到的是PVP活动。因为1.13的Forge没有出,还没有玩家雷达这种作弊工具,因此PVP会比较精彩。考虑到毛线最近新人较多,为了能让新人融入活动,我们也定下了要以小队形式参加的事情。后来卷表示圣诞节打打杀杀的有伤风气,于是我们也考虑过类似抢礼物、海上竞技等形式,但最后活动鬼才兽兽提出的模拟经营玩法得到了一致认同。当时是十一月中旬。
核心玩法的构建
模拟经营的核心玩法大同小异,就是不断投资,使金钱指数级上升。结合MC的一些特色,我们决定了多条致富之路:种田、养殖、植树、挖矿、打怪、钓鱼/摸鱼,后来又加入了合成。这些游戏内容对于一晚来说已经足够丰富,因此我们决定把游戏的时间定为三个小时(一般为两个小时)。
这里面的一些线路很容易打通,例如钓鱼、挖矿、打怪,因此它们适合在前中期提供固定的产出。另一些线路则有很大的上升空间,例如种田、养殖、植树。它们则适合在中后期不断加大投资,实现金钱的指数级上升。这些倒不是在活动策划的开始就决定的,而是在数值策划和调整的过程中逐渐注意到的。
下面决定的是游戏模式。玩家需要破坏方块,但生存模式会过于开放。为了尽可能的限制那些我们没想到的黑科技玩法,我们决定使用冒险模式。即使在冒险模式,也可以通过特殊的物品标签实现方块的放置和破坏,这样可以最大限度的让玩家在我们设计的范围内游戏。
由于游戏时间较短,我们需要将作物生长速度加快。而MC动物可繁殖的特性使得养动物成为了bug般的存在,因此需要取消动物繁殖。我在单机确认能将这两项功能实现后,游戏的核心内容就被完全确定下来了。
分工与数值策划
确定核心内容后,分工也很明确了。兽兽负责规划和建设场地,而我就去实现活动的核心机制,制作自定义物品,以及完成活动流程的制作。
数值策划的部分,我们实际上是在场地开始建设之后才开始的。我自己并不是很想做这个繁琐的计算工作,只是没人帮着弄,就和兽兽讨论着两人设计完了。
在数值策划之前,我们先确定了活动中会出现的所有物品。想着每条线路可能会出现的物品,就这么自然地确定了。对于种类繁多的作物和合成品,我们选了几个比较顺眼的。
模拟经营的数值策划,大致需要符合一个中心思想:低级投资低回报高回报率;高级投资高回报低回报率。本着这个想法,顺口胡诌了一堆数字,把各个物品的购买价格和出售价格都确定了下来。
由于各项数值在这个活动中至关重要,在后期的讨论和完整测试中,我们还对数值进行了多次的调整,以便玩家更好的发展。
初始资金是一个既不会让某些玩家玩死,又不至于太多的数目。最后的数目的每队400块。
建设阶段
服务器的搭建和插件的选择
因为卷那边也没有现成的活动服务端,因此我在本地笨手笨脚的自己搭了一个发给了卷。服务端使用了以下插件:
- Essentials:必备
- WorldEdit:建设场地用
- LuckPerms:权限管理
- Multiverse:多世界用
- Shopkeeper:NPC
- NyaaUtils:一些实用的小功能(以及大家习惯的功能)
- RPGItems:尽管没用上但想着可能有用还是装了
- WorldGuard:用于解决后来发现的一些场地破坏问题
- 其它的一些支持插件
基本上就是喵窝的那一套插件方案,毕竟我对这些插件都比较熟悉,应用起来也会方便一些。
地图的建造
小镇没什么好说的。就造几栋房子塞几个NPC,具体的规划思路还请兽兽娓娓道来。
基地为了方便复制粘贴,并且看上去不丑,直接做成了地下的形式。每个队伍有一些房间,不同的房间有着不一样的用途。房间的整体结构不可更改,但里面的某些地方可以拓展。由于冒险模式下,带特殊标签的物品只能放置在特定的方块上,因此对于可拓展的地块,我们统一使用了彩色混凝土,而在其它的建筑中则避开了这种建材。
物品的创建
从这里开始就是我的主要工作。我首先做的一件事情是创建在冒险模式下可以被使用/放置的物品。MC中的方块/物品都拥有 CanPlacedOn
和 CanDestroy
标签。拥有这些标签的物品可以在冒险模式下破坏特定方块/放置在特定方块上。对于所有需要对方块进行操作的物品,我都加上了这些标签。例如对于优质服务器,使用了以下命令生成:
/minecraft:give @p minecraft:potato{CanPlaceOn:["minecraft:farmland"]} 1
由于加入了Essentials插件,默认的 /give
会被Essentials的give功能覆盖,因此在服务器内使用原生指令最好加上minecraft:
命名空间。
物品的命名和Lore则使用了NU的rename
和addlore/setlore
功能。
对于武器、护甲之类,因为不需要对方块进行操作,直接从物品栏摸出来就行了。
而解锁配方的合成书则用以下指令生成:
/minecraft:give @p minecraft:knowledge_book{Recipes:["minecraft:melon"]} 1
上面的命令会给你一本可以解锁西瓜合成表的合成书。
限制物品合成的实现
MC的游戏规则中包含一条doLimitedCrafting
的规则。该规则仅允许玩家使用已解锁的配方合成物品:
/gamerule doLimitedCrafting true
实际测试中,我们发现这条规则并不会防止玩家在得到物品时自动获取相关配方。
求助google后我们找到了解决方案。出现这种情况是因为配方的解锁由进度系统实现,当玩家第一次获得某些物品后,就会解锁一类特殊的进度,从而获得配方。解决的方法是提前解锁所有玩家的这些进度:
/advancement grant @a from minecraft:recipes/root
然后再把所有玩家的合成配方锁定
/recipe take @a *
如此即可防止自动解锁配方。这一步操作应放在游戏开局,和所有后来的新玩家加入游戏时。
生长速度与动物繁殖
作物的生长速度本想通过spigot服务器配置来调整,但发现搞不定,于是直接通过调整游戏随机刻来实现:
/gamerule randomTickSpeed 9
通过这次机会,我也在wiki上熟悉了随机刻的一些机制。在本次活动中,它控制了作物/树木的生长速度。MC的默认值是3,调整至9即为三倍。在活动中的小精灵事件时,随机刻被临时调整至最高100。
禁止动物的繁殖就没有那么简单的工具了。我的思路是高频归零所有动物的InLove
标签。动物在被喂养对应的物品后会冒红心,这时它的InLove
标签值会由0变到600,即具有三十秒的求偶时间,只要在动物被喂养的瞬间将这一值改回0,动物就不再处于求偶状态,因此实现了无法繁殖。由于/data merge entity
只能作用于单个实体,于是用/execute
对全体生物执行:
/execute as @e[nbt={InLove:600}] run data merge entity @s {InLove:0}
上述命令使用循环命令方块执行。RUA!
同样用到高频命令方块的还有玩家的饱食度问题。我们讨论后决定不考虑玩家的吃饭问题,因此需要自动为玩家补充饱食度,同时这样也相当于给了玩家一个缓慢回血的buff。这有很多的实现方式,比如给一个常驻的饱和buff之类。不过我个人不太喜欢那样,因此用了下面的方法:
首先创建一个记分板项目记录玩家的饱食度:
/scoreboard objectives add food food
然后用循环命令方块执行下面的操作:
/execute as @a[scores={food=..19}] run effect give @s minecraft:saturation 1 0 true
自动喂食功能完成了。
鱼
每隔半小时的摸鱼是手动开启的。在鱼塘下面放了一堆生成鱼的命令方块,每一波由兽兽操作。
如果大家都钓过鱼就会发现,这里无法钓上鱼以外的物品。因为考虑到原版钓鱼的东西物品过于杂乱,我们直接把多余的物品删除了。这里就用到了1.13的新功能——数据包。我们只要手动写一个新的钓鱼列表,将原来的覆盖掉即可。
解包MC的jar文件后,在路径\data\minecraft\loot_tables\gameplay\
下找到fishing.json
。手动编辑并删掉关于宝藏的部分之后变成这样:
{
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "loot_table",
"name": "minecraft:gameplay/fishing/fish",
"weight": 1,
"quality": -1
}
]
}
]
}
在服务端主世界的文件夹下创建\datapacks\fishmod\data\minecraft\loot_tables\gameplay
目录,把修改后的json文件复制进入,并在\datapacks\fishmod
路径下创建pack.mcmeta
文件,内容如下:
{
"pack": {
"description": "Fishing Mod",
"pack_format": 1
}
}
至此,钓鱼部分的修改就完成了。
啊等等……还要在游戏里面加载我们刚刚做好的数据包:
/datapack enable "file/fishmod"
池塘边上的小房间有祈雨珠的兑换NPC。那个珠子就是使用之前想着可能会用到的RGI插件制作的。关于RGI的制作,我可能会另开笔记详述。
使用祈雨珠将会执行两条命令:
/minecraft:weather rain 600
/title @a title ["{player} 使用了",{"text":"祈雨珠","color":"aqua"}]
实际使用时,会出现 selector not allowed
的错误,使得后面的公告失效。其原因在于spigot没有为选择器分配一个权限节点,而是直接粗暴的仅允许管理员使用选择器。这导致在RGI配置中(RGI可以在使用前给玩家一个临时的权限,并在使用后立刻撤回),除非权限配置项设置为管理员,否则就无法执行。但在1.13中,给管理员权限的操作会直接让服务器停止响应一秒左右的时间。这也是在职业死斗测试局中,服务器卡的没法玩的原因。
因此得出的结论就是:RGI中目前无法使用选择器。
刷怪区域
具体自定义刷怪笼的制作方法……我相信网上有很多的教程,我就不赘述了。懒得自己写命令,也有很多优秀的命令生成器可以使用。我自己用的是卷安利的MCStacker。刷怪笼的命令如下:
/minecraft:give @p minecraft:spawner{BlockEntityTag:{SpawnCount:4,SpawnRange:10,Delay:-1,MinSpawnDelay:600,MaxSpawnDelay:1200,MaxNearbyEntities:6,RequiredPlayerRange:20,SpawnData:{id:"minecraft:zombie",Health:40f,Attributes:[{Name:generic.maxHealth,Base:40},{Name:generic.attackDamage,Base:8}]},SpawnPotentials:[{Weight:1,Entity:{id:"minecraft:zombie",Health:40f,Attributes:[{Name:generic.maxHealth,Base:40},{Name:generic.attackDamage,Base:8}]}},{Weight:1,Entity:{id:"minecraft:skeleton",Health:40f,Attributes:[{Name:generic.maxHealth,Base:40}]}},{Weight:1,Entity:{id:"minecraft:spider",Health:32f,Attributes:[{Name:generic.maxHealth,Base:32},{Name:generic.attackDamage,Base:6}]}},{Weight:1,Entity:{id:"minecraft:creeper",Health:40f,Attributes:[{Name:generic.maxHealth,Base:40}]}}]}} 1
由于过亮无法刷怪,我们需要在头顶放一堆乌云。实践之后发现乌云的效果不佳……因此我们直接在255高度铺了一大片黑曜石平台用来遮光。
当兽兽敲下//set obsidian
的那一刻……服务器崩溃了。
经反复确认,是WE引起的大面积光照更新所致。后来我们有尝试使用原版的/fill
指令来完成,结果服务器又崩溃了。
再三崩溃之下,我们终于是铺完了这片黑曜石平台。它的确带来了遮光的效果,但我怀疑WE操作给地图存档带来了一些不可修复的问题。刷怪区域的光照衰减出现了一些神奇的bug,出现了一些“无法被照亮的区域”。由于原版WE没有fixlighting的功能,我没能找到解决方案。好在没有影响到小镇的区域。
保护区设立
我原本是不打算添加WorldGuard插件的,因为我不会用。但在考虑到玩家可能会用锄头去锄小镇里的地……我还是决定去读一下WG的插件帮助。
在整个地图范围内(地图边界我用/worldborder
缩的很小)的地表区域,我建立了一个保护区。flag的配置如下:
use: ALLOW
damage_animals: DENY
interact: ALLOW
chest-access: ALLOW
而在矿洞,玩家需要能够破坏方块。矿界与地表保护区有着重合的区域,因此需要将矿界的优先级调高。矿界的flag配置如下:
passthrough: ALLOW
build: ALLOW
后来在测试中发现,没有保护区内build权限的玩家是无法查看展示框内的成书的,因此针对每个成书展示框的单独一个方块,我都分别设置了新的保护区,添加了build权限,并把优先级调高。
所有保护区的owner都设置为建造组,以方便场地建设。下面将讲到用户组设置。
用户组与玩家权限
1.13 活动开发中,遇到最大的困难在于:插件命令基本无法使用选择器,且/execute
无法执行插件命令。对于TP、清背包、治疗等常见操作,我可以使用原版命令来代替,但对权限配置而言,这将是致命的,这意味着我不能实现权限自动分配,也不能批量设置权限。玩家的权限组只能是默认权限组一个,因为我不能每加入一个新玩家就手动输入命令把他添加到某个权限组里面。
这样一来,给不同队伍的玩家设置不同的出生点的计划就泡汤了(出生点是绑定到用户组的)。不过往好处想,至少我配置权限会省心不少,不用做那么多不同的用户组了。
默认的玩家用户组配置如下:
essentials.build
essentials.chat.color
essentials.chat.format
essentials.hat
essentials.motd
essentials.msg
essentials.spawn
essentials.tpaccpet
nu.show
对于帮忙建设场地的建设组,追加权限如下:
essentials.gamemode.*
multiverse.teleport.self.*
mv.bypass.gamemode.*
worldedit.*
essentials.back
essentials.tpa
essentials.tpahere
nu.rename
其它琐碎的配置
还有一些琐碎的世界设定,尽管很麻烦,却是必不可少的。
活动使用了两个世界,一个等待/组队用世界,另一个是活动世界。从活动流程上看也许不需要两个世界,在同一世界的不同区域也行。但若遇到了世界需要卸载的问题,有一个等待用世界是极好的。
每个世界的默认游戏模式设置为冒险模式:
/mvm set gamemode adventure
关闭火焰传播、怪物方块破坏、生物自然生成:
/gamerule doFireTick false
/gamerule mobGriefing false
/gamerule doMobSpawning false
打开死亡不掉落:
/gamerule keepInventory true
设定世界边界:
/worldborder set 600
传送
本次活动用到的传送并不多,但值得单独拿出来说一说。第一波集体传送至小镇是致命的,一堆玩家不知为何传送到了墙里面。因为同时传送会有一种游戏开局的感觉,因此我没有使用分开传送的方式。考虑到所有玩家都传送到一个固定的点可能会被挤死,我使用了/spreadplayer
将玩家分散到一个小区域里面。但最后还是出现了挤压的情况,因此我怀疑/spreadplayer
不能跨世界传送。下次会考虑直接用定点传送,并在集体传送之前事先给玩家一个抗性提升。
集体传送带来了一个我没有想到的问题,就是第一波玩家回到基地的拥堵。回到基地的踏板我有意设置为一次只传送一位玩家,这样多位玩家同时回基地时,会有“踩踏板回到基地”的实感。但看到这次的情况,下回我也许还是应该一次传送范围内的所有玩家。
传送回到基地的踏板会根据玩家的队伍传送到相应的基地。由于队伍有20个之多,使用的命令方块至少要用到20个。此时如果每次都仅用@p
选择器,很难保证一次执行中每个命令方块选到的都是同一个玩家。为了保证每次都对同一个玩家执行操作,我使用的方式是先给最近的玩家添加tag,再对指定tag的玩家进行传送操作。
最后的传送命令方块链依次为:
/tag @a remove teleport
/tag @p[gamemode=adventure,distance=..4] add teleport
/minecraft:tp @p[tag=teleport,distance=..50,team=0] -373 23 -149
/minecraft:tp @p[tag=teleport,distance=..50,team=1] -373 23 -115
/minecraft:tp @p[tag=teleport,distance=..50,team=2] -373 23 -81
……
/minecraft:tp @p[tag=teleport,distance=..50,team=J] -241 23 -47
流程控制
整体流程
游戏的整体流程为:
玩家熟悉小镇->分队->游戏开始->游戏结束->兑换奖励并统计->奖励发放。
玩家从六点半开始可以进入服务器熟悉小镇,到七点十五分开始正式分队,最后七点半准时开始。十点半结束后,玩家有一段额外的时间可以将金钱整理为代币并统计。
在组队时,玩家通过踩踏板加入小队。成功加入后,名字的前面会出现相应队伍的前缀。这可以通过修改队伍配置来实现:
/team modify 0 prefix "Team 0"
由于使用了JSON格式,前缀的颜色其实是可以更改的,不过我有点懒。
游戏开始时需要进行的操作为:
统一重置配方->清空背包->治疗玩家->传送至出生点
而游戏结束时,需要进行的操作仅为停止作物生长。
新玩家加入
新玩家加入时,需要手动加入某一队伍,同时也要执行下面的一串操作。
重置配方->清空背包->治疗->传送至出生点
我们在地下做了一个小黑屋,上面的操作就可以通过玩家踩踏板一步到位的执行了。
活动文案
论坛文案以及宣传
在十一月底,活动已经基本制作完成。由于是大型活动,这次的活动文案我写的会更加认真一些。正式的宣传从十二月初开始,直到活动前一周不断预热,从活动参与人数来看,宣传效果应该是相当不错的。
当然,喵窝那边的人数也基本符合预期。
游戏内文案
为了帮助玩家熟悉小镇,我们使用NyaaUtils的新功能,将介绍写到了成书里。本次活动中很少有玩家提出蠢问题,我们也是比较欣慰的。
活动时公告文案
非常抱歉,我们完全没有写活动时的公告文案,所以活动公告都非常的简单直白。以后我们会提前写好更符合活动世界设定的文案的~
奖励设计
奖励设计这方面,其实喵窝那边会更认真一些。一是可以使用RGI,发挥余地会大很多,二是喵窝的东西都很IMBA,所以平衡方面也比较好调整。
而在毛线方面,我其实刚开始是有一些头疼的。既要在功能上特殊化,又要不影响平衡,能做的东西其实很少。在上一次国庆活动中,我们破天荒的加入了AttributeModifier,而这一次活动中,我们则头一回使用了高等级的附魔(X级附魔)。考虑到如果这样的道具如果再通过铁砧强化就太IMBA了,我将所有活动奖励的铁砧惩罚调整至了100级。因此只要将它们放上铁砧,都会显示“过于昂贵”。
但综合来说,对原版生存的影响还是较小的。例如这次的金剑,尽管附魔了锋利X,但总体伤害并没有锋利V的钻石剑高。它的特殊之处可能是上面的穿刺X。
在之后的活动中,我们可能会加入一些“使用次数有限”的物品,这样我们可以在一些核心附魔上(例如抢夺、时运)稍稍放开一些限制。