About this mod
Make your time more accurate!This is a set of Stardew Valley time with minutes (0.7 seconds) as the smallest unit.让你的时间更准确!这是一套以分钟(0.7秒)为最小单位的星露谷时间
- Requirements
- Permissions and credits
- Mirrors
- Changelogs
English
What's this?
This is a set of Stardew Valley times minimized in minutes (0.7 seconds)
Why this Helper?
While developing Don't Starve in Stardew Valley , I came across this requirement.A life restoration buff that restores 2 life points every 2 seconds.There is an IsMultipleOf(int number) method in UpdateTickingEventArgs that can run every 2 seconds.However, after looking through the source code, I realized that this method detects if the Ticks are a multiple of number, and the Ticks will keep increasing from the start of the game, even if the game is paused.Obviously, this buff shouldn't bleed back when the game is paused, so the last guy who wrote the C# for this mod used this approach:
if (!Context.IsWorldReady || !Game1.shouldTimePass()) return;
if (e.IsMultipleOf(120)) {
...
}
He checks if the game time is running out before running the logic, thus avoiding the problem of restoring blood on the pause page.But this introduces another problem: if you pause frequently, you'll get a variable time between these restores.This doesn't affect the life restoration buff too much, but then there's another requirement: introducing a sanity system.Introduce a sanity system that reduces sanity by a certain amount per second during the night.In line with the problem mentioned above, it is possible to circumvent the sanity deduction in TAS by pausing “just in time” at frame 60, so that no sanity is deducted for the rest of the night.To solve this problem, I chose to use Stardew Valley Time to handle this set of time-related requirements.But in using Stardew Valley Time, I found another problem.In Stardew Valley, an hour is 100, ten minutes is 10, but there is no smaller unit of time.This means that the smallest unit of time in Stardew Valley is 10, not 1, and 10 minutes in Stardew Valley corresponds to 7 seconds in reality.The 7 second update interval is too long, so I decided to read the game source code and make a new time, and fix some problems with the original time.
Concept
This time is stored in a format similar to a timestamp, where 1 minute is represented as 1 and 1 hour is represented as 60.Stardew Valley time is only 600-2600, which is from 6:00 to 26:00 (2:00 the next morning).In this time system, time and date exist together.That is, you can check the specific time through it, you can also check the specific date through it.For the day time, it changes 626 to the real 0~24 hour system.In the game time, 26:00 corresponds to 2:00am of the next day in the current time, and in the game date, it is the previous day, but it is already the next day in the current time system.
Realization
This set of time refers to the original time, under normal circumstances according to the reality of 700 milliseconds for 1 minute in the game (in the skull cave and other places will also follow the original logic to slow down the flow)However, due to the many factors affecting the time in the original version, the time of the game cannot follow the original time completely, so a time synchronization mechanism is introduced to solve the problem of time inconsistency.In time synchronization, a delta is provided to indicate the difference between the original time and the actual time of the original version.If delta is equal to 0, it means that the original time and the actual time are fully synchronized, no need to do any processing.If delta is greater than 0, the original time is slower than the actual time, and more delta updates need to be performed.If delta is less than 0, it means the original time is faster than the actual time, need to perform less delta updates.
How to use
Install this Mod for the game
Declare an interface class in your Mod source code:
public interface TimeApi {
public long time { get; }
public List<Action<long>> onLoad { get; }
public List<Action<long>> onUpdate { get; }
public List<Action<long, long>> onSync { get; }
}
Get an instance of the interface after the game has started:
helper.Events.GameLoop.GameLaunched += (_, _) => {
var timeApi = helper.ModRegistry.GetApi<TimeApi>("Yurin.MinuteTimeHelper")!;
};
Register update, sync listeners:
timeApi.onUpdate.Add(update);
timeApi.onSync.Add(sync);
Write update and sync code bodies:
// Used to perform less when delta is less than 0
private static long wait;
// update is normally called every 0.7 seconds.
private static void update(long time) {
if (wait > 0) {
wait--;
return;
}
...
}
// sync is normally called every 7 seconds.
private static void sync(long time, long delta) {
if (delta < 0) {
wait += -delta;
} else {
// Call update when delta is greater than or equal to 0.
for (var i = 0; i <= delta; i++) {
update(time);
}
}
}
Please note:
Internally, update is not called when time synchronization occurs, which means that even if the time is exactly the same, update will go off once every 7 seconds.So you need to make up for this by calling update one more time inside sync, even if the delta is 0.
If you have a requirement to execute once every x seconds:
private static long lastTime;private static void update(long time) {
if (wait > 0) {
wait--;
return;
}
// Performed every 3 minutes (2.1 seconds realistically)
if (time - lastTime >= 3) {
...
lastTime = time;
}
}
让我们说中文
这是什么
这是一套以分钟(0.7秒)为最小单位的星露谷时间
为什么要做这个 Helper
我在开发 不要饿死在星露谷(Don't Starve in Stardew Valley) 时遇到这样一个需求:
一个生命恢复 BUFF, 每2秒恢复2点生命值
UpdateTickingEventArgs 中有一个 IsMultipleOf(int number) 方法可以实现每2秒运行一次逻辑
但在我翻阅源码后发现这个方法检测的是 Ticks 是否是 number 的倍数, 而 Ticks 是从游戏启动起就会不停增加, 哪怕游戏暂停也不会停止增加
很显然, 这个 BUFF 在暂停游戏时不应该回血, 所以上一位给这 MOD 写 C# 的使用了这个办法:
if (!Context.IsWorldReady || !Game1.shouldTimePass()) return;
if (e.IsMultipleOf(120)) {
...
}
他在运行逻辑前检查游戏时间是否会流逝, 从而避免了在暂停页面还会恢复血量的问题
但这引入了另一个问题: 如果频繁暂停, 会导致这个回复的间隔时间不固定
在生命恢复这个 BUFF 上不会造成太大影响, 但随后又有了另外一个需求:
引入一个理智值系统, 在晚上每秒减少一定数量的理智值
配合前面说的问题, 在 TAS 上可以通过在第 60 帧"恰好"暂停来规避这次扣理智, 从而实现一个晚上都不会扣理智的情况
为了解决这个问题, 我选择使用星露谷时间来处理这一系列与时间相关的需求
但在使用星露谷时间时, 我发现了另外的问题:
星露谷中一个小时为 100, 十分钟为 10, 但没有更小的时间单位了
也就是说星露谷时间最小单位是 10 而不是 1, 而星露谷 10 分钟对应现实的 7 秒钟
7 秒的更新间隔太长了, 所以我决定阅读游戏源码后重新做一份时间, 并修复原版时间的一些问题
概念
这套时间使用类似时间戳的存储格式, 其中 1 分钟表示为 1, 1小时表示为 60
星露谷时间只有 600-2600, 表示 6 点到 26 点(第二天早上 2 点)
而在本套时间系统中, 时间与日期是存在一起的
即可以通过它查询到具体时间, 也可以通过它查询到具体日期
而对于一日时间, 它将 626 改为了现实的 0~24 小时制
游戏时间中 26 点对应本时间第二天的凌晨 2 点, 游戏日期中处于前一天, 但在本套时间系统内已经是第二天了
实现
本套时间参考原版时间, 正常情况下按现实 700 毫秒为游戏内 1 分钟(在骷髅洞穴等地方也会按照原版逻辑减慢流速)
但由于原版影响时间的因素过多, 本套时间无法完全跟随原版时间, 所以引入了时间同步机制来解决时间不一致问题
在时间同步中, 会提供一个 delta 表示原先时间与原版实际时间之间的差值
若 delta 等于 0, 表示原先时间与实际时间完全同步, 无需做任何处理
若 delta 大于 0, 表示原先时间慢于实际时间, 需要多执行 delta 次更新
若 delta 小于 0, 表示原先时间快于实际时间, 需要少执行 delta 次更新
使用
为游戏安装本 Mod
在您的 Mod 源码中声明一个接口类:
public interface TimeApi {
public long time { get; }
public List<Action<long>> onLoad { get; }
public List<Action<long>> onUpdate { get; }
public List<Action<long, long>> onSync { get; }
}
在游戏启动后获得接口实例:
helper.Events.GameLoop.GameLaunched += (_, _) => {
var timeApi = helper.ModRegistry.GetApi<TimeApi>("Yurin.MinuteTimeHelper")!;
};
注册 update, sync 监听器:
timeApi.onUpdate.Add(update);
timeApi.onSync.Add(sync);
编写 update 与 sync 代码体:
// 用于在 delta 小于 0 时少执行
private static long wait;
// update 在正常情况下会每 0.7 秒调用一次
private static void update(long time) {
if (wait > 0) {
wait--;
return;
}
...
}
// sync在正常情况下会每 7 秒调用一次
private static void sync(long time, long delta) {
if (delta < 0) {
wait += -delta;
} else {
// 在 delta 大于等于 0 时调用 update
for (var i = 0; i <= delta; i++) {
update(time);
}
}
}
请注意:
内部在发生时间同步时, 不会调用 update, 这导致哪怕时间完全一致, update 也会每 7 秒少走 1 次
所以你需要在 sync 内再多调用一次来弥补, 所以哪怕 delta 为 0, 你也需要调用一次 update
如果你有每 x 秒执行一次的需求:
private static long lastTime;private static void update(long time) {
if (wait > 0) {
wait--;
return;
}
// 每 3 分钟(现实 2.1 秒)执行一次
if (time - lastTime >= 3) {
...
lastTime = time;
}
}