local S = minetest.get_translator("mcl_burning") function mcl_burning.get_default(datatype) local default_table = {string = "", float = 0.0, int = 0, bool = false} return default_table[datatype] end function mcl_burning.get(obj, datatype, name) local key if obj:is_player() then local meta = obj:get_meta() return meta["get_" .. datatype](meta, "mcl_burning:" .. name) else local luaentity = obj:get_luaentity() return luaentity and luaentity["mcl_burning_" .. name] or mcl_burning.get_default(datatype) end end function mcl_burning.set(obj, datatype, name, value) if obj:is_player() then local meta = obj:get_meta() meta["set_" .. datatype](meta, "mcl_burning:" .. name, value or mcl_burning.get_default(datatype)) else local luaentity = obj:get_luaentity() if mcl_burning.get_default(datatype) == value then value = nil end luaentity["mcl_burning_" .. name] = value end end function mcl_burning.is_burning(obj) return mcl_burning.get(obj, "float", "burn_time") > 0 end function mcl_burning.is_affected_by_rain(obj) return mcl_weather.get_weather() == "rain" and mcl_weather.is_outdoor(obj:get_pos()) end function mcl_burning.get_collisionbox(obj, smaller) local box = obj:get_properties().collisionbox local minp, maxp = vector.new(box[1], box[2], box[3]), vector.new(box[4], box[5], box[6]) if smaller then local s_vec = vector.new(0.1, 0.1, 0.1) minp = vector.add(minp, s_vec) maxp = vector.subtract(maxp, s_vec) end return minp, maxp end function mcl_burning.get_touching_nodes(obj, nodenames) local pos = obj:get_pos() local box = obj:get_properties().collisionbox local minp, maxp = mcl_burning.get_collisionbox(obj, true) local nodes = minetest.find_nodes_in_area(vector.add(pos, minp), vector.add(pos, maxp), nodenames) return nodes end function mcl_burning.get_highest_group_value(obj, groupname) local nodes = mcl_burning.get_touching_nodes(obj, "group:" .. groupname, true) local highest_group_value = 0 for _, pos in pairs(nodes) do local node = minetest.get_node(pos) local group_value = minetest.get_item_group(node.name, groupname) if group_value > highest_group_value then highest_group_value = group_value end end return highest_group_value end function mcl_burning.damage(obj) local luaentity = obj:get_luaentity() local health if luaentity then health = luaentity.health end local hp = health or obj:get_hp() if hp <= 0 then return end local do_damage = true if obj:is_player() then if mcl_potions.player_has_effect(obj, "fire_proof") then do_damage = false else local name = obj:get_player_name() armor.last_damage_types[name] = "fire" local deathmsg = S("@1 burned to death.", name) local reason = mcl_burning.get(obj, "string", "reason") if reason ~= "" then deathmsg = S("@1 was burned by @2.", name, reason) end mcl_death_messages.player_damage(obj, deathmsg) end else if luaentity.fire_damage_resistant then do_damage = false end end if do_damage then local new_hp = hp - 1 if health then luaentity.health = new_hp else obj:set_hp(new_hp) end end end function mcl_burning.set_on_fire(obj, burn_time, reason) local luaentity = obj:get_luaentity() if luaentity and luaentity.fire_resistant then return end local old_burn_time = mcl_burning.get(obj, "float", "burn_time") local max_fire_prot_lvl = 0 if obj:is_player() then if minetest.is_creative_enabled(obj:get_player_name()) then burn_time = burn_time / 100 end local inv = obj:get_inventory() for i = 2, 5 do local stack = inv:get_stack("armor", i) local fire_prot_lvl = mcl_enchanting.get_enchantment(stack, "fire_protection") max_fire_prot_lvl = math.max(max_fire_prot_lvl, fire_prot_lvl) end end if max_fire_prot_lvl > 0 then burn_time = burn_time - math.floor(burn_time * max_fire_prot_lvl * 0.15) end if old_burn_time <= burn_time then local sound_id = mcl_burning.get(obj, "int", "sound_id") if sound_id == 0 then sound_id = minetest.sound_play("fire_fire", { object = obj, gain = 0.18, max_hear_distance = 16, loop = true, }) + 1 end local hud_id if obj:is_player() then hud_id = mcl_burning.get(obj, "int", "hud_id") if hud_id == 0 then hud_id = obj:hud_add({ hud_elem_type = "image", position = {x = 0.5, y = 0.5}, scale = {x = -100, y = -100}, text = "fire_basic_flame.png", z_index = 1000, }) + 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) local fire_entity = minetest.add_entity(obj:get_pos(), "mcl_burning:fire") local minp, maxp = mcl_burning.get_collisionbox(obj) local obj_size = obj:get_properties().visual_size local vertical_grow_factor = 1.2 local horizontal_grow_factor = 1.1 local grow_vector = vector.new(horizontal_grow_factor, vertical_grow_factor, horizontal_grow_factor) local size = vector.subtract(maxp, minp) size = vector.multiply(size, grow_vector) size = vector.divide(size, obj_size) local offset = vector.new(0, size.y * 10 / 2, 0) fire_entity:set_properties({visual_size = size}) fire_entity:set_attach(obj, "", offset, {x = 0, y = 0, z = 0}) mcl_burning.update_animation_frame(obj, fire_entity, 0) end end function mcl_burning.extinguish(obj) if mcl_burning.is_burning(obj) then local sound_id = mcl_burning.get(obj, "int", "sound_id") - 1 minetest.sound_stop(sound_id) if obj:is_player() then local hud_id = mcl_burning.get(obj, "int", "hud_id") - 1 obj:hud_remove(hud_id) end mcl_burning.set(obj, "string", "reason") mcl_burning.set(obj, "float", "burn_time") mcl_burning.set(obj, "float", "damage_timer") mcl_burning.set(obj, "int", "hud_id") mcl_burning.set(obj, "int", "sound_id") end end function mcl_burning.catch_fire_tick(obj, dtime) if mcl_burning.is_affected_by_rain(obj) or #mcl_burning.get_touching_nodes(obj, "group:puts_out_fire") > 0 then mcl_burning.extinguish(obj) else local set_on_fire_value = mcl_burning.get_highest_group_value(obj, "set_on_fire") if set_on_fire_value > 0 then mcl_burning.set_on_fire(obj, set_on_fire_value) end end end function mcl_burning.tick(obj, dtime) local burn_time = mcl_burning.get(obj, "float", "burn_time") - dtime if burn_time <= 0 then mcl_burning.extinguish(obj) else mcl_burning.set(obj, "float", "burn_time", burn_time) local damage_timer = mcl_burning.get(obj, "float", "damage_timer") + dtime if damage_timer >= 1 then damage_timer = 0 mcl_burning.damage(obj) end mcl_burning.set(obj, "float", "damage_timer", damage_timer) end mcl_burning.catch_fire_tick(obj, dtime) end function mcl_burning.update_animation_frame(obj, fire_entity, animation_frame) local fire_texture = "mcl_burning_entity_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. animation_frame local fire_HUD_texture = "mcl_burning_hud_flame_animated.png^[opacity:180^[verticalframe:" .. mcl_burning.animation_frames .. ":" .. animation_frame fire_entity:set_properties({textures = {"blank.png", "blank.png", fire_texture, fire_texture, fire_texture, fire_texture}}) if obj:is_player() then local hud_id = mcl_burning.get(obj, "int", "hud_id") - 1 obj:hud_change(hud_id, "text", fire_HUD_texture) end end function mcl_burning.fire_entity_step(self, dtime) if self.removed then return end local obj = self.object local parent = obj:get_attach() local do_remove self.doing_step = true if not parent or not mcl_burning.is_burning(parent) then do_remove = true else for _, other in ipairs(minetest.get_objects_inside_radius(obj:get_pos(), 0)) do local luaentity = obj:get_luaentity() if luaentity and luaentity.name == "mcl_burning:fire" and not luaentity.doing_step and not luaentity.removed then do_remove = true break end end end self.doing_step = false if do_remove then self.removed = true obj:remove() return end local animation_timer = self.animation_timer + dtime 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 animation_frame = 0 end mcl_burning.update_animation_frame(parent, obj, animation_frame) self.animation_frame = animation_frame end self.animation_timer = animation_timer end minetest.register_chatcommand("burn", { params = S(" "), 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, })