[6] Time

01/09/2024, 11:47pm

Happy New Year! The most recent addition to the game is one of the biggest yet - Time.

(time scaled up x1000 in this clip)

Time will now pass in the world, creating distinct morning, afternoon, evening, and night phases. I took the opportunity to upgrade the water on the game client as well. The old shader had a few issues and also didn't interact well with a dynamic sky. I used some assets for this: Enviro 3 and Lux Water. No reason to reinvent the wheel for this stuff, at least not right now.

The current time ratio is 4:1, so a full day in the game takes 6 real-world hours. Time affects a few things in the game world: At night, dangerous beasts will come out and hunt down unsuspecting players. Certain bosses or quests may be only available at certain times. And when fishing, your success at catching certain fish will depend on the time (more on that in a later update).

Serverside, I've been experimenting with scripting again to take advantage of this new feature. Here's an example for an NPC spawner that is only active at night:

npc-spawner-night.toml ... [transform] [npc_spawner] min_spawn_delay = 50 max_spawn_delay = 250 max_spawned_ents = 3 [time_based] on_time_of_day_changed_lua = """ local ent_id = Context:ent_id() local time_of_day = time_of_day() local is_night = (time_of_day:value() == 3) set_entity_active(ent_id, is_night) print("[Lua] npc-spawner-night is_active = " .. tostring(is_night)) """ ...

You could also use a TimeBasedComponent for an NPC that despawns when the sun comes up:

[time_based] on_time_of_day_changed_lua = """ local ent_id = Context:ent_id() local time_of_day = time_of_day() local despawn_effect_id = 25 -- corresponds to some "poof" effect on client -- despawn this NPC if it is dawn or day local needs_despawn = (time_of_day:value() == 0) or (time_of_day:value() == 1) if needs_despawn then print("[Lua] Day is here, despawning") spawn_effect_on_entity(ent_id, despawn_effect_id) delete_entity(ent_id) end """

The scripting API is still pretty rough. As time goes on I'll continue making improvements there. But it's nice to be able to data-drive some simple behaviors.

Time is synced to clients using a new packet:

101 - 0x65 - Time Initialize struct TimeInitializePacket { world_time: String, world_time_scale: i32, }

Clients will receive this packet on connection and at infrequent intervals after that, unless time changes in some dramatic way (like through an admin command), upon which it will also be sent. Aside from that, there's not much attempt here to sync a tick count or stay super accurate with the server. Since both clocks advance independently in real time I'm expecting them to stay in sync at least well enough to support the day/night cycle, as time on the client is purely visual and there are no game mechanics that require precise timing (yet). An improvement that could be made here though is to take the latency between client and server into account when syncing the initial time. With this current system, if the time scale and ping are high enough, the times could diverge by several in-game minutes.

Now that it can get dark at night (in the wilderness at least), you probably want to carry a light around. If you find yourself without one, you can use the new Lighter item to ignite a Stick and get a Torch.

Over the holidays I also did a lot of boring terrain fixes. Most notably, terrain normals don't break down at certain chunk boundaries anymore. I need to investigate just pre-calculating these or even setting up some sort of static map, because they really are simple, and my current method of normals generation is getting expensive for no reason.

Before. Yuck.

After. Normals on the chunk boundary now take those boundary tiles into account.

I'm glad to have finally gotten all this stuff done. Lots more to do, though. Thanks for reading.

- Declan (@dooskington)