#193 Add mcl_time

This commit is contained in:
kay27 2022-02-13 03:56:44 +04:00
parent a886eff056
commit 62231b50b6
6 changed files with 210 additions and 88 deletions

View File

@ -0,0 +1,52 @@
# mcl_time
## by kay27 for MineClone 5
---------------------------
This mod counts time when all players sleep or some area is inactive.
It depends very much in `time_speed` configuration variable, which could be changed 'on the fly' by a chat command.
If `time_speed` set to 0, this mod logs warnings and returns zeroes.
### mcl_time.get_seconds_irl()
------------------------------
Returns: Integer value of realtime (not in-game) seconds since world creation.
Usually this value grow smoothly. But whe you skip the night being in the bed, or leave some area for some time, you may experience value jumps. That's basically the idea of this mod.
### mcl_time.get_number_of_times(last_time, interval, chance)
-------------------------------------------------------------
Handy to process AMBs.
You pass `last_time` - last known value of `seconds_irl`, also ABM `interval` and ABM `chance`.
Returns:
* Integer number of how many times ABM function should be called if the area was active all the time and you didn't skip the night.
* Integer value of realtime (not in-game) seconds since world creation.
### mcl_time.touch(pos)
-----------------------
This function 'toches' node at position `pos` by writing `_t` meta variable of `seconds_irl`.
### mcl_time.get_number_of_times_at_pos(pos, interval, chance)
--------------------------------------------------------------
Much more handy to call from LBM or area load, than `mcl_time.get_number_of_times()`!
It reads meta variable `_t` from position `pos` and uses it as previous `seconds_irl`, which then pass as first argument into `mcl_time.get_number_of_times()`.
After calling this, it also 'touches' the node at `pos` by writing `seconds_irl` into meta variable `_t`.
Returns:
* Integer number of how many times ABM function should be called if the area was active all the time and you didn't skip the night.
* Integer value of realtime (not in-game) seconds since world creation.
*Warning!* This function can return 0. So it's better not to use it for regular ABMs - use `mcl_time.get_number_of_times_at_pos_or_1()` instead.
### mcl_time.get_number_of_times_at_pos_or_1(pos, interval, chance)
-------------------------------------------------------------------
Much more handy to process ABMs than `mcl_time.get_number_of_times()` and `mcl_time.get_number_of_times_at_pos()`!
It just calls `mcl_time.get_number_of_times_at_pos()` but doesn't return 0, the minimum number it can return is 1,
which is the most suitable for regular ABM processing function.
Returns:
* Integer number of how many times ABM function should be called if the area was active all the time and you didn't skip the night.
* Integer value of realtime (not in-game) seconds since world creation.

140
mods/CORE/mcl_time/init.lua Normal file
View File

