创建任务与剧情 (Creating Missions)

零、温馨提示

如果您希望为自己的或他人的模组中的生物创建对应的剧情,请阅读本文并根据流程实现您的任务创建过程。

作者并不建议您为Minecraft原生生物(包括友好生物)创建任务,因为这是我们开发团队在未来要逐步解决的问题,如有必要,您可以直接向仓库内提交PR参与模组贡献,而为原版生物的剧情写数据包、尤其是额外的模组,可能因为未来模组内容的更新而导致您耗费时间辛苦完成的作品被替代,甚至是无法兼容。

但作者十分推荐您为其它模组添加的生物创建任务剧情并实现数据包或mod。

原生模组以MIT协议开源,因而您可以自由指定您的数据包和拓展模组的开源协议。

一、剧情与对话内容

如果您添加过战利品表、配方或者成就进度,或许这部分将会理解得更容易一点。在这一节,不涉及代码操作,只有数据包的添加。

数据包结构

您需要在data/<modid>/rpm/missions目录中添加JSON文件,注意文件的存放路径和命名——这将是任务的id。举例,如果您的模组的data目录是这样的:

+ data
|---+ your_mod_id
|   |---+ advancements
|   |   |--- ...
|   |---+ loot_tables
|   |   |--- ...
|   |---+ rpm
|   |   |---+ missions
|   |       |---* lumine1.json
|   |       |---* lumine2.json
|   |       |---+ wraith
|   |           |---* entrenchment.json
|   |           |---* outside.json
|   |---+ recipes
|   |   |--- ...
|   |---+ tags
|   |   |--- ...
|   |---  ...
|---+ minecraft
    |---+ tags
    |   |--- ...
    |--- ...

那么您的模组将会添加四个任务,其id(即Resource Location)分别为:

your_mod_id:lumine1
your_mod_id:lumine2
your_mod_id:wraith/entrenchment
your_mod_id:wraith/outside

这个id后面讲触发任务的时候会用到,所以需要留意一下。

JSON文件格式

上述的JSON文件都需要写成如下形式:

{
  "requires": [
    <多个resource location,表示前置任务,未完成所有前置任务的玩家无法触发本任务>
  ],
  "reward": <可选,entity_type的resouce location,表示玩家完成任务后哪种生物将不再攻击他/她>,
  "messages": [
    <接收任务时的任务对话,包含多条服从如下格式的object>
    {
      "key": <本地化键名,为显示的文本>,
      "speaker": <"player"或"npc",代表这句话是玩家说出的还是对话NPC说出的>
    }
  ],
  "messagesAfter": [
    <完成任务时的任务对话,包含多条服从如下格式的object>
    {
      "key": <本地化键名,为显示的文本>,
      "speaker": <"player"或"npc",代表这句话是玩家说出的还是对话NPC说出的>
    }
  ],
  "loot_table": <可选,战利品表id,表示任务完成时或接收时获得的战利品>,
  "loot_before": <可选,true或false(默认false),true则在接受任务时获得战利品,false则在完成任务时获得战利品>
}

任务强制只在开始和结束时才触发对话,而对话的目标最多只有一位NPC(当然也可以是玩家独白)——这是这个简易系统唯一的局限性。更复杂的对话场景请通过多次触发任务来实现。

你可以在这里找到JSON文件的编写范例。

显示的文本由于是键名,因此你需要在本地化文件中翻译它们,你可以在这里找到本地化的编写范例。

二、任务的触发

原生模组提供了两种方式来触发剧情,包括使用生成方块的方法和使用API调用的方法。数据包开发者可以使用前者,而拓展模组作者除了可以使用生成方块,还可以通过调用API实现任务触发。

触发条件

首先,完成和触发过该任务的玩家不会重复触发任务。即进行着“僵尸城堡”任务的玩家进入另一个僵尸城堡,任务不会重复触发;完成“击碎发光的水晶”任务的玩家,再次进入另一个骷髅宫殿,或是击碎一个水晶,任务也不会重复触发。

其次,对于完成任务的触发,只有该任务在进行中的玩家可以触发,比如,如果你杀死了一个僵尸暴君,但此前没有接收“为僵尸们而战”任务,那么你将不会结束这个任务——事实上模组并不允许你这么做,因为僵尸暴君有着超高的抗性、回血量和恢复速率,理论无法在生存模式下杀死,只有玩家完成魔法水池任务后才能被打破他的这些buff。

最后,创造模式的玩家不会触发任务,方便数据包和模组开发者们搭建建筑结构时技术性方块的设置;而生存、冒险、极限甚至旁观模式(方便debug快速过剧情)下的玩家都可以触发。

