diff --git a/README.md b/README.md index 722f4cad9..691497f05 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # MineClone 5 -Version: 0.71.7 Release Candidate 0 MineClone 5 is a fork of MineClone 2 with different workflow: * No Minecraft version limitation, target version is Latest Java Edition @@ -70,9 +69,8 @@ Use the `/giveme` chat command to obtain them. See the in-game help for an explanation. ## Installation -This game requires [Minetest](http://minetest.net) to run (version 5.4.1 or -later). So you need to install Minetest first. Only stable versions of Minetest -are officially supported. +This game requires latest stable [Minetest](http://minetest.net) to run, please install +it first. Only stable versions of Minetest are officially supported. There is no support for running MineClone 5 in development versions of Minetest. To install MineClone 5 (if you haven't already), move this directory into the @@ -83,10 +81,8 @@ Minetest to learn more. The MineClone 5 repository is hosted at Mesehub. To contribute or report issues, head there. * Mesehub: -* IRC: -* Matrix: -* Reddit: -* Minetest forums: +* Minetest forums: +* Semi-official Discord: ## Project description The main goal of **MineClone 5** is to be a clone of Minecraft and to be released as free software. @@ -155,7 +151,7 @@ The following features are incomplete: * Special minecarts * A couple of non-trivial blocks and items -Bonus features (not found in Minecraft 1.12): +Bonus features (not found in Minecraft): * Built-in crafting guide which shows you crafting and smelting recipes * In-game help system containing extensive help about gameplay basics, blocks, items and more @@ -168,6 +164,7 @@ Bonus features (not found in Minecraft 1.12): * Nether Brick Fence Gate * Red Nether Brick Fence * Red Nether Brick Fence Gate +* Arbitrary-shaped Nether portals Technical differences from Minecraft: diff --git a/mods/CORE/mcl_time/README.md b/mods/CORE/mcl_time/README.md new file mode 100644 index 000000000..c3a1bb3af --- /dev/null +++ b/mods/CORE/mcl_time/README.md @@ -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 on `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 when 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 on 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. diff --git a/mods/CORE/mcl_time/init.lua b/mods/CORE/mcl_time/init.lua new file mode 100644 index 000000000..31bcbbe29 --- /dev/null +++ b/mods/CORE/mcl_time/init.lua @@ -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) +) diff --git a/mods/CORE/mcl_time/mod.conf b/mods/CORE/mcl_time/mod.conf new file mode 100644 index 000000000..c1f6f0948 --- /dev/null +++ b/mods/CORE/mcl_time/mod.conf @@ -0,0 +1,3 @@ +name = mcl_time +author = kay27 +description = This mod counts time when all players sleep or some area is inactive diff --git a/mods/ENTITIES/mcl_boats/init.lua b/mods/ENTITIES/mcl_boats/init.lua index 3c49f5cf7..9ec06d870 100644 --- a/mods/ENTITIES/mcl_boats/init.lua +++ b/mods/ENTITIES/mcl_boats/init.lua @@ -112,7 +112,9 @@ local function detach_object(obj, change_pos) mcl_player.player_attached[obj:get_player_name()] = false mcl_player.player_set_animation(obj, "stand" , 30) else - obj:get_luaentity()._old_visual_size = nil + local luaentity = obj:get_luaentity() + if not luaentity then return end + luaentity._old_visual_size = nil end if change_pos then obj:set_pos(vector.add(obj:get_pos(), vector.new(0, 0.2, 0))) diff --git a/mods/ENTITIES/mcl_burning/init.lua b/mods/ENTITIES/mcl_burning/init.lua index 2e01411b6..077b1e1a8 100644 --- a/mods/ENTITIES/mcl_burning/init.lua +++ b/mods/ENTITIES/mcl_burning/init.lua @@ -16,7 +16,11 @@ dofile(modpath .. "/api.lua") minetest.register_globalstep(function(dtime) for _, player in pairs(get_connected_players()) do local storage = mcl_burning.storage[player] - if not mcl_burning.tick(player, dtime, storage or {}) and not mcl_burning.is_affected_by_rain(player) then + if not storage then + mcl_burning.init_player(player) + storage = mcl_burning.storage[player] + end + if not mcl_burning.tick(player, dtime, storage) and not mcl_burning.is_affected_by_rain(player) then local nodes = mcl_burning.get_touching_nodes(player, {"group:puts_out_fire", "group:set_on_fire"}, storage) local burn_time = 0 diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua index cdcffd81c..6da0d5a1a 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua @@ -893,6 +893,17 @@ function mobs.mob_step(self, dtime) end end + --water damage + if self.water_damage and self.water_damage ~= 0 then + local pos = self.object:get_pos() + local node = minetest_get_node(pos).name + if minetest_get_item_group(node, "water") ~= 0 then + mobs.smoke_effect(self) + self.health = self.health - self.water_damage + self:teleport() + end + end + --set mobs on fire when burned by sunlight if self.ignited_by_sunlight then local pos = self.object:get_pos() diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/mob_effects.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/mob_effects.lua index 83df80992..26513a648 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/mob_effects.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/mob_effects.lua @@ -147,4 +147,24 @@ mobs.breeding_effect = function(self) vertical = false, texture = "heart.png", }) -end \ No newline at end of file +end + +mobs.smoke_effect = function(self) + local pos = self.object:get_pos() + minetest.add_particlespawner({ + amount = 5, + time = 0.25, + minpos = pos, + maxpos = pos, + minvel = {x = -1, y = -1, z = -1}, + maxvel = {x = 1, y = 1, z = 1}, + minacc = {x = 0, y = 10, z = 0}, + maxacc = {x = 0, y = 10, z = 0}, + minexptime = 0.1, + maxexptime = 1, + minsize = 0.5, + maxsize = 1, + collisiondetection = false, + texture = "mcl_particles_smoke.png", + }) +end diff --git a/mods/ENTITIES/mobs_mc/depends.txt b/mods/ENTITIES/mobs_mc/depends.txt deleted file mode 100644 index 96c9f5131..000000000 --- a/mods/ENTITIES/mobs_mc/depends.txt +++ /dev/null @@ -1,2 +0,0 @@ -mcl_mobs -mcl_potions \ No newline at end of file diff --git a/mods/ITEMS/mcl_enchanting/enchantments.lua b/mods/ITEMS/mcl_enchanting/enchantments.lua index 4edd79ec9..21736b630 100644 --- a/mods/ITEMS/mcl_enchanting/enchantments.lua +++ b/mods/ITEMS/mcl_enchanting/enchantments.lua @@ -655,7 +655,7 @@ mcl_enchanting.enchantments.soul_speed = { mcl_enchanting.enchantments.unbreaking = { name = S("Unbreaking"), max_level = 3, - primary = {armor_head = true, armor_torso = true, armor_legs = true, armor_feet = true, pickaxe = true, shovel = true, axe = true, hoe = true, sword = true, fishing_rod = true, bow = true}, + primary = {armor_head = true, armor_torso = true, armor_legs = true, armor_feet = true, crossbow = true, pickaxe = true, shovel = true, axe = true, hoe = true, sword = true, fishing_rod = true, bow = true}, secondary = {tool = true}, disallow = {non_combat_armor = true}, incompatible = {}, diff --git a/mods/ITEMS/mcl_furnaces/init.lua b/mods/ITEMS/mcl_furnaces/init.lua index 9f836d161..d2f357e3c 100644 --- a/mods/ITEMS/mcl_furnaces/init.lua +++ b/mods/ITEMS/mcl_furnaces/init.lua @@ -215,62 +215,11 @@ local function swap_node(pos, name) 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) -- -- 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 src_time = meta:get_float("src_time") or 0 local src_item = meta:get_string("src_item") or "" @@ -294,6 +243,7 @@ local function furnace_node_timer(pos, elapsed) end 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 -- -- 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) -- 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) on_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) end, on_metadata_inventory_put = function(pos) -- 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. minetest.get_node_timer(pos):start(1.0) end, on_metadata_inventory_take = function(pos, listname, index, stack, player) -- 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 minetest.get_node_timer(pos):start(1.0) diff --git a/mods/ITEMS/mcl_furnaces/mod.conf b/mods/ITEMS/mcl_furnaces/mod.conf index fe0b9c208..b2dad2920 100644 --- a/mods/ITEMS/mcl_furnaces/mod.conf +++ b/mods/ITEMS/mcl_furnaces/mod.conf @@ -1,3 +1,3 @@ name = mcl_furnaces -depends = mcl_init, mcl_formspec, mcl_core, mcl_sounds, mcl_craftguide, mcl_achievements, mcl_particles +depends = mcl_init, mcl_formspec, mcl_core, mcl_sounds, mcl_craftguide, mcl_achievements, mcl_particles, mcl_time optional_depends = doc, screwdriver diff --git a/mods/ITEMS/mcl_nether/mod.conf b/mods/ITEMS/mcl_nether/mod.conf index f5ffa61ac..55f635d2c 100644 --- a/mods/ITEMS/mcl_nether/mod.conf +++ b/mods/ITEMS/mcl_nether/mod.conf @@ -1,3 +1,3 @@ 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 diff --git a/mods/ITEMS/mcl_nether/nether_wart.lua b/mods/ITEMS/mcl_nether/nether_wart.lua index 1d2482b5b..0fe1a990a 100644 --- a/mods/ITEMS/mcl_nether/nether_wart.lua +++ b/mods/ITEMS/mcl_nether/nether_wart.lua @@ -6,21 +6,6 @@ local interval = 35 local chance = 11 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", { 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."), @@ -183,6 +168,9 @@ local function grow(pos, node) new_node.param = node.param new_node.param2 = node.param2 minetest.set_node(pos, new_node) + local meta = minetest.get_meta(pos) + meta:set_string("gametime", tostring(mcl_time:get_seconds_irl())) + end minetest.register_abm({ @@ -197,9 +185,10 @@ minetest.register_abm({ return end pos.y = pos.y+1 - grow(pos, node) - local meta = minetest.get_meta(pos) - meta:set_string("gametime", tostring(current_game_time)) + + for i = 1, mcl_time.get_number_of_times_at_pos_or_1(pos, interval, chance) do + grow(pos, node) + end end }) @@ -214,26 +203,14 @@ minetest.register_lbm({ return end pos.y = pos.y+1 - local meta = minetest.get_meta(pos) - 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 + for i = 1, mcl_time.get_number_of_times_at_pos(pos, interval, chance) do grow(pos, node) - real_seconds = real_seconds - threshold - threshold = math.random(interval, max_interval) - i = i + 1 end end }) 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) end end diff --git a/mods/PLAYER/mcl_player/init.lua b/mods/PLAYER/mcl_player/init.lua index f466d30df..9dfb82f33 100644 --- a/mods/PLAYER/mcl_player/init.lua +++ b/mods/PLAYER/mcl_player/init.lua @@ -92,7 +92,6 @@ end local function set_texture(player, index, texture) local textures = player_textures[player:get_player_name()] if not textures then - -- TODO: Check if minetest.register_on_joinplayer() shouldn't really work in minetest 5.1.1? player_textures[player:get_player_name()] = {"blank.png", "blank.png", "blank.png"} textures = player_textures[player:get_player_name()] end