@ -0,0 +1,140 @@
mcl_time = {}
local time_update_interval = 2
local retry_on_fail_interval = 500
local default_time_speed = 72
local save_to_storage_interval = 600
local meta_name = "_t"
local current_time_update_interval = time_update_interval
local storage = minetest.get_mod_storage()
local seconds_irl_public = tonumber(storage:get_string("seconds_irl")) or -2
local last_save_seconds_irl = seconds_irl_public
local next_save_seconds_irl = last_save_seconds_irl + save_to_storage_interval
local previous_seconds_irl = -2
local function get_seconds_irl()
local time_speed = tonumber(minetest.settings:get("time_speed") or default_time_speed)
if time_speed < 1 then
minetest.log("warning", "[mcl_time] time_speed < 1 - please increase to make mcl_time api work (default: " .. default_time_speed .. ")")
return 0
end
local irl_multiplier = 86400 / time_speed
local day_count = minetest.get_day_count()
local timeofday = minetest.get_timeofday()
local seconds_irl
if not day_count or not timeofday then
seconds_irl = seconds_irl_public
else
local days_ig = 0.0 + day_count + timeofday
seconds_irl = days_ig * irl_multiplier
end
if previous_seconds_irl == seconds_irl then
current_time_update_interval = math.min(current_time_update_interval * 2, retry_on_fail_interval)
minetest.log("warning", "[mcl_time] Time doesn't change! seconds_irl=" .. tostring(seconds_irl)
.. ", day_count = " .. tostring(day_count) .. ", timeofday=" .. tostring(timeofday)
.. " - increasing update interval to " .. tostring(current_time_update_interval))
else
previous_seconds_irl = seconds_irl
if current_time_update_interval ~= time_update_interval then
current_time_update_interval = time_update_interval
minetest.log("action", "[mcl_time] Time is changing again: seconds_irl=" .. tostring(seconds_irl)
.. ", day_count = " .. tostring(day_count) .. ", timeofday=" .. tostring(timeofday)
.. ", update_interval=" .. tostring(current_time_update_interval))
end
end
if last_save_seconds_irl >= next_save_seconds_irl then
storage:set_string("seconds_irl", tostring(seconds_irl))
next_save_seconds_irl = seconds_irl + save_to_storage_interval
end
return seconds_irl
end
local seconds_irl_public = get_seconds_irl()
function mcl_time.get_seconds_irl()
return seconds_irl_public
end
local function time_runner()
seconds_irl_public = get_seconds_irl()
minetest.after(current_time_update_interval, time_runner)
end
function mcl_time.get_number_of_times(last_time, interval, chance)
if not last_time then return 0 end
if seconds_irl_public < 2 then return 0 end
if not interval then return 0 end
if not chance then return 0 end
if interval < 1 then return 0 end
if chance < 1 then return 0 end
local number_of_intervals = (seconds_irl_public - last_time) / interval
if number_of_intervals < 1 then return 0 end
local average_chance = (1 + chance) / 2
local number_of_times = math.floor(number_of_intervals / average_chance)
return number_of_times, seconds_irl_public
end
local get_number_of_times = mcl_time.get_number_of_times
function mcl_time.touch(pos)
local meta = minetest.get_meta(pos)
meta:set_int(meta_name, seconds_irl_public)
end
local touch = mcl_time.touch
function mcl_time.get_number_of_times_at_pos(pos, interval, chance)
if not pos then return 0 end
local meta = minetest.get_meta(pos)
local last_time = meta:get_int(meta_name)
local number_of_times = (last_time == 0) and 0 or get_number_of_times(last_time, interval, chance)
touch(pos)
return number_of_times, seconds_irl_public
end
local get_number_of_times_at_pos = mcl_time.get_number_of_times_at_pos
function mcl_time.get_number_of_times_at_pos_or_1(pos, interval, chance)
return math.max(get_number_of_times_at_pos(pos, interval, chance), 1), seconds_irl_public
end
function mcl_time.get_irl_seconds_passed_at_pos(pos)
if not pos then return 0 end
local meta = minetest.get_meta(pos)
local last_time = meta:get_int(meta_name)
local irl_seconds_passed = (last_time == 0) and 0 or (seconds_irl_public - last_time)
return irl_seconds_passed
end
function mcl_time.get_irl_seconds_passed_at_pos_or_1(pos)
if not pos then return 1 end
local meta = minetest.get_meta(pos)
local last_time = meta:get_int(meta_name)
local irl_seconds_passed = (last_time == 0) and 1 or (seconds_irl_public - last_time)
return irl_seconds_passed
end
function mcl_time.get_irl_seconds_passed_at_pos_or_nil(pos)
if not pos then return end
local meta = minetest.get_meta(pos)
local last_time = meta:get_int(meta_name)
if last_time == 0 then return end
local delta_time = seconds_irl_public - last_time
if delta_time <= 0 then return end
return delta_time
end
time_runner()
local day_count = minetest.get_day_count()
local timeofday = minetest.get_timeofday()
minetest.log("action", "[mcl_time] time runner started, current in-real-life seconds: " .. seconds_irl_public
.. ", time_speed: " .. tostring(minetest.settings:get("time_speed"))
.. ", day_count: " .. tostring(day_count)
.. ", timeofday: " .. tostring(timeofday)
.. ", update_interval=" .. tostring(current_time_update_interval)
)

