Compare commits

..

42 Commits

Author SHA1 Message Date
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. 23f1c51912 Merge pull request 'ITEMS/mcl_farming: Convert correct floor node to dirt as gourd grows' (#231) from fix-gourd-farmland-dirtification into master
Reviewed-on: Mineclonia/Mineclonia#231
Reviewed-by: Li0n_2 <li0n_2@noreply.git.minetest.land>
2022-01-15 22:07:24 +00:00
Nils Dagsson Moskopp 4d02af8c94
Convert correct floor node to dirt as gourd grows
Before this patch, growing a gourd (e.g. melon, pumpkin) would always
convert a node west of the node below the stem to dirt if belonged to
the group “dirtifies_below_solid”. This happened because of a loop in
which the variables floorpos and floor were re-used without setting a
new value … therefore, both floorpos and floor were always containing
the last values set in a previous loop instead of the correct values.

This patch fixes the problem by setting both variables in both loops.
2022-01-13 07:29:57 +01:00
Li0n_2 7ede0ca79a Merge pull request 'Fix shearsy wool typo in GROUPS.md' (#229) from JosiahWI/Mineclonia:fix-groups-typo into master
Reviewed-on: Mineclonia/Mineclonia#229
Reviewed-by: Li0n_2 <li0n_2@noreply.git.minetest.land>
2022-01-12 21:19:10 +00:00
JosiahWI e2e7e15b39
fix one character typo in GROUPS.md 2022-01-12 11:09:07 -06:00
cora 93a0879b40 Merge pull request 'MISC/mcl_selftests: Do not crash if minetest.find_nodes_in_area() lies' (#214) from fix-selftest-crash into master
Reviewed-on: Mineclonia/Mineclonia#214
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-12-20 22:42:59 +00:00
Nils Dagsson Moskopp 0b5fa14041 Do not crash if minetest.find_nodes_in_area() lies 2021-12-20 21:38:24 +01:00
cora 4f33f626f5 Merge pull request 'ITEMS/mcl_chests: Reskin chests as presents on December 24th to 26th' (#212) from add-christmas-chests into master
Reviewed-on: Mineclonia/Mineclonia#212
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-12-20 16:34:54 +00:00
Nils Dagsson Moskopp df7bd78af5
Add noise to christmas chest textures 2021-12-12 15:40:29 +01:00
Nils Dagsson Moskopp fe3e837e1b
Reskin chests as presents on December 24th to 26th 2021-12-11 18:17:53 +01:00
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. 6f811b3cee Merge pull request 'ITEMS/mcl_chests: Spawn small chest entities only if animated chests are enabled' (#207) from fix-animchest-setting into master
Reviewed-on: Mineclonia/Mineclonia#207
Reviewed-by: erlehmann <nils+git.minetest.land@dieweltistgarnichtso.net>
2021-12-11 14:27:24 +00:00
Lizzy Fleckenstein dfa56f229f
Spawn small chest entities only if animated chests are enabled 2021-12-10 22:15:53 +01:00
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. ee77f33ea8 Merge pull request 'PLAYER/mcl_playerplus: send player object props only if changed' (#147) from fix-active-object-message-spam into master
Reviewed-on: Mineclonia/Mineclonia#147
Reviewed-by: erlehmann <nils+git.minetest.land@dieweltistgarnichtso.net>
2021-12-09 09:31:09 +00:00
cora 5deaabdb47
Fix player bone positions and properties setting
The comparison and setting logic in the previous patch that set player
bone positions and properties conditionally incorrectly did not update
some values (like player eye level position) when they changed. This
patch fixes it and adds asserts to ensure the code works as intended.
2021-12-09 04:53:45 +01:00
cora d77f31eab8
Only set/send player properties if necessary
Before this patch, Mineclonia set (and therefore sent) player bone
positions and player properties in every globalstep. This results in
about 30 TOCLIENT_ACTIVE_OBJECT_MESSAGE per second per player. This
patch adds conditional functions to set bone positions and properties.
The functions set values only when they have changed, reducing traffic.
2021-12-09 04:26:22 +01:00
cora 10670d5c10 Merge pull request 'HUD/mcl_death_messages: Fix crash when skeleton kills player using bow' (#202) from fix-skelly-crash-2 into master
Reviewed-on: Mineclonia/Mineclonia#202
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-12-06 18:42:20 +00:00
Nils Dagsson Moskopp 609105e091
Add test cases for get_tool_name() 2021-12-06 04:11:55 +01:00
cora f9e3c4fd6d
Fix crash when skeleton kills player using bow
Commit 5252952555 used string.gsub() to
strip newlines from tools in death messages. The second return value of
string.gsub() (the number of substitutions) was erroneously returned too
by get_tool_name(). This bug caused a crash whenever a skeleton killed a
player using its bow.
2021-12-06 03:48:39 +01:00
cora d70c05e92c Merge pull request 'ITEMS/mcl_chests: Fix access to ender chest inventory without an ender chest' (#193) from EliasFleckenstein03/Mineclonia:enderchest-fix into master
Reviewed-on: Mineclonia/Mineclonia#193
Reviewed-by: erlehmann <nils+git.minetest.land@dieweltistgarnichtso.net>
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-12-04 13:25:26 +00:00
Lizzy Fleckenstein cacddd3fb4
Fix access to ender inventory without an ender chest 2021-12-03 21:23:10 +01:00
cora dd347c50c5 Merge pull request 'ITEMS/mcl_chests: Add debug command to show ender chest inventory formspec.' (#194) from add-enderchest-command into master
Reviewed-on: Mineclonia/Mineclonia#194
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-12-03 10:53:51 +00:00
Nils Dagsson Moskopp 54b36c42a9
Add debug command to show ender chest inventory formspec. 2021-12-02 19:44:22 +01:00
cora 5252952555 strip newlines from tools in death messages
Players naming their tools with newlines can mess up chat with death
messages. This commit strips out newlines (\r and \n ) in the tool
name for death messages.
2021-12-01 13:25:57 +00:00
Li0n_2 c08f9850f6 Merge pull request 'Add packed ice crafting recipe' (#180) from add-packed-ice-crafting-recipe into master
Reviewed-on: Mineclonia/Mineclonia#180
Reviewed-by: Li0n_2 <li0n_2@noreply.git.minetest.land>
2021-11-27 23:43:28 +00:00
Nils Dagsson Moskopp f8b5066c61
Add packed ice crafting recipe 2021-11-24 06:06:26 +01:00
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. 4b9094ddc2 Merge pull request 'ITEMS/mcl_core: remove swiftness from egapps' (#177) from fix-egapp-speed into master
Reviewed-on: Mineclonia/Mineclonia#177
Reviewed-by: erlehmann <nils+git.minetest.land@dieweltistgarnichtso.net>
2021-11-20 01:21:11 +00:00
cora 5087e8194a remove swiftness from egapps
The minetest world is already so small that you can reach world
border from spawn in about 2 hours. I think the regen and fire
resistance are  good enough to make them special.
2021-11-20 02:14:16 +01:00
cora 71befcff10 Merge pull request 'ENTITIES/mcl_mobs: Give mobs sunburn at correct light level' (#175) from fix-mobs-burning-in-sunlight into master
Reviewed-on: Mineclonia/Mineclonia#175
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-11-19 20:27:55 +00:00
Nils Dagsson Moskopp 66dd87af25
Give mobs sunburn at correct light level
Before this patch, the light level that makes mobs that burn in sunlight
catch fire had been mistakenly specified as minetest.LIGHT_MAX or more.

The correct light level for sunlight is LIGHT_SUN, which is one higher
than minetest.LIGHT_MAX. Since minetest.LIGHT_MAX is the maximum light
value that a node can have and torches emit light at that light level,
mobs that burn in sunlight caught fire inside torch nodes.
2021-11-19 03:14:36 +01:00
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. fc47f0a87e Merge pull request 'CORE/mcl_colors: Add mod providing global color values' (#172) from add-mcl-colors into master
Reviewed-on: Mineclonia/Mineclonia#172
Reviewed-by: Li0n <li0nsdichachu@outlook.com>
2021-11-18 20:57:08 +00:00
Nils Dagsson Moskopp 6bd4b0e62c
Fix typo in mcl_colors API documentation 2021-11-18 19:23:57 +01:00
AFCMS 3473f55136
Add API documentation to mcl_colors 2021-11-18 19:14:21 +01:00
Lizzy Fleckenstein af7f7c3574
Add mcl_colors 2021-11-18 19:13:08 +01:00
cora 434e545745 Merge pull request 'MISC/mcl_engine_workarounds: Add crash fix and tests for minetest.find_nodes_in_area()' (#169) from fix-find-nodes-in-area-3 into master
Reviewed-on: Mineclonia/Mineclonia#169
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-11-18 11:43:57 +00:00
Nils Dagsson Moskopp 76d2846c59
Improve test for minetest.find_nodes_in_area()
• By emerging test areas, the test gets real map data and does not block.
• The test now also ensures that any return values are compared exactly.
• minetest.find_nodes_in_area() is tested with the “grouped” parameter.
2021-11-18 04:47:03 +01:00
Nils Dagsson Moskopp 526c25aa57
Add crash fix and tests for minetest.find_nodes_in_area()
For some specific out of bounds values, the volume calculation in
minetest.find_nodes_in_area() is off by about four million nodes.
Unfortunately that behaviour made it trivial to crash Mineclonia,
as Minetest immediately crashes upon encountering large numbers.

This commit introduces a wrapper around minetest.find_nodes_in_area()
which should avoid a crash. Additionally, three self tests are executed
when a server starts; they crash Mineclonia in case the workaround fails.
2021-11-18 00:55:59 +01:00
My favourite Minetest cheat clients are Dragonfire and Waspsaliva. 249cfb8118 Merge pull request 'ENTITIES/mcl_burning: Fix burning animation packetspam' (#149) from fix-burning-animation-packetspam into master
Reviewed-on: Mineclonia/Mineclonia#149
Reviewed-by: erlehmann <nils+git.minetest.land@dieweltistgarnichtso.net>
2021-11-13 01:34:17 +00:00
cora 84194b71e6 burning: prevent adding multiple entities+huds 2021-11-12 01:16:16 +01:00
cora 0b7097cb28 add a burning animation fps setting 2021-11-12 01:16:16 +01:00
cora bfbc953b92 Merge pull request 'ENTITIES/mcl_burning: Add debug command to burn a player' (#164) from add-burn-command-2 into master
Reviewed-on: Mineclonia/Mineclonia#164
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-11-11 22:25:14 +00:00
Nils Dagsson Moskopp 253100380c
Add debug command to burn a player 2021-11-11 21:37:26 +01:00
cora df98db1d8c Merge pull request 'ITEMS/mcl_banners: Render preview banners with transparent patterns correctly' (#158) from banner-mask-fix-4 into master
Reviewed-on: Mineclonia/Mineclonia#158
Reviewed-by: cora <cora@noreply.git.minetest.land>
2021-11-08 12:21:50 +00:00
Nils Dagsson Moskopp f410de485a
Render preview banners with transparent patterns correctly
Without this fix, the banner pattern preview generation does not mask
the banner pattern, so the alpha channel of the banner pattern is not
taken into account. This lead to preview banners with color gradients
showing up as a solid color banner and opaque pixel artifacts for the
bottom triangle pattern.
2021-10-31 20:53:18 +01:00
33 changed files with 1501 additions and 841 deletions

View File

@ -21,7 +21,7 @@ The basic digging time groups determine by which tools a node can be dug.
* `swordy=1`: Diggable by sword (any material), and this node is *not* a cobweb
* `swordy_cobweb=1`: Diggable by sword (any material), and this node is a cobweb
* `shearsy=1`: Diggable by shears, and this node is *not* wool
* `shearsy=wool=1`: Diggable by shears, and this node is wool
* `shearsy_wool=1`: Diggable by shears, and this node is wool
* `handy=1`: Breakable by hand and this node gives it useful drop when dug by hand. All nodes which are breakable by pickaxe, axe, shovel, sword or shears are also automatically breakable by hand, but not neccess
* `creative_breakable=1`: Block is breakable by hand in creative mode. This group is implied if the node belongs to any other digging group

View File

@ -0,0 +1,8 @@
# mcl_colors
Mod providing global table containing legacy minecraft colors to be used in mods.
## mcl_colors.*
Colors by upper name, in hex value.
## mcl_colors.background.*
Background colors by upper name, in hex value.

View File

@ -0,0 +1,36 @@
mcl_colors = {
BLACK = "#000000",
DARK_BLUE = "#0000AA",
DARK_GREEN = "#00AA00",
DARK_AQUA = "#00AAAA",
DARK_RED = "#AA0000",
DARK_PURPLE = "#AA00AA",
GOLD = "#FFAA00",
GRAY = "#AAAAAA",
DARK_GRAY = "#555555",
BLUE = "#5555FF",
GREEN = "#55FF55",
AQUA = "#55FFFF",
RED = "#FF5555",
LIGHT_PURPLE = "#FF55FF",
YELLOW = "#FFFF55",
WHITE = "#FFFFFF",
background = {
BLACK = "#000000",
DARK_BLUE = "#00002A",
DARK_GREEN = "#002A00",
DARK_AQUA = "#002A2A",
DARK_RED = "#2A0000",
DARK_PURPLE = "#2A002A",
GOLD = "#2A2A00",
GRAY = "#2A2A2A",
DARK_GRAY = "#151515",
BLUE = "#15153F",
GREEN = "#153F15",
AQUA = "#153F3F",
RED = "#3F1515",
LIGHT_PURPLE = "#3F153F",
YELLOW = "#3F3F15",
WHITE = "#373501",
}
}

View File

@ -0,0 +1,3 @@
name = mcl_colors
author = Fleckenstein
description = The HTML sequences for the minecraft colors

View File

@ -155,6 +155,16 @@ function mcl_burning.set_on_fire(obj, burn_time, reason)
}) + 1
end
local already_burning = mcl_burning.is_burning(obj)
mcl_burning.set(obj, "float", "burn_time", burn_time)
mcl_burning.set(obj, "string", "reason", reason)
if already_burning then
return
end
local hud_id
if obj:is_player() then
hud_id = mcl_burning.get(obj, "int", "hud_id")
@ -168,8 +178,7 @@ function mcl_burning.set_on_fire(obj, burn_time, reason)
}) + 1
end
end
mcl_burning.set(obj, "float", "burn_time", burn_time)
mcl_burning.set(obj, "string", "reason", reason)
mcl_burning.set(obj, "int", "hud_id", hud_id)
mcl_burning.set(obj, "int", "sound_id", sound_id)
@ -285,7 +294,7 @@ function mcl_burning.fire_entity_step(self, dtime)
end
local animation_timer = self.animation_timer + dtime
if animation_timer >= 0.015 then
if animation_timer >= ( 1 / mcl_burning.animation_fps ) then
animation_timer = 0
local animation_frame = self.animation_frame + 1
if animation_frame > mcl_burning.animation_frames - 1 then
@ -296,3 +305,48 @@ function mcl_burning.fire_entity_step(self, dtime)
end
self.animation_timer = animation_timer
end
minetest.register_chatcommand("burn", {
params = S("<playername> <duration> <reason>"),
description = S("Sets a player on fire for the given amount of seconds with the given reason."),
privs = { debug = true },
func = function(name, params)
local playername, duration, reason = params:match("^(.+) (.+) (.+)$")
if not (playername and duration and reason) then
return false, S("Error: Parameter missing.")
end
local player = minetest.get_player_by_name(playername)
if not player then
return false, S(
"Error: Player “@1” not found.",
playername
)
end
local duration_number = tonumber(duration)
-- Lua numbers are truthy
-- NaN is not equal to NaN
if not duration_number or (duration_number ~= duration_number) then
return false, S(
"Error: Duration “@1” is not a number.",
duration
)
end
if duration_number < 0 then
return false, S(
"Error: Duration “@1” is negative.",
duration
)
end
mcl_burning.set_on_fire(
player,
duration_number,
reason
)
return true, S(
"Set @1 on fire for @2s for the following reason: @3",
playername,
duration,
reason
)
end,
})

View File

@ -2,7 +2,8 @@ local S = minetest.get_translator("mcl_burning")
local modpath = minetest.get_modpath("mcl_burning")
mcl_burning = {
animation_frames = tonumber(minetest.settings:get("fire_animation_frames")) or 8
animation_frames = tonumber(minetest.settings:get("fire_animation_frames")) or 8,
animation_fps = tonumber(minetest.settings:get("fire_animation_fps")) or 30
}
dofile(modpath .. "/api.lua")

View File

@ -14,6 +14,8 @@ local DEFAULT_FALL_SPEED = -10
local FLOP_HEIGHT = 5.0
local FLOP_HOR_SPEED = 1.5
local LIGHT_SUN = minetest.LIGHT_MAX + 1
local MOB_CAP = {}
MOB_CAP.hostile = 70
MOB_CAP.passive = 10
@ -1057,7 +1059,7 @@ local do_env_damage = function(self)
if mod_worlds then
_, dim = mcl_worlds.y_to_layer(pos.y)
end
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (minetest.get_node_light(pos) or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (minetest.get_node_light(pos) or 0) == LIGHT_SUN and dim == "overworld" then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
else

View File

@ -1,78 +1,5 @@
`mcl_weather`
=======================
This was largely rewritten to use particlespawners (what were you thinking !?) and an event based code flow.
Weathers included
-----------------------
* rain
* snow
* thunder
Commands
-----------------------
`weather <weather>`, requires `weather_manager` privilege.
Dependencies
-----------------------
Lightning has been made a hard dependency since it is included in Mineclonia.
Adding new weathers:
It's as simple as calling a mcl_weather.register_weather(weatherdef):
Weatherdef
-----------------------
{
light_factor = 1,
-- 0-1
cloudcolor= ,
--hexrgb
sound,
--A sound name (loop)
min_duration ,
-- In seconds
max_duration ,
transitions,
--The chances how likely this weather is to change into another weather.
-- e.g.
-- {
-- [65] = "none",
-- [80] = "rain",
-- [100] = "thunder",
-- }
skylayer= ,
-- a skylayer defintion (see skycolor api)
particlespawners = {},
-- a table of particlespawnerdefinitions
start ,
--A function that is run when the weather is about to start
start_player,
--A function per player that is run when the weather is about to start
clear ,
--A function that is run when the weather is about to clear/change
clear_player , -- function per player that is run on change (first argument is the playername)
at_pos ,
--A function that is run for each player with current position as argument. weather is disabled for the player when it returns false
change_at_pos,
--function that is run for each player with current position as argument. return a weather name and the weather transitions into the supplied weather for that player
}
This was the original README.md from mcl2_weather:
Weather mod for MineClone 2. Forked from the `weather_pack` mod by xeranas.
Weathers included

View File

@ -1,385 +1,17 @@
local S = minetest.get_translator("mcl_weather")
local modpath = minetest.get_modpath("mcl_weather")
mcl_weather = {}
mcl_weather.allow_abm = true
mcl_weather.current = "none"
mcl_weather.state = "none"
mcl_weather.current_endtime = 0
mcl_weather.next = nil
mcl_weather.registered_weathers = {}
mcl_weather.min_duration = 600
mcl_weather.max_duration = 9000
local players = {}
players.particlespawners={}
players.soundhandler={}
players.weatheractive = {}
players.weather = {}
local interval=1
local weathercycle_active=false
function mcl_weather.register_weather(name,def)
mcl_weather.registered_weathers[name] = def
-- If not located then embeded skycolor mod version will be loaded.
if minetest.get_modpath("skycolor") == nil then
dofile(modpath.."/skycolor.lua")
end
mcl_weather.register_weather("none",{
min_duration = mcl_weather.min_duration,
max_duration = mcl_weather.max_duration,
transitions = {
[50] = "rain",
[100] = "snow"
}
})
local modpath=minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/skycolor.lua")
dofile(modpath.."/weather_core.lua")
dofile(modpath.."/snow.lua")
dofile(modpath.."/rain.lua")
dofile(modpath.."/nether_dust.lua")
dofile(modpath.."/thunder.lua")
function mcl_weather.get_weatherdef(name)
if mcl_weather.registered_weathers[name] then
return mcl_weather.registered_weathers[name]
end
return mcl_weather.registered_weathers["none"]
if minetest.get_modpath("lightning") ~= nil then
dofile(modpath.."/thunder.lua")
end
function mcl_weather.get_weather()
return mcl_weather.current
end
function mcl_weather.get_current_light_factor()
local lf = mcl_weather.get_weatherdef(mcl_weather.current).light_factor
if mcl_weather.current == "none" or not lf then
return nil
else
return lf
end
end
-- Returns true if pos is outdoor.
-- Outdoor is defined as any node in the Overworld under open sky.
-- FIXME: Nodes below glass also count as “outdoor”, this should not be the case.
function mcl_weather.is_outdoor(pos)
local cpos = {x=pos.x, y=pos.y+1, z=pos.z}
local dim = mcl_worlds.pos_to_dimension(cpos)
if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then
return true
end
return false
end
-- checks if player is undewater. This is needed in order to
-- turn off weather particles generation.
function mcl_weather.is_underwater(player)
local ppos=player:get_pos()
local offset = player:get_eye_offset()
local player_eye_pos = {x = ppos.x + offset.x,
y = ppos.y + offset.y + 1.5,
z = ppos.z + offset.z}
local node_level = minetest.get_node_level(player_eye_pos)
if node_level == 8 or node_level == 7 then
return true
end
return false
end
function mcl_weather.player_has_weather(player)
local pos=player:get_pos()
if mcl_weather.is_outdoor(pos) and not mcl_weather.is_underwater(player) then
return true
end
return false
end
function mcl_weather.add_sound(name,sound)
players.soundhandler[name] = minetest.sound_play(sound, {
to_player = name,
loop = true,
})
end
function mcl_weather.remove_sound(name)
if players.soundhandler[name] then
minetest.sound_fade(players.soundhandler[name], -0.5, 0.0)
end
end
function mcl_weather.add_particlespawners_player(name,spawners)
if not spawners then return end
local player = minetest.get_player_by_name(name)
if not player then return end
if players.particlespawners[name] ~= nil then return end
players.particlespawners[name]={}
for k,v in ipairs(spawners) do
v.playername=name
v.attached=player
table.insert(players.particlespawners[name],minetest.add_particlespawner(v))
end
end
function mcl_weather.delete_particlespawners_player(name)
if players.particlespawners[name] == nil then
players.particlespawners[name]={}
end
for k,v in ipairs(players.particlespawners[name]) do
minetest.delete_particlespawner(v)
end
players.particlespawners[name]=nil
end
local function doplayers(func,players)
players = players or minetest.get_connected_players()
for k,v in ipairs(players) do
func(v:get_player_name(),v)
end
end
function mcl_weather.get_next_weather()
local current=mcl_weather.get_weatherdef(mcl_weather.current)
local random_roll = math.random(0,100)
local new_weather
if current and current.transitions then
for v, weather in pairs(current.transitions) do
if random_roll < v then
new_weather=weather
break
end
end
end
if not new_weather then new_weather = "none" end
return new_weather
end
function mcl_weather.start_weather_player(name,def)
local player=minetest.get_player_by_name(name)
mcl_weather.add_particlespawners_player(name,def.particlespawners)
if def.skycolor then
local player=minetest.get_player_by_name(name)
player:set_clouds({color=def.skycolor})
end
if def.sound then
mcl_weather.add_sound(name,def.sound)
end
players.weather[name]=def
players.weatheractive[name] = true
end
function mcl_weather.stop_weather_player(name,def)
mcl_weather.delete_particlespawners_player(name)
if def.skycolor then
local player=minetest.get_player_by_name(name)
player:set_clouds({color="#FFF0F0E5"})
end
mcl_weather.remove_sound(name,def.sound)
end
function mcl_weather.start_weather(def)
if def.start then def.start() end
if def.skylayer then
mcl_weather.skycolor.add_layer("weather",def.skylayer)
mcl_weather.skycolor.update_sky_color()
end
doplayers(function(name)
if def.start_player == nil or not def.start_player(name) then
mcl_weather.start_weather_player(name,def)
players.weather[name]=mcl_weather.current
end
end)
end
function mcl_weather.stop_weather(def)
if def.clear then def.clear() end
if def.clear_player then doplayers(def.clear_player) end
doplayers(function(name)
mcl_weather.stop_weather_player(name,def)
end)
if def.skylayer then
mcl_weather.skycolor.remove_layer("weather")
mcl_weather.skycolor.force_update=true
mcl_weather.skycolor.update_sky_color()
end
end
function mcl_weather.change(new_weather,force)
if new_weather == mcl_weather.current then return end
local def=mcl_weather.get_weatherdef(new_weather)
local old=mcl_weather.get_weatherdef(mcl_weather.current)
if not def then return end
if force or minetest.settings:get_bool("mcl_doWeatherCycle") then
mcl_weather.stop_weather(old)
mcl_weather.start_weather(def)
mcl_weather.current = new_weather
mcl_weather.state = new_weather
end
end
function mcl_weather.seasonal_change()
local new=mcl_weather.get_next_weather()
local def=mcl_weather.get_weatherdef(new)
local duration = math.random(def.min_duration,def.max_duration)
mcl_weather.current_endtime = os.time() + duration
minetest.log("action", "[mcl_weather] Weather changed from " .. mcl_weather.current .. " to " .. new .. " Duration: "..duration)
mcl_weather.change(new)
minetest.after(duration,mcl_weather.seasonal_change)
end
minetest.register_on_mods_loaded(function()
minetest.after(5,mcl_weather.seasonal_change)
end)
function mcl_weather.change_player(name,new)
if players.weather[name] ~= new then
local nd = mcl_weather.get_weatherdef(new)
local od=mcl_weather.get_weatherdef(mcl_weather.current)
if players.weather[name] ~= nil then
minetest.log("action", "[mcl_weather] Weather for ".. name .." changed from "..players.weather[name].. " to "..new)
od=mcl_weather.get_weatherdef(players.weather[name])
else
minetest.log("action", "[mcl_weather] Weather for ".. name .." changed from "..mcl_weather.current.. " to "..new)
end
if nd then
mcl_weather.stop_weather_player(name,od)
mcl_weather.start_weather_player(name,nd)
players.weather[name] = new
end
end
end
function mcl_weather.tick()
doplayers(function(name,player)
local pos=player:get_pos()
local cdef=mcl_weather.get_weatherdef(mcl_weather.current)
if type(cdef.change_at_pos) == "function" then --switch to returned weather in at_pos conditions
local cap=cdef.change_at_pos(pos)
if cap then
mcl_weather.change_player(name,cap)
else
mcl_weather.change_player(name,mcl_weather.current)
end
end
if players.weatheractive[name] then --turn off weather indoows/underwater
if not mcl_weather.player_has_weather(player) then
mcl_weather.stop_weather_player(name,cdef)
players.weatheractive[name] = false
end
if type(cdef.at_pos) == "function" and cdef.at_pos(pos) then
mcl_weather.stop_weather_player(name,cdef)
players.weatheractive[name] = false
end
else
if mcl_weather.player_has_weather(player) then
mcl_weather.start_weather_player(name,cdef)
players.weather[name]=mcl_weather.current
players.weatheractive[name] = true
end
if type(cdef.at_pos) == "function" and not cdef.at_pos(pos) then
mcl_weather.start_weather_player(name,cdef)
players.weatheractive[name] = false
end
end
end)
minetest.after(interval,mcl_weather.tick)
end
mcl_weather.tick()
mcl_worlds.register_on_dimension_change(function(player, dimension)
if mcl_worlds.has_weather(player:get_pos()) then
mcl_weather.start_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
else
mcl_weather.stop_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
end
end)
minetest.register_on_joinplayer(function(player)
mcl_weather.start_weather_player(player:get_player_name(),mcl_weather.get_weatherdef(mcl_weather.current))
end)
minetest.register_on_leaveplayer(function(player)
cla_weather.stop_weather_player(player:get_player_name())
end)
minetest.register_privilege("weather_manager", {
description = S("Gives ability to control weather"),
give_to_singleplayer = false
})
-- Weather command definition. Set
minetest.register_chatcommand("weather", {
params = "(clear | rain | snow | thunder) [<duration>]",
description = S("Changes the weather to the specified parameter."),
privs = {weather_manager = true},
func = function(name, param)
if (param == "") then
return false, S("Error: No weather specified.")
end
local player=minetest.get_player_by_name(name)
local new_weather, duration
local parse1, parse2 = string.match(param, "(%w+) ?(%d*)")
if parse1 then
if parse1 == "clear" then
new_weather = "none"
else
new_weather = parse1
end
else
return false, S("Error: Invalid parameters.")
end
if parse2 then
if type(tonumber(parse2)) == "number" then
duration = tonumber(parse2)
if duration < 1 then
return false, S("Error: Duration can't be less than 1 second.")
end
end
end
local def=mcl_weather.get_weatherdef(param)
local old_weather=mcl_weather.current
if def then
if duration then
def.min_duration=duration
def.max_duration=duration
end
mcl_weather.change(param,true)
minetest.log("action", "[mcl_weather] " .. name .. " changed the weather from " .. old_weather .. " to " .. param)
return true
end
return false, S("Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”.")
end
})
minetest.register_chatcommand("biome", {
params = "",
description = S("Gets current biome"),
privs = {weather_manager = true},
func = function(name)
local player=minetest.get_player_by_name(name)
local biome=minetest.get_biome_data(player:get_pos())
local bname = minetest.get_biome_name(biome.biome)
minetest.chat_send_player(name,bname.. " " ..dump(biome))
end
})
minetest.register_chatcommand("toggledownfall", {
params = "",
description = S("Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)"),
privs = {weather_manager = true},
func = function(name, param)
-- Currently rain/thunder/snow: Set weather to clear
if mcl_weather.state ~= "none" then
mcl_weather.change("none",true)
-- Currently clear: Set weather randomly to rain/thunder/snow
else
local new = { "rain", "thunder", "snow" }
local r = math.random(1, #new)
return mcl_weather.change(new[r],true)
end
end
})

View File

@ -1,3 +1,4 @@
name = mcl_weather
description = Weather and sky handling: Rain, snow, thunderstorm, End and Nether ambience
depends = mcl_init, mcl_worlds, lightning
depends = mcl_init, mcl_worlds
optional_depends = lightning

View File

@ -1,70 +1,36 @@
mcl_weather.nether_dust = {}
mcl_weather.nether_dust.particlespawners = {}
mcl_weather.nether_dust.particles_count = 99
local psdef= {
amount = 99,
time = 0,
minpos = vector.new(-25,0,-25),
maxpos =vector.new(25,4,25),
minvel = vector.new(-0.3,-0.15,-1),
maxvel = vector.new(0.3,0.15,0.3),
minacc = vector.new(-1,-0.4,-1),
maxacc = vector.new(1,0.4,1),
minexptime = 1,
maxexptime = 1,
minsize = 0.1,
maxsize = 2,
collisiondetection = false,
collision_removal = false,
object_collision = false,
vertical = false,
glow = math.random(0,minetest.LIGHT_MAX)
}
local function check_player(player)
local name=player:get_player_name(name)
if mcl_worlds.has_dust(player:get_pos()) and not mcl_weather.nether_dust.particlespawners[name] then
return true
end
end
mcl_weather.nether_dust.add_particlespawners = function(player)
if not check_player(player) then return end
local name=player:get_player_name(name)
mcl_weather.nether_dust.particlespawners[name]={}
psdef.playername = name
psdef.attached = player
for i=1,3 do
psdef.texture="mcl_particles_nether_dust"..i..".png"
mcl_weather.nether_dust.particlespawners[name][i]=minetest.add_particlespawner(psdef)
-- calculates coordinates and draw particles for Nether dust
mcl_weather.nether_dust.add_dust_particles = function(player)
for i=mcl_weather.nether_dust.particles_count, 1,-1 do
local rpx, rpy, rpz = mcl_weather.get_random_pos_by_player_look_dir(player)
minetest.add_particle({
pos = {x = rpx, y = rpy - math.random(6, 18), z = rpz},
velocity = {x = math.random(-30,30)*0.01, y = math.random(-15,15)*0.01, z = math.random(-30,30)*0.01},
acceleration = {x = math.random(-50,50)*0.02, y = math.random(-20,20)*0.02, z = math.random(-50,50)*0.02},
expirationtime = 3,
size = math.random(6,20)*0.01,
collisiondetection = false,
object_collision = false,
vertical = false,
glow = math.random(0,minetest.LIGHT_MAX),
texture = "mcl_particles_nether_dust"..tostring(i%3+1)..".png",
playername = player:get_player_name()
})
end
end
mcl_weather.nether_dust.delete_particlespawners = function(player)
local name=player:get_player_name(name)
if mcl_weather.nether_dust.particlespawners[name] then
for i=1,3 do
minetest.delete_particlespawner(mcl_weather.nether_dust.particlespawners[name][i])
end
mcl_weather.nether_dust.particlespawners[name]=nil
end
end
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < 0.7 then return end
timer = 0
mcl_worlds.register_on_dimension_change(function(player, dimension)
if dimension == "nether" then
if check_player(player) then
mcl_weather.nether_dust.add_particlespawners(player)
for _, player in ipairs(minetest.get_connected_players()) do
if not mcl_worlds.has_dust(player:get_pos()) then
return false
end
else
mcl_weather.nether_dust.delete_particlespawners(player)
mcl_weather.nether_dust.add_dust_particles(player)
end
end)
minetest.register_on_joinplayer(function(player)
if check_player(player) then
mcl_weather.nether_dust.add_particlespawners(player)
end
end)
minetest.register_on_leaveplayer(function(player)
mcl_weather.nether_dust.delete_particlespawners(player)
end)

View File

@ -1,105 +1,200 @@
local PARTICLES_COUNT_RAIN = 30
local PARTICLES_COUNT_THUNDER = 45
mcl_weather.rain = {
-- max rain particles created at time
particles_count = PARTICLES_COUNT_RAIN,
-- flag to turn on/off extinguish fire for rain
extinguish_fire = true,
-- flag useful when mixing weathers
raining = false
}
mcl_weather.raining = false
raining = false,
mcl_weather.register_weather("rain",{
light_factor = 0.6,
cloudcolor= "#5D5D5FE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[70] = "none",
[100] = "thunder",
},
skylayer= {{r=0, g=0, b=0},
-- keeping last timeofday value (rounded).
-- Defaulted to non-existing value for initial comparing.
sky_last_update = -1,
init_done = false,
}
mcl_weather.rain.sound_handler = function(player)
return minetest.sound_play("weather_rain", {
to_player = player:get_player_name(),
loop = true,
})
end
-- set skybox based on time (uses skycolor api)
mcl_weather.rain.set_sky_box = function()
if mcl_weather.state == "rain" then
mcl_weather.skycolor.add_layer(
"weather-pack-rain-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=98},
{r=135, g=135, b=151},
{r=85, g=86, b=98},
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 800,
time = 0,
minpos = vector.new(-10,10,-10),
maxpos =vector.new(10,15,10),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-30,0),
minexptime = 1,
maxexptime = 2,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_1.png"
},
{
amount = 200,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,15,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-40,0),
minexptime = 1,
maxexptime = 1,
minsize = 0.5,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_2.png"
},
{
amount = 200,
time = 0,
minpos = vector.new(-5,0,-5),
maxpos = vector.new(5,0.8,5),
minvel = vector.new(-4, 5,-4),
maxvel = vector.new(4,10,4),
minacc = vector.new(0,10,0),
maxacc = vector.new(0,25,0),
minexptime = 0.01,
maxexptime = 0.04,
minsize = 0.5,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
texture = "weather_pack_rain_raindrop_2.png",
glow = 0
}
},
start = function() mcl_weather.rain.raining = true end,
clear = function() mcl_weather.rain.raining = false end,
at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if mcl_worlds.has_weather(pos) and biome.heat > 95 then
return true
end
end,
change_at_pos=function(pos)
local biome=minetest.get_biome_data(pos)
if mcl_worlds.has_weather(pos) and ( biome.heat < 15 or pos.y > 64 ) then
return "snow"
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#5D5D5FE8"})
end
end
})
end
-- creating manually parctiles instead of particles spawner because of easier to control
-- spawn position.
mcl_weather.rain.add_rain_particles = function(player)
mcl_weather.rain.last_rp_count = 0
for i=mcl_weather.rain.particles_count, 1,-1 do
local random_pos_x, random_pos_y, random_pos_z = mcl_weather.get_random_pos_by_player_look_dir(player)
if mcl_weather.is_outdoor({x=random_pos_x, y=random_pos_y, z=random_pos_z}) then
mcl_weather.rain.last_rp_count = mcl_weather.rain.last_rp_count + 1
minetest.add_particle({
pos = {x=random_pos_x, y=random_pos_y, z=random_pos_z},
velocity = {x=0, y=-10, z=0},
acceleration = {x=0, y=-30, z=0},
expirationtime = 1.0,
size = math.random(0.5, 3),
collisiondetection = true,
collision_removal = true,
vertical = true,
texture = mcl_weather.rain.get_texture(),
playername = player:get_player_name()
})
end
end
end
-- Simple random texture getter
mcl_weather.rain.get_texture = function()
local texture_name
local random_number = math.random()
if random_number > 0.33 then
texture_name = "weather_pack_rain_raindrop_1.png"
elseif random_number > 0.66 then
texture_name = "weather_pack_rain_raindrop_2.png"
else
texture_name = "weather_pack_rain_raindrop_3.png"
end
return texture_name;
end
-- register player for rain weather.
-- basically needs for origin sky reference and rain sound controls.
mcl_weather.rain.add_player = function(player)
if mcl_weather.players[player:get_player_name()] == nil then
local player_meta = {}
player_meta.origin_sky = {player:get_sky()}
mcl_weather.players[player:get_player_name()] = player_meta
end
end
-- remove player from player list effected by rain.
-- be sure to remove sound before removing player otherwise soundhandler reference will be lost.
mcl_weather.rain.remove_player = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil and player_meta.origin_sky ~= nil then
player:set_clouds({color="#FFF0F0E5"})
mcl_weather.players[player:get_player_name()] = nil
end
end
mcl_worlds.register_on_dimension_change(function(player, dimension)
if dimension ~= "overworld" and dimension ~= "void" then
mcl_weather.rain.remove_sound(player)
mcl_weather.rain.remove_player(player)
elseif dimension == "overworld" then
mcl_weather.rain.update_sound(player)
if mcl_weather.rain.raining then
mcl_weather.rain.add_rain_particles(player)
mcl_weather.rain.add_player(player)
end
end
end)
-- adds and removes rain sound depending how much rain particles around player currently exist.
-- have few seconds delay before each check to avoid on/off sound too often
-- when player stay on 'edge' where sound should play and stop depending from random raindrop appearance.
mcl_weather.rain.update_sound = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil then
if player_meta.sound_updated ~= nil and player_meta.sound_updated + 5 > minetest.get_gametime() then
return false
end
if player_meta.sound_handler ~= nil then
if mcl_weather.rain.last_rp_count == 0 then
minetest.sound_fade(player_meta.sound_handler, -0.5, 0.0)
player_meta.sound_handler = nil
end
elseif mcl_weather.rain.last_rp_count > 0 then
player_meta.sound_handler = mcl_weather.rain.sound_handler(player)
end
player_meta.sound_updated = minetest.get_gametime()
end
end
-- rain sound removed from player.
mcl_weather.rain.remove_sound = function(player)
local player_meta = mcl_weather.players[player:get_player_name()]
if player_meta ~= nil and player_meta.sound_handler ~= nil then
minetest.sound_fade(player_meta.sound_handler, -0.5, 0.0)
player_meta.sound_handler = nil
player_meta.sound_updated = nil
end
end
-- callback function for removing rain
mcl_weather.rain.clear = function()
mcl_weather.rain.raining = false
mcl_weather.rain.sky_last_update = -1
mcl_weather.rain.init_done = false
mcl_weather.rain.set_particles_mode("rain")
mcl_weather.skycolor.remove_layer("weather-pack-rain-sky")
for _, player in ipairs(minetest.get_connected_players()) do
mcl_weather.rain.remove_sound(player)
mcl_weather.rain.remove_player(player)
end
end
minetest.register_globalstep(function(dtime)
if mcl_weather.state ~= "rain" then
return false
end
mcl_weather.rain.make_weather()
end)
mcl_weather.rain.make_weather = function()
if mcl_weather.rain.init_done == false then
mcl_weather.rain.raining = true
mcl_weather.rain.set_sky_box()
mcl_weather.rain.set_particles_mode(mcl_weather.mode)
mcl_weather.rain.init_done = true
end
for _, player in ipairs(minetest.get_connected_players()) do
if (mcl_weather.is_underwater(player) or not mcl_worlds.has_weather(player:get_pos())) then
mcl_weather.rain.remove_sound(player)
return false
end
mcl_weather.rain.add_player(player)
mcl_weather.rain.add_rain_particles(player)
mcl_weather.rain.update_sound(player)
end
end
-- Switch the number of raindrops: "thunder" for many raindrops, otherwise for normal raindrops
mcl_weather.rain.set_particles_mode = function(mode)
if mode == "thunder" then
mcl_weather.rain.particles_count = PARTICLES_COUNT_THUNDER
else
mcl_weather.rain.particles_count = PARTICLES_COUNT_RAIN
end
end
if mcl_weather.allow_abm then
-- ABM for extinguish fire
@ -168,4 +263,19 @@ if mcl_weather.allow_abm then
end
end
})
end
end
if mcl_weather.reg_weathers.rain == nil then
mcl_weather.reg_weathers.rain = {
clear = mcl_weather.rain.clear,
light_factor = 0.6,
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[65] = "none",
[70] = "snow",
[100] = "thunder",
}
}
end

View File

@ -70,7 +70,7 @@ mcl_weather.skycolor = {
else
arg = ratio
end
if arg and arg >= 0 and arg <= 1 then player:override_day_night_ratio(arg) end
player:override_day_night_ratio(arg)
end,
-- Update sky color. If players not specified update sky for all players.

View File

@ -1,65 +1,96 @@
mcl_weather.register_weather("snow",{
light_factor = 0.6,
cloudcolor= "#ADADADE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[65] = "none",
[80] = "rain",
[100] = "thunder",
},
skylayer= {{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 99,
time = 0,
minpos = vector.new(-15,-5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-1,0),
maxvel = vector.new(0,-4,0),
minacc = vector.new(0,-1,0),
maxacc = vector.new(0,-4,0),
minexptime = 2,
maxexptime = 5,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_snow_snowflake1.png"
},{
amount = 30,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-1,0),
maxvel = vector.new(0,-5,0),
minacc = vector.new(0,-1,0),
maxacc = vector.new(0,-4,0),
minexptime = 0.1,
maxexptime = 0.5,
minsize = 0.5,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_snow_snowflake2.png"
}
},
change_at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if pos.y > 50 or (mcl_worlds.has_weather(pos) and biome.heat > 15 ) then
return "rain"
end
mcl_weather.snow = {}
mcl_weather.snow.particles_count = 15
mcl_weather.snow.init_done = false
-- calculates coordinates and draw particles for snow weather
mcl_weather.snow.add_snow_particles = function(player)
mcl_weather.rain.last_rp_count = 0
for i=mcl_weather.snow.particles_count, 1,-1 do
local random_pos_x, random_pos_y, random_pos_z = mcl_weather.get_random_pos_by_player_look_dir(player)
random_pos_y = math.random() + math.random(player:get_pos().y - 1, player:get_pos().y + 7)
if minetest.get_node_light({x=random_pos_x, y=random_pos_y, z=random_pos_z}, 0.5) == 15 then
mcl_weather.rain.last_rp_count = mcl_weather.rain.last_rp_count + 1
minetest.add_particle({
pos = {x=random_pos_x, y=random_pos_y, z=random_pos_z},
velocity = {x = math.random(-100,100)*0.001, y = math.random(-300,-100)*0.004, z = math.random(-100,100)*0.001},
acceleration = {x = 0, y=0, z = 0},
expirationtime = 8.0,
size = 1,
collisiondetection = true,
collision_removal = true,
object_collision = false,
vertical = false,
texture = mcl_weather.snow.get_texture(),
playername = player:get_player_name()
})
end
end
})
end
mcl_weather.snow.set_sky_box = function()
mcl_weather.skycolor.add_layer(
"weather-pack-snow-sky",
{{r=0, g=0, b=0},
{r=85, g=86, b=86},
{r=135, g=135, b=135},
{r=85, g=86, b=86},
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#ADADADE8"})
end
mcl_weather.skycolor.active = true
end
mcl_weather.snow.clear = function()
mcl_weather.skycolor.remove_layer("weather-pack-snow-sky")
mcl_weather.snow.init_done = false
end
-- Simple random texture getter
mcl_weather.snow.get_texture = function()
return "weather_pack_snow_snowflake"..math.random(1,2)..".png"
end
local timer = 0
minetest.register_globalstep(function(dtime)
if mcl_weather.state ~= "snow" then
return false
end
timer = timer + dtime;
if timer >= 0.5 then
timer = 0
else
return
end
if mcl_weather.snow.init_done == false then
mcl_weather.snow.set_sky_box()
mcl_weather.snow.init_done = true
end
for _, player in ipairs(minetest.get_connected_players()) do
if (mcl_weather.is_underwater(player) or not mcl_worlds.has_weather(player:get_pos())) then
return false
end
mcl_weather.snow.add_snow_particles(player)
end
end)
-- register snow weather
if mcl_weather.reg_weathers.snow == nil then
mcl_weather.reg_weathers.snow = {
clear = mcl_weather.snow.clear,
light_factor = 0.6,
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[65] = "none",
[80] = "rain",
[100] = "thunder",
}
}
end

View File

@ -1,110 +1,61 @@
-- turn off lightning mod 'auto mode'
lightning.auto = false
mcl_weather.thunder = {
next_strike = 0,
min_delay = 3,
max_delay = 12
max_delay = 12,
init_done = false,
}
lightning.auto=false
local thunderactive=false
local function do_thunder(start)
if (start and not thunderactive) or mcl_weather.current == "thunder" then
local delay = math.random(mcl_weather.thunder.min_delay, mcl_weather.thunder.max_delay)
minetest.after(delay,do_thunder)
thunderactive=true
else
thunderactive=false
minetest.register_globalstep(function(dtime)
if mcl_weather.get_weather() ~= "thunder" then
return false
end
minetest.chat_send_all("thunder "..mcl_weather.current)
lightning.strike()
end
mcl_weather.rain.set_particles_mode("thunder")
mcl_weather.rain.make_weather()
mcl_weather.register_weather("thunder",{
light_factor = 0.33,
cloudcolor= "#3D3D3FE8" ,
sound = "weather_rain",
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[100] = "rain"
},
skylayer= {{r=0, g=0, b=0},
if mcl_weather.thunder.init_done == false then
mcl_weather.skycolor.add_layer(
"weather-pack-thunder-sky",
{{r=0, g=0, b=0},
{r=40, g=40, b=40},
{r=85, g=86, b=86},
{r=40, g=40, b=40},
{r=0, g=0, b=0}},
particlespawners = {
{
amount = 1200,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,10,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-30,0),
minexptime = 1,
maxexptime = 1,
minsize = 2,
maxsize = 7,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_1.png"
},
{
amount = 800,
time = 0,
minpos = vector.new(-15,5,-15),
maxpos =vector.new(15,15,15),
minvel = vector.new(0,-8,0),
maxvel = vector.new(0,-12,0),
minacc = vector.new(0,-20,0),
maxacc = vector.new(0,-40,0),
minexptime = 1,
maxexptime = 1,
minsize = 1,
maxsize = 5,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
glow = 1,
texture="weather_pack_rain_raindrop_2.png"
},
{
amount = 350,
time = 0,
minpos = vector.new(-5,0,-5),
maxpos = vector.new(5,2,5),
minvel = vector.new(-4, 5,-4),
maxvel = vector.new(4,10,4),
minacc = vector.new(0,10,0),
maxacc = vector.new(0,30,0),
minexptime = 0.01,
maxexptime = 0.04,
minsize = 1,
maxsize = 3,
collisiondetection = true,
collision_removal = true,
object_collision = true,
vertical = true,
texture = "weather_pack_rain_raindrop_2.png",
glow = 1
}
},
start = function()
do_thunder(true)
end,
clear = function()
mcl_weather.skycolor.remove_layer("lightning")
end,
at_pos = function(pos)
local biome=minetest.get_biome_data(pos)
if not mcl_worlds.has_weather(pos) and biome.heat > 95 then
return true
end
end
})
{r=0, g=0, b=0}})
mcl_weather.skycolor.active = true
for _, player in ipairs(minetest.get_connected_players()) do
player:set_clouds({color="#3D3D3FE8"})
end
mcl_weather.thunder.init_done = true
end
if (mcl_weather.thunder.next_strike <= minetest.get_gametime()) then
lightning.strike()
local delay = math.random(mcl_weather.thunder.min_delay, mcl_weather.thunder.max_delay)
mcl_weather.thunder.next_strike = minetest.get_gametime() + delay
end
end)
mcl_weather.thunder.clear = function()
mcl_weather.rain.clear()
mcl_weather.skycolor.remove_layer("weather-pack-thunder-sky")
mcl_weather.skycolor.remove_layer("lightning")
mcl_weather.thunder.init_done = false
end
-- register thunderstorm weather
if mcl_weather.reg_weathers.thunder == nil then
mcl_weather.reg_weathers.thunder = {
clear = mcl_weather.thunder.clear,
light_factor = 0.33333,
-- 10min - 20min
min_duration = 600,
max_duration = 1200,
transitions = {
[100] = "rain",
}
}
end

View File

@ -0,0 +1,291 @@
local S = minetest.get_translator("mcl_weather")
-- weather states, 'none' is default, other states depends from active mods
mcl_weather.state = "none"
-- player list for saving player meta info
mcl_weather.players = {}
-- default weather check interval for global step
mcl_weather.check_interval = 5
-- weather min duration
mcl_weather.min_duration = 600
-- weather max duration
mcl_weather.max_duration = 9000
-- weather calculated end time
mcl_weather.end_time = nil
-- registered weathers
mcl_weather.reg_weathers = {}
-- global flag to disable/enable ABM logic.
mcl_weather.allow_abm = true
mcl_weather.reg_weathers["none"] = {
min_duration = mcl_weather.min_duration,
max_duration = mcl_weather.max_duration,
light_factor = nil,
transitions = {
[50] = "rain",
[100] = "snow",
},
clear = function() end,
}
local storage = minetest.get_mod_storage()
-- Save weather into mod storage, so it can be loaded after restarting the server
local save_weather = function()
storage:set_string("mcl_weather_state", mcl_weather.state)
storage:set_int("mcl_weather_end_time", mcl_weather.end_time)
minetest.log("verbose", "[mcl_weather] Weather data saved: state="..mcl_weather.state.." end_time="..mcl_weather.end_time)
end
minetest.register_on_shutdown(save_weather)
mcl_weather.get_rand_end_time = function(min_duration, max_duration)
local r
if min_duration ~= nil and max_duration ~= nil then
r = math.random(min_duration, max_duration)
else
r = math.random(mcl_weather.min_duration, mcl_weather.max_duration)
end
return minetest.get_gametime() + r
end
mcl_weather.get_current_light_factor = function()
if mcl_weather.state == "none" then
return nil
else
return mcl_weather.reg_weathers[mcl_weather.state].light_factor
end
end
-- Returns true if pos is outdoor.
-- Outdoor is defined as any node in the Overworld under open sky.
-- FIXME: Nodes below glass also count as “outdoor”, this should not be the case.
mcl_weather.is_outdoor = function(pos)
local cpos = {x=pos.x, y=pos.y+1, z=pos.z}
local dim = mcl_worlds.pos_to_dimension(cpos)
if minetest.get_node_light(cpos, 0.5) == 15 and dim == "overworld" then
return true
end
return false
end
-- checks if player is undewater. This is needed in order to
-- turn off weather particles generation.
mcl_weather.is_underwater = function(player)
local ppos = player:get_pos()
local offset = player:get_eye_offset()
local player_eye_pos = {x = ppos.x + offset.x,
y = ppos.y + offset.y + 1.5,
z = ppos.z + offset.z}
local node_level = minetest.get_node_level(player_eye_pos)
if node_level == 8 or node_level == 7 then
return true
end
return false
end
-- trying to locate position for particles by player look direction for performance reason.
-- it is costly to generate many particles around player so goal is focus mainly on front view.
mcl_weather.get_random_pos_by_player_look_dir = function(player)
local look_dir = player:get_look_dir()
local player_pos = player:get_pos()
local random_pos_x = 0
local random_pos_y = 0
local random_pos_z = 0
if look_dir.x > 0 then
if look_dir.z > 0 then
random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
else
random_pos_x = math.random() + math.random(player_pos.x - 2.5, player_pos.x + 5)
random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
end
else
if look_dir.z > 0 then
random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
random_pos_z = math.random() + math.random(player_pos.z - 2.5, player_pos.z + 5)
else
random_pos_x = math.random() + math.random(player_pos.x - 5, player_pos.x + 2.5)
random_pos_z = math.random() + math.random(player_pos.z - 5, player_pos.z + 2.5)
end
end
random_pos_y = math.random() + math.random(player_pos.y + 10, player_pos.y + 15)
return random_pos_x, random_pos_y, random_pos_z
end
local t, wci = 0, mcl_weather.check_interval
minetest.register_globalstep(function(dtime)
t = t + dtime
if t < wci then return end
t = 0
if mcl_weather.end_time == nil then
mcl_weather.end_time = mcl_weather.get_rand_end_time()
end
-- recalculate weather
if mcl_weather.end_time <= minetest.get_gametime() then
local changeWeather = minetest.settings:get_bool("mcl_doWeatherCycle")
if changeWeather == nil then
changeWeather = true
end
if changeWeather then
mcl_weather.set_random_weather(mcl_weather.state, mcl_weather.reg_weathers[mcl_weather.state])
else
mcl_weather.end_time = mcl_weather.get_rand_end_time()
end
end
end)
-- Sets random weather (which could be 'none' (no weather)).
mcl_weather.set_random_weather = function(weather_name, weather_meta)
if weather_meta == nil then return end
local transitions = weather_meta.transitions
local random_roll = math.random(0,100)
local new_weather
for v, weather in pairs(transitions) do
if random_roll < v then
new_weather = weather
break
end
end
if new_weather then
mcl_weather.change_weather(new_weather)
end
end
-- Change weather to new_weather.
-- * explicit_end_time is OPTIONAL. If specified, explicitly set the
-- gametime (minetest.get_gametime) in which the weather ends.
-- * changer is OPTIONAL, for logging purposes.
mcl_weather.change_weather = function(new_weather, explicit_end_time, changer_name)
local changer_name = changer_name or debug.getinfo(2).name.."()"
if (mcl_weather.reg_weathers ~= nil and mcl_weather.reg_weathers[new_weather] ~= nil) then
if (mcl_weather.state ~= nil and mcl_weather.reg_weathers[mcl_weather.state] ~= nil) then
mcl_weather.reg_weathers[mcl_weather.state].clear()
end
local old_weather = mcl_weather.state
mcl_weather.state = new_weather
if old_weather == "none" then
old_weather = "clear"
end
if new_weather == "none" then
new_weather = "clear"
end
minetest.log("action", "[mcl_weather] " .. changer_name .. " changed the weather from " .. old_weather .. " to " .. new_weather)
local weather_meta = mcl_weather.reg_weathers[mcl_weather.state]
if explicit_end_time then
mcl_weather.end_time = explicit_end_time
else
mcl_weather.end_time = mcl_weather.get_rand_end_time(weather_meta.min_duration, weather_meta.max_duration)
end
mcl_weather.skycolor.update_sky_color()
save_weather()
return true
end
return false
end
mcl_weather.get_weather = function()
return mcl_weather.state
end
minetest.register_privilege("weather_manager", {
description = S("Gives ability to control weather"),
give_to_singleplayer = false
})
-- Weather command definition. Set
minetest.register_chatcommand("weather", {
params = "(clear | rain | snow | thunder) [<duration>]",
description = S("Changes the weather to the specified parameter."),
privs = {weather_manager = true},
func = function(name, param)
if (param == "") then
return false, S("Error: No weather specified.")
end
local new_weather, end_time
local parse1, parse2 = string.match(param, "(%w+) ?(%d*)")
if parse1 then
if parse1 == "clear" then
new_weather = "none"
else
new_weather = parse1
end
else
return false, S("Error: Invalid parameters.")
end
if parse2 then
if type(tonumber(parse2)) == "number" then
local duration = tonumber(parse2)
if duration < 1 then
return false, S("Error: Duration can't be less than 1 second.")
end
end_time = minetest.get_gametime() + duration
end
end
local success = mcl_weather.change_weather(new_weather, end_time, name)
if success then
return true
else
return false, S("Error: Invalid weather specified. Use “clear”, “rain”, “snow” or “thunder”.")
end
end
})
minetest.register_chatcommand("toggledownfall", {
params = "",
description = S("Toggles between clear weather and weather with downfall (randomly rain, thunderstorm or snow)"),
privs = {weather_manager = true},
func = function(name, param)
-- Currently rain/thunder/snow: Set weather to clear
if mcl_weather.state ~= "none" then
return mcl_weather.change_weather("none", nil, name)
-- Currently clear: Set weather randomly to rain/thunder/snow
else
local new = { "rain", "thunder", "snow" }
local r = math.random(1, #new)
return mcl_weather.change_weather(new[r], nil, name)
end
end
})
-- Configuration setting which allows user to disable ABM for weathers (if they use it).
-- Weather mods expected to be use this flag before registering ABM.
local weather_allow_abm = minetest.settings:get_bool("weather_allow_abm")
if weather_allow_abm ~= nil and weather_allow_abm == false then
mcl_weather.allow_abm = false
end
local load_weather = function()
local weather = storage:get_string("mcl_weather_state")
if weather and weather ~= "" then
mcl_weather.state = weather
mcl_weather.end_time = storage:get_int("mcl_weather_end_time")
mcl_weather.change_weather(weather, mcl_weather.end_time)
if type(mcl_weather.end_time) ~= "number" then
-- Fallback in case of corrupted end time
mcl_weather.end_time = mcl_weather.min_duration
end
minetest.log("action", "[mcl_weather] Weather restored.")
else
minetest.log("action", "[mcl_weather] No weather data found. Starting with clear weather.")
end
end
load_weather()

View File

@ -3,13 +3,106 @@ local N = function(s) return s end
local function get_tool_name(item)
local name = item:get_meta():get_string("name")
if name ~= "" then
return name
if name == "" then
local def = item:get_definition()
name=def._tt_original_description or def.description
end
local def = item:get_definition()
return def._tt_original_description or def.description
local sanitized_name, substitution_count = name:gsub("[\r\n]"," ")
return sanitized_name
end
local test_tool_1a = {
get_meta = function()
return {
get_string = function()
return "foo 1a"
end
}
end
}
assert( get_tool_name(test_tool_1a) == "foo 1a" )
local test_tool_1b = {
get_meta = function()
return {
get_string = function()
return "bar\rbaz\n1b"
end
}
end
}
assert( get_tool_name(test_tool_1b) == "bar baz 1b" )
local test_tool_2a = {
get_definition = function()
return {
_tt_original_description = "foo 2a"
}
end,
get_meta = function()
return {
get_string = function()
return ""
end
}
end
}
assert( get_tool_name(test_tool_2a) == "foo 2a" )
local test_tool_2b = {
get_definition = function()
return {
_tt_original_description = "bar\rbaz\n2b"
}
end,
get_meta = function()
return {
get_string = function()
return ""
end
}
end
}
assert( get_tool_name(test_tool_2b) == "bar baz 2b" )
local test_tool_3a = {
get_definition = function()
return {
description = "foo 3a"
}
end,
get_meta = function()
return {
get_string = function()
return ""
end
}
end
}
assert( get_tool_name(test_tool_3a) == "foo 3a" )
local test_tool_3b = {
get_definition = function()
return {
description = "bar\rbaz\n3b"
}
end,
get_meta = function()
return {
get_string = function()
return ""
end
}
end
}
assert( get_tool_name(test_tool_3b) == "bar baz 3b" )
mcl_death_messages = {}
-- Death messages

View File

@ -387,13 +387,15 @@ for colorid, colortab in pairs(mcl_banners.colors) do
-- redraw the pattern textures as low-resolution pixel
-- art and use that instead.
local layer = "(([combine:20x40:-2,-2="..pattern.."^[resize:16x24^[colorize:"..color..":"..layer_ratio.."))"
local layer = "([combine:20x40:-2,-2=" .. pattern .. "^[resize:16x24^[colorize:" .. color .. ":" .. layer_ratio .. ")"
local mask = "([combine:20x40:-2,-2=" .. pattern .. "^[resize:16x24" .. ")"
function escape(text)
return text:gsub("%^", "\\%^"):gsub(":", "\\:") -- :gsub("%(", "\\%("):gsub("%)", "\\%)")
end
finished_banner = "[combine:32x32:0,0=" .. escape(base) .. ":8,4=" .. escape(layer)
local layer_masked = layer .. "^[mask:" .. escape(mask)
finished_banner = "[combine:32x32:0,0=" .. escape(base) .. ":8,4=" .. escape(layer_masked)
end
inv = finished_banner

View File

@ -1,6 +1,41 @@
local S = minetest.get_translator("mcl_chests")
local mod_doc = minetest.get_modpath("doc")
-- Christmas chest setup
local it_is_christmas = false
local date = os.date("*t")
if (
date.month == 12 and (
date.day == 24 or
date.day == 25 or
date.day == 26
)
) then
it_is_christmas = true
end
local tiles_chest_normal_small = {"mcl_chests_normal.png"}
local tiles_chest_normal_double = {"mcl_chests_normal_double.png"}
if it_is_christmas then
tiles_chest_normal_small = {"mcl_chests_normal_present.png^mcl_chests_noise.png"}
tiles_chest_normal_double = {"mcl_chests_normal_double_present.png^mcl_chests_noise_double.png"}
end
local tiles_chest_trapped_small = {"mcl_chests_trapped.png"}
local tiles_chest_trapped_double = {"mcl_chests_trapped_double.png"}
if it_is_christmas then
tiles_chest_trapped_small = {"mcl_chests_trapped_present.png^mcl_chests_noise.png"}
tiles_chest_trapped_double = {"mcl_chests_trapped_double_present.png^mcl_chests_noise_double.png"}
end
local tiles_chest_ender_small = {"mcl_chests_ender.png"}
if it_is_christmas then
tiles_chest_ender_small = {"mcl_chests_ender_present.png^mcl_chests_noise.png"}
end
-- Chest Entity
local animate_chests = (minetest.settings:get_bool("animated_chests") ~= false)
local entity_animations = {
@ -152,7 +187,10 @@ if minetest.get_modpath("screwdriver") then
local nodename = node.name
local nodedef = minetest.registered_nodes[nodename]
local dir = minetest.facedir_to_dir(new_param2)
find_or_create_entity(pos, nodename, nodedef._chest_entity_textures, new_param2, false, nodedef._chest_entity_sound, nodedef._chest_entity_mesh, nodedef._chest_entity_animation_type, dir):set_yaw(dir)
if animate_chests then
find_or_create_entity(pos, nodename, nodedef._chest_entity_textures, new_param2, false, nodedef._chest_entity_sound, nodedef._chest_entity_mesh, nodedef._chest_entity_animation_type, dir):set_yaw(dir)
end
else
return false
end
@ -181,6 +219,10 @@ local player_chest_open = function(player, pos, node_name, textures, param2, dou
local dir = minetest.facedir_to_dir(param2)
local blocked = not shulker and (back_is_blocked(pos, dir) or double and back_is_blocked(mcl_util.get_double_container_neighbor_pos(pos, param2, node_name:sub(-4)), dir))
find_or_create_entity(pos, node_name, textures, param2, double, sound, mesh, shulker and "shulker" or "chest", dir):open(name, blocked)
else
minetest.sound_play(sound .. "_open", {
pos = pos,
})
end
end
@ -212,11 +254,14 @@ local chest_update_after_close = function(pos)
if node.name == "mcl_chests:trapped_chest_on_small" then
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_small", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_small", {"mcl_chests_trapped.png"}, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small")
if animate_chests then
find_or_create_entity(pos, "mcl_chests:trapped_chest_small", tiles_chest_trapped_small, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small")
end
mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
elseif node.name == "mcl_chests:trapped_chest_on_left" then
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
find_or_create_entity(pos, "mcl_chests:trapped_chest_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
@ -228,7 +273,7 @@ local chest_update_after_close = function(pos)
local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
end
end
@ -242,6 +287,10 @@ local player_chest_close = function(player)
end
if animate_chests then
find_or_create_entity(open_chest.pos, open_chest.node_name, open_chest.textures, open_chest.param2, open_chest.double, open_chest.sound, open_chest.mesh, open_chest.shulker and "shulker" or "chest"):close(name)
else
minetest.sound_play(open_chest.sound .. "_close", {
pos = open_chest.pos,
})
end
chest_update_after_close(open_chest.pos)
@ -380,12 +429,21 @@ minetest.register_node(small_name, {
_doc_items_longdesc = longdesc,
_doc_items_usagehelp = usagehelp,
_doc_items_hidden = hidden,
drawtype = "nodebox",
node_box = {
drawtype = animate_chests and "nodebox" or "mesh",
mesh = not animate_chests and "mcl_chests_chest.obj" or nil,
node_box = animate_chests and {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
} or nil,
collision_box = {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
},
tiles = {"mcl_chests_blank.png"},
selection_box = {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
},
tiles = animate_chests and {"mcl_chests_blank.png"} or small_textures,
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "clip" or true,
_chest_entity_textures = small_textures,
_chest_entity_sound = "default_chest",
@ -435,7 +493,10 @@ minetest.register_node(small_name, {
minetest.swap_node(p, { name = "mcl_chests:"..canonical_basename.."_right", param2 = param2 })
else
minetest.swap_node(pos, { name = "mcl_chests:"..canonical_basename.."_small", param2 = param2 })
create_entity(pos, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest")
if animate_chests then
create_entity(pos, small_name, small_textures, param2, false, "default_chest", "mcl_chests_chest", "chest")
end
end
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
@ -823,8 +884,8 @@ register_chest("chest",
chestusage,
S("27 inventory slots") .. "\n" .. S("Can be combined to a large chest"),
{
small = {"mcl_chests_normal.png"},
double = {"mcl_chests_normal_double.png"},
small = tiles_chest_normal_small,
double = tiles_chest_normal_double,
inv = {"default_chest_top.png", "mcl_chests_chest_bottom.png",
"mcl_chests_chest_right.png", "mcl_chests_chest_left.png",
"mcl_chests_chest_back.png", "default_chest_front.png"},
@ -839,8 +900,8 @@ register_chest("chest",
)
local traptiles = {
small = {"mcl_chests_trapped.png"},
double = {"mcl_chests_trapped_double.png"},
small = tiles_chest_trapped_small,
double = tiles_chest_trapped_double,
inv = {"mcl_chests_chest_trapped_top.png", "mcl_chests_chest_trapped_bottom.png",
"mcl_chests_chest_trapped_right.png", "mcl_chests_chest_trapped_left.png",
"mcl_chests_chest_trapped_back.png", "mcl_chests_chest_trapped_front.png"},
@ -865,7 +926,9 @@ register_chest("trapped_chest",
}},
function(pos, node, clicker)
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_on_small", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_on_small", {"mcl_chests_trapped.png"}, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_small")
if animate_chests then
find_or_create_entity(pos, "mcl_chests:trapped_chest_on_small", tiles_chest_trapped_small, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_small")
end
mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
end,
function(pos, node, clicker)
@ -873,7 +936,7 @@ register_chest("trapped_chest",
meta:set_int("players", 1)
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_on_left", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_on_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left")
find_or_create_entity(pos, "mcl_chests:trapped_chest_on_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left")
mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
@ -887,7 +950,7 @@ register_chest("trapped_chest",
mesecon.receptor_on(pos, trapped_chest_mesecons_rules)
minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_on_left", param2 = node.param2})
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_on_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left")
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_on_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_on_left")
mesecon.receptor_on(pos_other, trapped_chest_mesecons_rules)
end
)
@ -908,13 +971,15 @@ local function close_if_trapped_chest(pos, player)
if node.name == "mcl_chests:trapped_chest_on_small" then
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_small", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_small", {"mcl_chests_trapped.png"}, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small")
if animate_chests then
find_or_create_entity(pos, "mcl_chests:trapped_chest_small", tiles_chest_trapped_small, node.param2, false, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_small")
end
mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
player_chest_close(player)
elseif node.name == "mcl_chests:trapped_chest_on_left" then
minetest.swap_node(pos, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
find_or_create_entity(pos, "mcl_chests:trapped_chest_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
find_or_create_entity(pos, "mcl_chests:trapped_chest_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
mesecon.receptor_off(pos, trapped_chest_mesecons_rules)
local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "left")
@ -928,7 +993,7 @@ local function close_if_trapped_chest(pos, player)
local pos_other = mcl_util.get_double_container_neighbor_pos(pos, node.param2, "right")
minetest.swap_node(pos_other, {name="mcl_chests:trapped_chest_left", param2 = node.param2})
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", {"mcl_chests_trapped_double.png"}, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
find_or_create_entity(pos_other, "mcl_chests:trapped_chest_left", tiles_chest_trapped_double, node.param2, true, "default_chest", "mcl_chests_chest", "chest"):reinitialize("mcl_chests:trapped_chest_left")
mesecon.receptor_off(pos_other, trapped_chest_mesecons_rules)
player_chest_close(player)
@ -976,7 +1041,7 @@ minetest.register_node("mcl_chests:ender_chest", {
_doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."),
drawtype = "mesh",
mesh = "mcl_chests_chest.obj",
tiles = {"mcl_chests_ender.png"},
tiles = tiles_chest_ender_small,
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
paramtype = "light",
paramtype2 = "facedir",
@ -1002,22 +1067,38 @@ local formspec_ender_chest = "size[9,8.75]"..
"listring[current_player;enderchest]"..
"listring[current_player;main]"
minetest.register_chatcommand("enderchest", {
description = S("Show ender chest inventory formspec."),
privs = { debug = true },
func = function(name, params)
minetest.show_formspec(name, "enderchest:enderchest", formspec_ender_chest)
end
})
minetest.register_node("mcl_chests:ender_chest_small", {
description = S("Ender Chest"),
_tt_help = S("27 interdimensional inventory slots") .. "\n" .. S("Put items inside, retrieve them from any ender chest"),
_doc_items_longdesc = S("Ender chests grant you access to a single personal interdimensional inventory with 27 slots. This inventory is the same no matter from which ender chest you access it from. If you put one item into one ender chest, you will find it in all other ender chests. Each player will only see their own items, but not the items of other players."),
_doc_items_usagehelp = S("Rightclick the ender chest to access your personal interdimensional inventory."),
drawtype = "nodebox",
node_box = {
drawtype = animate_chests and "nodebox" or "mesh",
mesh = not animate_chests and "mcl_chests_chest.obj" or nil,
node_box = animate_chests and {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.5, 0.375, 0.4375},
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
} or nil,
collision_box = {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
},
_chest_entity_textures = {"mcl_chests_ender.png"},
selection_box = {
type = "fixed",
fixed = {-0.4375, -0.5, -0.4375, 0.4375, 0.375, 0.4375},
},
tiles = animate_chests and {"mcl_chests_blank.png"} or tiles_chest_ender_small,
_chest_entity_textures = tiles_chest_ender_small,
_chest_entity_sound = "mcl_chests_enderchest",
_chest_entity_mesh = "mcl_chests_chest",
_chest_entity_animation_type = "chest",
tiles = {"mcl_chests_blank.png"},
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "clip" or true,
--[[{"mcl_chests_ender_chest_top.png", "mcl_chests_ender_chest_bottom.png",
"mcl_chests_ender_chest_right.png", "mcl_chests_ender_chest_left.png",
@ -1034,10 +1115,13 @@ minetest.register_node("mcl_chests:ender_chest_small", {
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", formspec_ender_chest)
create_entity(pos, "mcl_chests:ender_chest_small", {"mcl_chests_ender.png"}, minetest.get_node(pos).param2, false, "mcl_chests_enderchest", "mcl_chests_chest", "chest")
if animate_chests then
create_entity(pos, "mcl_chests:ender_chest_small", tiles_chest_ender_small, minetest.get_node(pos).param2, false, "mcl_chests_enderchest", "mcl_chests_chest", "chest")
end
end,
on_rightclick = function(pos, node, clicker)
player_chest_open(clicker, pos, "mcl_chests:ender_chest_small", {"mcl_chests_ender.png"}, node.param2, false, "mcl_chests_enderchest", "mcl_chests_chest")
player_chest_open(clicker, pos, "mcl_chests:ender_chest_small", tiles_chest_ender_small, node.param2, false, "mcl_chests_enderchest", "mcl_chests_chest")
end,
on_receive_fields = function(pos, formname, fields, sender)
if fields.quit then
@ -1055,6 +1139,20 @@ minetest.register_on_joinplayer(function(player)
inv:set_size("enderchest", 9*3)
end)
minetest.register_allow_player_inventory_action(function(player, action, inv, info)
if inv:get_location().type == "player" and (
action == "move" and (info.from_list == "enderchest" or info.to_list == "enderchest")
or action == "put" and info.listname == "enderchest"
or action == "take" and info.listname == "enderchest"
) then
local def = player:get_wielded_item():get_definition()
if not minetest.find_node_near(player:get_pos(), def and def.range or ItemStack():get_definition().range, "mcl_chests:ender_chest_small", true) then
return 0
end
end
end)
minetest.register_craft({
output = 'mcl_chests:ender_chest',
recipe = {
@ -1215,8 +1313,9 @@ for color, desc in pairs(boxtypes) do
_doc_items_entry_name = entry_name,
_doc_items_longdesc = longdesc,
_doc_items_usagehelp = usagehelp,
drawtype = "nodebox",
tiles = {"mcl_chests_blank.png"},
drawtype = animate_chests and "nodebox" or "mesh",
mesh = not animate_chests and "mcl_chests_shulker.obj" or nil,
tiles = animate_chests and {"mcl_chests_blank.png"} or {mob_texture},
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "clip" or true,
_chest_entity_textures = {mob_texture},
_chest_entity_sound = "mcl_chests_shulker",
@ -1237,7 +1336,10 @@ for color, desc in pairs(boxtypes) do
meta:set_string("formspec", formspec_shulker_box(nil))
local inv = meta:get_inventory()
inv:set_size("main", 9*3)
create_entity(pos, small_name, {mob_texture}, minetest.get_node(pos).param2, false, "mcl_chests_shulker", "mcl_chests_shulker", "shulker")
if animate_chests then
create_entity(pos, small_name, {mob_texture}, minetest.get_node(pos).param2, false, "mcl_chests_shulker", "mcl_chests_shulker", "shulker")
end
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
local nmeta = minetest.get_meta(pos)
@ -1356,6 +1458,11 @@ local function select_and_spawn_entity(pos, node)
local node_name = node.name
local node_def = minetest.registered_nodes[node_name]
local double_chest = minetest.get_item_group(node_name, "double_chest") > 0
if not animate_chests and not double_chest then
return
end
find_or_create_entity(pos, node_name, node_def._chest_entity_textures, node.param2, double_chest, node_def._chest_entity_sound, node_def._chest_entity_mesh, node_def._chest_entity_animation_type)
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@ -382,8 +382,14 @@ minetest.register_craft({
}
})
-- TODO: Add crafting recipe: 9 ice → 1 packed ice
-- Add it when silk touch tools work.
minetest.register_craft({
output = 'mcl_core:packed_ice 1',
recipe = {
{'mcl_core:ice', 'mcl_core:ice', 'mcl_core:ice'},
{'mcl_core:ice', 'mcl_core:ice', 'mcl_core:ice'},
{'mcl_core:ice', 'mcl_core:ice', 'mcl_core:ice'},
}
})
--
-- Crafting (tool repair)

View File

@ -165,7 +165,7 @@ local function eat_gapple(itemstack, placer, pointed_thing)
mcl_potions.fire_resistance_func(placer, 1, 300)
mcl_potions.leaping_func(placer, 1, 300)
end
mcl_potions.swiftness_func(placer, absorbtion_factor, 120) -- TODO: Absorbtion
-- TODO: Absorbtion
mcl_potions.regeneration_func(placer, 2, regen_duration)
return gapple_hunger_restore(itemstack, placer, pointed_thing)
end

View File

@ -388,12 +388,11 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
{ x=0, y=0, z=-1 },
{ x=0, y=0, z=1 },
}
local floorpos, floor
for n=#neighbors, 1, -1 do
local offset = neighbors[n]
local blockpos = vector.add(stempos, offset)
floorpos = { x=blockpos.x, y=blockpos.y-1, z=blockpos.z }
floor = minetest.get_node(floorpos)
local floorpos = { x=blockpos.x, y=blockpos.y-1, z=blockpos.z }
local floor = minetest.get_node(floorpos)
local block = minetest.get_node(blockpos)
local soilgroup = minetest.get_item_group(floor.name, "soil")
if not ((minetest.get_item_group(floor.name, "grass_block") == 1 or floor.name=="mcl_core:dirt" or soilgroup == 2 or soilgroup == 3) and block.name == "air") then
@ -407,6 +406,8 @@ function mcl_farming:add_gourd(full_unconnected_stem, connected_stem_basename, s
local r = math.random(1, #neighbors)
local offset = neighbors[r]
local blockpos = vector.add(stempos, offset)
local floorpos = { x=blockpos.x, y=blockpos.y-1, z=blockpos.z }
local floor = minetest.get_node(floorpos)
local p2
if offset.x == 1 then
minetest.set_node(stempos, {name=connected_stem_names[1]})

View File

@ -0,0 +1,206 @@
local clamp = function(value, min, max)
assert( min < max )
if value < min then
return min
end
if value > max then
return max
end
return value
end
assert( clamp(000, -100, 100) == 000 )
assert( clamp(999, -100, 100) == 100 )
assert( clamp(999, -999, 999) == 999 )
assert( clamp(998, 999, 1999) == 999 )
assert( clamp(999, 999, 1999) == 999 )
local clamp_s16 = function(value)
-- seems minetest hangs on -32768 and 32767
return clamp(value, -32767, 32766)
end
assert( clamp_s16(000000) == 000000 )
assert( clamp_s16(000001) == 000001 )
assert( clamp_s16(000010) == 000010 )
assert( clamp_s16(000100) == 000100 )
assert( clamp_s16(001000) == 001000 )
assert( clamp_s16(010000) == 010000 )
assert( clamp_s16(100000) == 032766 )
assert( clamp_s16(-00000) == -00000 )
assert( clamp_s16(-00009) == -00009 )
assert( clamp_s16(-00099) == -00099 )
assert( clamp_s16(-00999) == -00999 )
assert( clamp_s16(-09999) == -09999 )
assert( clamp_s16(-99999) == -32767 )
local minetest_find_nodes_in_area = minetest.find_nodes_in_area
minetest.find_nodes_in_area = function(minp, maxp, ...)
if
minp.x >= 32767 or minp.x <= -32768 or
minp.y >= 32767 or minp.y <= -32768 or
minp.z >= 32767 or minp.z <= -32768 or
maxp.x >= 32767 or maxp.x <= -32768 or
maxp.y >= 32767 or maxp.y <= -32768 or
maxp.z >= 32767 or maxp.z <= -32768
then
minetest.log(
"warning",
"find_nodes_in_area() called with coords outside interval (-32768, 32767), clamping arguments: " ..
"minp { x=" .. minp.x .. ", y=" .. minp.y .. " z=" .. minp.z .. " } " ..
"maxp { x=" .. maxp.x .. ", y=" .. maxp.y .. " z=" .. maxp.z .. " } "
)
return minetest_find_nodes_in_area(
{ x=clamp_s16(minp.x), y=clamp_s16(minp.y), z=clamp_s16(minp.z) },
{ x=clamp_s16(maxp.x), y=clamp_s16(maxp.y), z=clamp_s16(maxp.z) },
...
)
else
return minetest_find_nodes_in_area(
minp,
maxp,
...
)
end
end
deep_compare = function(a, b)
local type_a = type(a)
local type_b = type(b)
if type_a ~= type_b then
return false
end
if type_a ~= "table" and type_b ~= "table" then
return a == b
end
for key_a, value_a in pairs(a) do
local value_b = b[key_a]
if not deep_compare(value_a, value_b) then
return false
end
end
for key_b, value_b in pairs(b) do
local value_a = a[key_b]
if not deep_compare(value_b, value_a) then
return false
end
end
return true
end
assert(
deep_compare(
1,
1.0
) == true
)
assert(
deep_compare(
true,
"true"
) == false
)
assert(
deep_compare(
{ a=1, b=-2, c=3.4 },
{ a=1, b=-2, c=3.4 }
) == true
)
assert(
deep_compare(
{ a={ 1, 2, 3 }, b="foo", c=false },
{ a={ 1, 2, 3 }, b="foo", c=false }
) == true
)
assert(
deep_compare(
{ a={ 1, 2, 3 }, b="foo", c=false },
{ a={ 4, 5, 6 }, b="foo", c=false }
) == false
)
assert(
deep_compare(
{ a={ 1, 2, 3 }, b={ c=false } },
{ a={ 1, 2, 3 }, b={ c=false } }
) == true
)
assert(
deep_compare(
{ a={ 1, 2, 3 }, b={ } },
{ a={ 1, 2, 3 }, b={ c=false } }
) == false
)
local test_minetest_find_nodes_in_area_implementation_equivalence = function()
-- If any assertion in this test function fails, the wrapper
-- for minetest.find_nodes_in_area() does not behave like the
-- original function. If you are reading the code because your
-- server crashed, please inform the Mineclonia developers.
local fun_1 = minetest_find_nodes_in_area
local fun_2 = minetest.find_nodes_in_area
for x = -31000, 31000, 15500 do
for y = -31000, 31000, 15500 do
for z = -31000, 31000, 15500 do
for d = 1, 9, 3 do
local minp = { x=x, y=y, z=z }
local maxp = { x=x+d, y=y+d, z=z+d }
minetest.emerge_area(
minp,
maxp,
function(blockpos, action, calls_remaining)
local npos_1, nnum_1 = fun_1(
minp,
maxp,
{ "air", "ignore" }
)
local npos_2, nnum_2 = fun_2(
minp,
maxp,
{ "air", "ignore" }
)
assert(
deep_compare(
npos_1,
npos_2
) == true
)
assert(
deep_compare(
nnum_1,
nnum_2
) == true
)
local ntab_1 = fun_1(
minp,
maxp,
{ "air", "ignore" },
true
)
local ntab_2 = fun_2(
minp,
maxp,
{ "air", "ignore" },
true
)
assert(
deep_compare(
ntab_1,
ntab_2
) == true
)
end
)
end
end
end
end
end
minetest.after( 0, test_minetest_find_nodes_in_area_implementation_equivalence )

View File

@ -0,0 +1,121 @@
local test_minetest_find_nodes_in_area_can_count = function(dtime)
-- This function tests that minetest.find_nodes_in_area() can
-- count nodes correctly. If it fails, the engine can not be
-- trusted to actually count how many nodes of a given type
-- are in a given volume. Yes, *it* is bad if that happens.
--
-- If you are looking at this function because it executes at
-- startup and crashes the game, by far the most stupid thing
-- you could do is disabling it. Only an absolute moron would
-- disable tests that ensure basic functionality still works.
--
-- Experience has taught me that such warnings are mostly not
-- taken seriously by both Minetest mod & core developers. As
-- there are very few situations in which someone would read
-- the code of the function without a crash, you are probably
-- asking yourself how bad it can be. Surely you will want an
-- example of what will break if this test breaks. The answer
-- to this simple question is equally simple and consists of a
-- heartfelt “What the fuck did you say, you stupid fuckwad?”.
--
-- Alrighty then, let's get it on …
local pos = { x=30999, y=30999, z=30999 }
-- You think there is nothing there? Well, here is the thing:
-- With standard settings you can only see map until x=30927,
-- although the renderer can actually render up to x=31007 if
-- you configure it to. Any statements given by minetest core
-- devs that contradict the above assertion are probably lies.
--
-- In any way, this area should be so far out that no one has
-- built here … yet. Now that you know it is possible, I know
-- you want to. How though? I suggest to figure the technique
-- out yourself, then go on and build invisible lag machines.
local radius = 3
local minp = vector.subtract(pos, radius)
local maxp = vector.add(pos, radius)
local nodename = "air"
local c_nodename = minetest.get_content_id(nodename)
-- Why not use minetest.set_node() here? Well, some mods do
-- trigger on every placement of a node in the entire map …
-- and we do not want to crash those mods in this test case.
-- (Voxelmanip does not trigger callbacks so all is well.)
--
-- And now for a funny story: I initially copied the following
-- code from the Minetest developer wiki. Can you spot a typo?
-- <https://dev.minetest.net/index.php?title=minetest.get_content_id&action=edit>
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(
minp,
maxp
)
local area = VoxelArea:new({
MinEdge=emin,
MaxEdge=emax
})
local data = vm:get_data()
for z = minp.z, maxp.z do
for y = minp.y, maxp.y do
local vi = area:index(minp.x, y, z)
for x = minp.x, maxp.y do
data[vi] = c_nodename
vi = vi + 1
end
end
end
vm:set_data(data)
vm:write_to_map()
local npos, nnum = minetest.find_nodes_in_area(
minp,
maxp,
{ nodename }
)
local nodes_expected = math.pow( 1 + (2 * radius), 3 )
local nodes_counted = nnum[nodename]
local nodes_difference = nodes_expected - nodes_counted
-- Originally, there was an assertion here that made the game
-- crash at startup if Minetest forgot how to count. This was
-- originally intended to avoid buggy engine releases, but it
-- mostly made people upset and hindered debugging. Also, the
-- assertion contained no error message hinting at the reason
-- for the crash, making it exceptionally user-unfriendly. It
-- follows that a game or mod should only assert on behaviour
-- of the Lua code, not the underlying implementation, unless
-- engine bugs are bad enough to permanently corrupt a world.
if ( 0 ~= nodes_difference ) then
minetest.debug(
"minetest.find_nodes_in_area() failed to find " ..
nodes_difference .. " nodes that were placed. " ..
"Downgrading to Minetest 5.4.1 might fix this."
)
end
end
minetest.after( 0, test_minetest_find_nodes_in_area_can_count )
local test_minetest_find_nodes_in_area_crash = function(dtime)
-- And now for our feature presentation, where we call the
-- function “minetest.find_nodes_in_area()” with a position
-- out of bounds! Will it crash? Who knows‽ If it does, the
-- workaround is not working though, so it should be patched.
local pos = { x=32767, y=32767, z=32767 }
-- Note that not all out of bounds values actually crash the
-- function minetest.find_nodes_in_area()“. In fact, the vast
-- majority of out of bounds values do not crash the function.
local radius = 3
local minp = vector.subtract(pos, radius)
local maxp = vector.add(pos, radius)
local nodename = "air"
local npos, nnum = minetest.find_nodes_in_area(
minp,
maxp,
{ nodename }
)
-- That's all, folks!
end
minetest.after( 0, test_minetest_find_nodes_in_area_crash )

View File

@ -19,8 +19,114 @@ end
local pitch, name, node_stand, node_stand_below, node_head, node_feet, pos
minetest.register_globalstep(function(dtime)
local function roundN(n, d)
if type(n) ~= "number" then return n end
local m = 10^d
return math.floor(n * m + 0.5) / m
end
local function close_enough(a,b)
local rt=true
if type(a) == "table" and type(b) == "table" then
for k,v in pairs(a) do
if roundN(v,2) ~= roundN(b[k],2) then
rt=false
break
end
end
else
rt = roundN(a,2) == roundN(b,2)
end
return rt
end
local function props_changed(props,oldprops)
local changed=false
local p={}
for k,v in pairs(props) do
if not close_enough(v,oldprops[k]) then
p[k]=v
changed=true
end
end
return changed,p
end
--test if assert works
assert(true)
assert(not false)
--test data for == and ~=
local test_equal1=42
local test_equal2=42.0
local test_equal3=42.1
assert(test_equal1==test_equal1)
assert(test_equal1==test_equal2)
assert(test_equal1~=test_equal3)
--testdata for roundN
local test_round1=15
local test_round2=15.00199999999
local test_round3=15.00111111
local test_round4=15.00999999
assert(roundN(test_round1,2)==roundN(test_round1,2)) --test again if basic equality works because wth not
assert(roundN(test_round1,2)==roundN(test_round2,2))
assert(roundN(test_round1,2)==roundN(test_round3,2))
assert(roundN(test_round1,2)~=roundN(test_round4,2))
-- tests for close_enough
local test_cb = {-0.35,0,-0.35,0.35,0.8,0.35} --collisionboxes
local test_cb_close = {-0.351213,0,-0.35,0.35,0.8,0.351212}
local test_cb_diff = {-0.35,0,-1.35,0.35,0.8,0.35}
local test_eh = 1.65 --eye height
local test_eh_close = 1.65123123
local test_eh_diff = 1.35
local test_nt = { r = 225, b = 225, a = 225, g = 225 } --nametag
local test_nt_diff = { r = 225, b = 225, a = 0, g = 225 }
assert(close_enough(test_cb,test_cb_close))
assert(not close_enough(test_cb,test_cb_diff))
assert(close_enough(test_eh,test_eh_close))
assert(not close_enough(test_eh,test_eh_diff))
assert(not close_enough(test_nt,test_nt_diff)) --no floats involved here
--tests for props_changed
local test_properties_set1={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
local test_properties_set2={collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 225, g = 225 }}
local test_p1,p=props_changed(test_properties_set1,test_properties_set1)
local test_p2,p=props_changed(test_properties_set1,test_properties_set2)
assert(not test_p1)
assert(test_p2)
-- we still don't really know if lua is lying to us! but at least everything *seems* to be ok
local function set_properties_conditional(player,props)
local changed,p=props_changed(props,player:get_properties())
if changed then
player:set_properties(p)
end
end
local function set_bone_position_conditional(player,b,p,r) --bone,position,rotation
local oldp,oldr=player:get_bone_position(b)
if vector.equals(vector.round(oldp),vector.round(p)) and vector.equals(vector.round(oldr),vector.round(r)) then
return
end
player:set_bone_position(b,p,r)
end
minetest.register_globalstep(function(dtime)
time = time + dtime
-- Update jump status immediately since we need this info in real time.
@ -45,55 +151,55 @@ minetest.register_globalstep(function(dtime)
-- controls right and left arms pitch when shooting a bow or punching
if string.find(player:get_wielded_item():get_name(), "mcl_bows:bow") and controls.RMB and not controls.LMB and not controls.up and not controls.down and not controls.left and not controls.right then
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch+90,-30,pitch * -1 * .35))
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3.5,5.785,0), vector.new(pitch+90,43,pitch * .35))
set_bone_position_conditional(player,"Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch+90,-30,pitch * -1 * .35))
set_bone_position_conditional(player,"Arm_Left_Pitch_Control", vector.new(3.5,5.785,0), vector.new(pitch+90,43,pitch * .35))
elseif controls.LMB and player:get_attach() == nil then
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch,0,0))
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3,5.785,0), vector.new(0,0,0))
set_bone_position_conditional(player,"Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(pitch,0,0))
set_bone_position_conditional(player,"Arm_Left_Pitch_Control", vector.new(3,5.785,0), vector.new(0,0,0))
else
player:set_bone_position("Arm_Left_Pitch_Control", vector.new(3,5.785,0), vector.new(0,0,0))
player:set_bone_position("Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(0,0,0))
set_bone_position_conditional(player,"Arm_Left_Pitch_Control", vector.new(3,5.785,0), vector.new(0,0,0))
set_bone_position_conditional(player,"Arm_Right_Pitch_Control", vector.new(-3,5.785,0), vector.new(0,0,0))
end
if controls.sneak and player:get_attach() == nil then
-- controls head pitch when sneaking
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch+36,0,0))
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch+36,0,0))
-- sets eye height, and nametag color accordingly
player:set_properties({collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 0, g = 225 }})
set_properties_conditional(player,{collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.35, nametag_color = { r = 225, b = 225, a = 0, g = 225 }})
-- sneaking body conrols
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
elseif minetest.get_item_group(mcl_playerinfo[name].node_head, "water") ~= 0 and player:get_attach() == nil and mcl_sprint.is_sprinting(name) == true then
-- set head pitch and yaw when swimming
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch+90-degrees(dir_to_pitch(player_velocity)),yaw - player_vel_yaw * -1,0))
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch+90-degrees(dir_to_pitch(player_velocity)),yaw - player_vel_yaw * -1,0))
-- sets eye height, and nametag color accordingly
player:set_properties({collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
set_properties_conditional(player,{collisionbox = {-0.35,0,-0.35,0.35,0.8,0.35}, eye_height = 0.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
-- control body bone when swimming
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(degrees(dir_to_pitch(player_velocity)) - 90,player_vel_yaw * -1 - yaw + 180,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(degrees(dir_to_pitch(player_velocity)) - 90,player_vel_yaw * -1 - yaw + 180,0))
elseif player:get_attach() == nil then
-- sets eye height, and nametag color accordingly
player:set_properties({collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
set_properties_conditional(player,{collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
if player_velocity.x > 0.35 or player_velocity.z > 0.35 or player_velocity.x < -0.35 or player_velocity.z < -0.35 then
if player_vel_yaw * -1 - yaw < 90 or player_vel_yaw * -1 - yaw > 270 then
-- controls head and Body_Control bones while moving backwards
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch,yaw - player_vel_yaw * -1,0))
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(0,player_vel_yaw * -1 - yaw,0))
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch,yaw - player_vel_yaw * -1,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(0,player_vel_yaw * -1 - yaw,0))
else
-- controls head and Body_Control bones while moving forwards
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch,yaw - player_vel_yaw * -1 + 180,0))
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(0,player_vel_yaw * -1 - yaw + 180,0))
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch,yaw - player_vel_yaw * -1 + 180,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(0,player_vel_yaw * -1 - yaw + 180,0))
end
else
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch,0,0))
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch,0,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
end
else
local attached = player:get_attach(parent)
local attached_yaw = degrees(attached:get_yaw())
player:set_properties({collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
player:set_bone_position("Head", vector.new(0,6.3,0), vector.new(pitch,degrees(player:get_look_horizontal()) * -1 + attached_yaw,0))
player:set_bone_position("Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
set_properties_conditional(player,{collisionbox = {-0.35,0,-0.35,0.35,1.8,0.35}, eye_height = 1.65, nametag_color = { r = 225, b = 225, a = 225, g = 225 }})
set_bone_position_conditional(player,"Head", vector.new(0,6.3,0), vector.new(pitch,degrees(player:get_look_horizontal()) * -1 + attached_yaw,0))
set_bone_position_conditional(player,"Body_Control", vector.new(0,6.3,0), vector.new(0,0,0))
end
if mcl_playerplus_internal[name].jump_cooldown > 0 then

View File

@ -91,6 +91,11 @@ flame_sound (Flame sound) bool true
# Form: Image height / Image width
fire_animation_frames (Fire Animation Frames) int 8
# How long to wait between frames of the fire animation in frames per second.
# A higher number means it looks better but also results in a lot of additional network traffic. A low single digit value is recommended for multiplayer.
#(default: 30)
fire_animation_fps (Fire Animation FPS) int 30 0 60
# Whether to animate chests when open / close
animated_chests (Animated chests) bool true