Mod 开发 -- 星露谷中的自定义条件与脚本

Mod 开发 – 星露谷中的自定义条件与脚本

前言

最近星露谷更新了 1.6, 大量 mod 需要更新, 在贴吧闲逛时遇到了个友友在求蘑菇洞与水果洞共存的 Mod, 觉得甚是滑稽, 因此有了实现的想法.

准备

首先探索实现的可能性, 首先入口是蘑菇洞与水果洞的选择入口是农场事件 Content\Data\Events, 内容如下:

1
2
3
{
"65/m 25000/t 600 1200/H": "continue/64 15/farmer 64 16 2 Demetrius 64 18 0/pause 1500/speak Demetrius \"嗨 @!我有好消息要告诉你。前几天,我对本地环境的研究取得了突破。$h#$b#技术细节我就不跟你说了,直接说结果……你知道往西一段距离的那个洞穴吧?我有办法把它变成有用的东西了……对你我都有用。#$b#我想利用那个洞吸引一些本地的物种。这样我就可以在一个更可控的环境下观察它们了。然后不管它们生出来啥都归你了。#$b#我也可以用这个洞吸引食菇蝠或果蝠。这些蝙蝠有时候会给你留下水果。\"/cave/speak Demetrius \"就这么说好了!我现在就给你收拾去!不会花很长时间的!$h#$b#谢谢你给我这次机会。\"/end dialogue Demetrius \"我很期待那个山洞会发生什么事,你也是吧?#$e#希望你对我的工作满意。\""
}

解析为 :

Preconditions:

  • 事件ID 65
  • 赚了 2,5000
  • 六点到十二点
  • 主机玩家

Commands:

  • 播放当前音乐
  • 相机对准 Tile 64,15
  • 玩家站位 Tile 64,16 向下看
  • Demetrius 站位 Tile 64,16 向上看
  • 停顿 1,500ms
  • Demetrius 说话:”嗨…略
  • 触发 洞穴 选项
  • Demetrius 说话:”就…略
  • 关闭 选项 dialogue
  • Demetrius 说话:”我很…略

实际的代码应该在 洞穴选项

源码分析

前置检查 与 事件脚本

检查/脚本 应该属于属于具体抽象, 为: StardewValley.Delegates.EventPreconditionDelegate / StardewValley.Delegates.EventCommandDelegate , 默认实现分别为 StardewValley.Preconditions / StardewValley.Event.DefaultCommands, 注册在代码 StardewValley.Event.SetupEventCommandsIfNeeded 函数中.

洞穴事件

定位到 StardewValley.Event.DefaultCommands.Cave 事件函数, 可以看到这里打开了一个选择窗口并指定窗口 ID 为 “cave”, Dialogue 没有回调函数, 因此处理应该是由 action 处理(比如鼠标点击事件), 由 Game1.currentLocation.createQuestionDialogue 可以看到, 我们应该是在 GameLocation 中处理, handle 入口递进为 StardewValley.Game1.pressActionButton > StardewValley.Event.answerDialogue , 从 “cave” 定位到如下代码:

1
2
3
4
5
6
7
8
9
10
11
         if(!(questionKey =="cave"))
break;
if(answerChoice ==0)
{
Game1.MasterPlayer.caveChoice.Value =2;
Game1.RequireLocation<FarmCave>("FarmCave").

setUpMushroomHouse();
break;
}
Game1.MasterPlayer.caveChoice.Value =1;

可以看到 根据选项不同, 主机玩家的 洞穴选择会持久化为 水果洞1, 蘑菇洞2 并设置蘑菇屋 (BC)128 与烘干机 (BC)Dehydrator, 在每日更新 StardewValley.Locations.FarmCave.DayUpdate 时, 水果洞1 会随机在地上放置几个水果 并设置 farmCaveReady 值为 true. 对于蘑菇洞, 适用建筑规则, 在 Content\Data\Machines, key (BC)128

实现

为了更融入游戏场景, 因此不选择 hack 的方式, 改为让小黑过来玩中国名梗我全都要, 因此我们应该让他在前置检查在: 选择事件之后, 大于 2,5000 的金钱是很好的选择但是无法处理前后事件顺序, 因此需要编写一个自定义的前置检查 caveChoice 的值初始化检查是一个很好的选择, 接下来是脚本的执行可以读取, 由于每日更新刷新水果硬编码需要 caveChoice值1, 所以我们能做的是, 根据之间的选择将 caveChoice 更新为 值1 并可能需要设置蘑菇屋.

自定义检查:

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary> 检查农场洞穴事件是否已经触发.</summary>
/// <param name="location">The location which is checking the event.</param>
/// <param name="eventId">The unique ID for the event being checked.</param>
/// <param name="args">The space-delimited event precondition string, including the precondition name.</param>
public static bool FarmCaveFirstComplete(GameLocation location,
string eventId, string[] args)
{
int caveChoiceValue = Game1.MasterPlayer.caveChoice.Value;
Trace(
$"[FarmCaveFirstComplete] event : {eventId} , caveChoiceValue: {caveChoiceValue}");
return caveChoiceValue is 1 or 2;
}

自定义事件脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
///  <summary> 洞穴升级.
/// 见 <see cref="FarmCavePreconditions.FarmCaveFirstComplete"/></summary>
public static void FarmCaveUpgrade(Event @event, string[] args, EventContext context)
{
var wrapper = Game1.MasterPlayer.caveChoice;
var lastCave = wrapper?.Value;
switch (lastCave)
{
// 水果 + 蘑菇
case 1:
Game1.RequireLocation<FarmCave>("FarmCave").setUpMushroomHouse();
break;
// 蘑菇 + 水果
case 2:
wrapper!.Set(1);
break;
// 错误状态
default:
context.LogErrorAndSkip("必须在洞穴事件后");
break;
}
Debug("FarmCaveUpgrade success!");
++@event.CurrentCommand;
}

注意 [email protected] , 脚本执行的块递进(符号 / 区分)由该值确定

使用

最终效果见 Nexus

另见

MoreEvents OnlyChildMakeChoices