View File

@ -0,0 +1,3 @@
name = mcl_time
author = kay27
description = This mod counts time when all players sleep or some area is inactive

View File

@ -215,62 +215,11 @@ local function swap_node(pos, name)
end end
end end
local function furnace_reset_delta_time(pos)
local meta = minetest.get_meta(pos)
local time_speed = tonumber(minetest.settings:get("time_speed") or 72)
if (time_speed < 0.1) then
return
end
local time_multiplier = 86400 / time_speed
local current_game_time = .0 + ((minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier)
-- TODO: Change meta:get/set_string() to get/set_float() for "last_gametime".
-- In Windows *_float() works OK but under Linux it returns rounded unusable values like 449540.000000000
local last_game_time = meta:get_string("last_gametime")
if last_game_time then
last_game_time = tonumber(last_game_time)
end
if not last_game_time or last_game_time < 1 or math.abs(last_game_time - current_game_time) <= 1.5 then
return
end
meta:set_string("last_gametime", tostring(current_game_time))
end
local function furnace_get_delta_time(pos, elapsed)
local meta = minetest.get_meta(pos)
local time_speed = tonumber(minetest.settings:get("time_speed") or 72)
local current_game_time
if (time_speed < 0.1) then
return meta, elapsed
else
local time_multiplier = 86400 / time_speed
current_game_time = .0 + ((minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier)
end
local last_game_time = meta:get_string("last_gametime")
if last_game_time then
last_game_time = tonumber(last_game_time)
end
if not last_game_time or last_game_time < 1 then
last_game_time = current_game_time - 0.1
elseif last_game_time == current_game_time then
current_game_time = current_game_time + 1.0
end
local elapsed_game_time = .0 + current_game_time - last_game_time
meta:set_string("last_gametime", tostring(current_game_time))
return meta, elapsed_game_time
end
local function furnace_node_timer(pos, elapsed) local function furnace_node_timer(pos, elapsed)
-- --
-- Inizialize metadata -- Inizialize metadata
-- --
local meta, elapsed_game_time = furnace_get_delta_time(pos, elapsed) local meta = minetest.get_meta(pos)
local fuel_time = meta:get_float("fuel_time") or 0 local fuel_time = meta:get_float("fuel_time") or 0
local src_time = meta:get_float("src_time") or 0 local src_time = meta:get_float("src_time") or 0
local src_item = meta:get_string("src_item") or "" local src_item = meta:get_string("src_item") or ""
@ -294,6 +243,7 @@ local function furnace_node_timer(pos, elapsed)
end end
local update = true local update = true
local elapsed_game_time = mcl_time.get_irl_seconds_passed_at_pos_or_nil(pos) or elapsed
while elapsed_game_time > 0.00001 and update do while elapsed_game_time > 0.00001 and update do
-- --
-- Cooking -- Cooking
@ -489,20 +439,20 @@ minetest.register_node("mcl_furnaces:furnace", {
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
-- Reset accumulated game time when player works with furnace: -- Reset accumulated game time when player works with furnace:
furnace_reset_delta_time(pos) mcl_time.touch(pos)
minetest.get_node_timer(pos):start(1.0) minetest.get_node_timer(pos):start(1.0)
on_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) on_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
end, end,
on_metadata_inventory_put = function(pos) on_metadata_inventory_put = function(pos)
-- Reset accumulated game time when player works with furnace: -- Reset accumulated game time when player works with furnace:
furnace_reset_delta_time(pos) mcl_time.touch(pos)
-- start timer function, it will sort out whether furnace can burn or not. -- start timer function, it will sort out whether furnace can burn or not.
minetest.get_node_timer(pos):start(1.0) minetest.get_node_timer(pos):start(1.0)
end, end,
on_metadata_inventory_take = function(pos, listname, index, stack, player) on_metadata_inventory_take = function(pos, listname, index, stack, player)
-- Reset accumulated game time when player works with furnace: -- Reset accumulated game time when player works with furnace:
furnace_reset_delta_time(pos) mcl_time.touch(pos)
-- start timer function, it will helpful if player clears dst slot -- start timer function, it will helpful if player clears dst slot
minetest.get_node_timer(pos):start(1.0) minetest.get_node_timer(pos):start(1.0)

View File

@ -1,3 +1,3 @@
name = mcl_nether name = mcl_nether
depends = mcl_core, mcl_sounds, mcl_util, walkover, doc_items, mcl_colors depends = mcl_core, mcl_sounds, mcl_util, walkover, doc_items, mcl_colors, mcl_time
optional_depends = doc, screwdriver optional_depends = doc, screwdriver

View File

@ -6,21 +6,6 @@ local interval = 35
local chance = 11 local chance = 11
local max_interval = interval * chance local max_interval = interval * chance
local time_speed
local time_multiplier
local current_game_time
function update_timespeed()
time_speed = tonumber(minetest.settings:get("time_speed") or 72)
time_multiplier = 86400 / time_speed
current_game_time = .0 + ((minetest.get_day_count() + minetest.get_timeofday()) * time_multiplier)
minetest.after(5, update_timespeed)
end
minetest.register_on_mods_loaded(function()
minetest.after(5, update_timespeed)
end)
minetest.register_node("mcl_nether:nether_wart_0", { minetest.register_node("mcl_nether:nether_wart_0", {
description = S("Premature Nether Wart (Stage 1)"), description = S("Premature Nether Wart (Stage 1)"),
_doc_items_longdesc = S("A premature nether wart has just recently been planted on soul sand. Nether wart slowly grows on soul sand in 4 stages (the second and third stages look identical). Although nether wart is home to the Nether, it grows in any dimension."), _doc_items_longdesc = S("A premature nether wart has just recently been planted on soul sand. Nether wart slowly grows on soul sand in 4 stages (the second and third stages look identical). Although nether wart is home to the Nether, it grows in any dimension."),
@ -183,6 +168,9 @@ local function grow(pos, node)
new_node.param = node.param new_node.param = node.param
new_node.param2 = node.param2 new_node.param2 = node.param2
minetest.set_node(pos, new_node) minetest.set_node(pos, new_node)
local meta = minetest.get_meta(pos)
meta:set_string("gametime", tostring(mcl_time:get_seconds_irl()))
end end
minetest.register_abm({ minetest.register_abm({
@ -197,9 +185,10 @@ minetest.register_abm({
return return
end end
pos.y = pos.y+1 pos.y = pos.y+1
grow(pos, node)
local meta = minetest.get_meta(pos) for i = 1, mcl_time.get_number_of_times_at_pos_or_1(pos, interval, chance) do
meta:set_string("gametime", tostring(current_game_time)) grow(pos, node)
end
end end
}) })
@ -214,26 +203,14 @@ minetest.register_lbm({
return return
end end
pos.y = pos.y+1 pos.y = pos.y+1
local meta = minetest.get_meta(pos) for i = 1, mcl_time.get_number_of_times_at_pos(pos, interval, chance) do
local last_game_time = tonumber(meta:get_string("gametime"))
if not last_game_time then return end
local real_seconds = last_game_time - current_game_time
if real_seconds < interval then return end
local threshold = math.random(interval, max_interval)
local i = 0
while real_seconds >= threshold and i < 4 do
grow(pos, node) grow(pos, node)
real_seconds = real_seconds - threshold
threshold = math.random(interval, max_interval)
i = i + 1
end end
end end
}) })
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
for i=1,2 do for i=1, 2 do
doc.add_entry_alias("nodes", "mcl_nether:nether_wart_0", "nodes", "mcl_nether:nether_wart_"..i) doc.add_entry_alias("nodes", "mcl_nether:nether_wart_0", "nodes", "mcl_nether:nether_wart_"..i)
end end
end end