所有的任务触发都是满足了上述三个条件后,再根据特定的游戏进程、事件或者玩家位置等因素的变化而选择性触发。

生成方块

如果采用生成方块,你通常需要自行创建自然生成的结构来实现。玩家走进结构之后,走入生成方块的感知半径,生成方块将会为范围内的所有玩家触发任务。

你可以使用创造模式物品栏中的生成方块放置它,然后通过命令:

/data modify block x y z <nbt>

来控制它生成和触发的任务。

其方块实体的NBT需要注意的参数如下:

{
  summon: <生成实体的NBT,其中必须包含id属性表示实体的entity_type,可留空代表不生成实体>,
  distance: <触发半径,默认16>,
  id: "real_peaceful_mode:summon_block",
  mission_type: <"receive"或"finish",表示方块用于触发任务的接收还是完成>,
  mission: <任务id(前文提到过),可留空代表不触发任务只生成实体>
}

比如,骷髅任务一(弓箭手委托玩家击碎水晶)触发的生成方块位于水晶旁,方块实体的NBT为:

{
  summon: { 
    ArmorItems: [{}, {}, {}, {id: "minecraft:chainmail_helmet", Count: 1b}]
    id: "minecraft:skeleton"
    NoAI: 1b
    HandItems: [{id: "minecraft:air", Count: 1b}]
  }
  mission: "real_peaceful_mode:skeleton1"
  distance: 8
  id: "real_peaceful_mode:summon_block"
  mission_type: "receive"
}

请务必在创造模式下或各种NBT修改的开发工具中完成NBT编辑,其它模式下将可能会触发这个任务。

生成方块触发任务后将立刻转变为空气方块,所以多人游戏中若多名玩家想同时触发任务,需要同时靠近生成方块。

API调用

模组封装了两个API,位于com.hexagram2021.real_peaceful_mode.api包的MissionHelper类中,分别是triggerMissionForPlayers和triggerMissionForPlayer,针对多玩家和单玩家的任务触发。

先来看这两个函数的签名:

public static void triggerMissionForPlayers(ResourceLocation missionId, SummonBlockEntity.SummonMissionType summonMissionType, ServerLevel serverLevel, Predicate<ServerPlayer> predicate, @Nullable LivingEntity npc, Consumer<ServerPlayer> additionWork);

void triggerMissionForPlayer(ResourceLocation missionId, SummonBlockEntity.SummonMissionType summonMissionType, ServerPlayer player, @Nullable LivingEntity npc, Consumer<ServerPlayer> additionWork);

missionId:任务的ID,不做解释。

summonMissionType:任务触发类型,即接收任务还是完成任务。

serverLevel(上):被触发玩家所在维度的世界。

predicate(上):触发任务的玩家的要求,如手持什么物品、或者与实体的距离低于多少格等。你无需对本节三条触发条件进行额外判断,这个mod会帮你做这个判断。

player(下):触发任务的玩家。

npc:与玩家对话的NPC,如果是玩家独白请传递null。

additionWork:玩家触发任务后额外对玩家的操作。

需要注意的是,这个函数你必须只在服务端调用。下面给出原生模组的部分使用范例。

比如,杀死僵尸暴君后,半径32格的玩家都将触发“为僵尸们而战”任务完成的触发。调用了前者,重写了实体的die函数,代码如下:

@Override
public void die(DamageSource damageSource) {
if(this.level() instanceof ServerLevel serverLevel) {
MissionHelper.triggerMissionForPlayers(
new ResourceLocation(MODID, "zombie3"), SummonBlockEntity.SummonMissionType.FINISH, serverLevel,
player -> player.closerThan(this, 32.0D), this, player -> {}
);
}
super.die(damageSource);
}

而玩家手持一组灵魂土对骷髅国王按下使用键后,玩家物品栏的灵魂土减少64个,同时骷髅国王失去权杖,用到了后者并通过最后一个参数实现回调:

if (itemInHand.is(Items.SOUL_SOIL) && itemInHand.getCount() >= 64) {
MissionHelper.triggerMissionForPlayer(
new ResourceLocation(MODID, "skeleton3"), SummonBlockEntity.SummonMissionType.FINISH, serverPlayer,
this, player1 -> {
player1.getItemInHand(hand).shrink(64);
this.getMainHandItem().setCount(0);
}
);
}

三、总结

看到这里的开发者们,非常感谢您选择为《真正的和平模式》开发拓展数据包和模组。相信您还记得本文开头的温馨提示。

最后诚挚祝愿您能够成功!

资料分类:任务

短评加载中..