Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst

This commit is contained in:
N-Nachtigal 2025-05-14 16:36:42 +02:00
parent 95945c0306
commit a0c893ca0b
1124 changed files with 64294 additions and 763 deletions

2
mods/3d_armor_flyswim/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View file

@ -0,0 +1,9 @@
[mod] 3d_armor_flyswim
=======================
Licence code LGPL v2.1
"Headanim" code by LoneWolfHT MIT Licence
Cape Textures - CC0
Blender Model/B3Ds as per base MTG - CC BY-SA 3.0
"3d_armor_trans.png" CC-BY-SA 3.0

View file

@ -0,0 +1,147 @@
_, __, _, __, _, _ _, __,
~_) | \ / \ |_) |\/| / \ |_)
_) |_/ |~| | \ | | \ / | \
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
__, _, , _ _ _, _ _, _, _ _ _ _, _ _, _ _ _, _ _,
|_ | \ | | |\ | / _ (_ | | | |\/| |\/| | |\ | / _
| |_, \| | | \| \ / , ) |/\| | | | | | | | \| \ /
~ ~ ~ ) ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
~'
---------------------------
## Information
---------------------------
This is a small utility mod which adds some new animations to the default animations found in player_api animations, the new animations are:
|Animation| Start | End | FPS |
|---------|-------|-----|-----|
|Swim | 246 | 279 | 30 |
|Swim Atk | 285 | 318 | 30 |
|Fly | 325 | 334 | 30 |
|Fly Atk | 340 | 349 | 30 |
|Fall | 355 | 364 | 30 |
|Fall Atk | 365 | 374 | 30 |
|Duck Std | 380 | 380 | 30 |
|Duck | 381 | 399 | 30 |
|Climb | 410 | 429 | 30 |
I have done my best to trigger the animations in the correct circumstances when viewing the player model in 3rd person.
I have only tested this against minetest versions 5.0 to 5.6
Mod now works with just player_api, 3d_armor is an optional depends.
Works with simple_skins - https://forum.minetest.net/viewtopic.php?f=11&t=9100
Works with skinsdb - https://forum.minetest.net/viewtopic.php?t=17899
Works with clothing 2 - https://forum.minetest.net/viewtopic.php?p=395157
When using skinsdb capes must be supplied by clothing 2.
![swim animation](https://github.com/sirrobzeroone/3d_armor_flyswim/blob/main/swimming_animated.gif)
---------------------------
## Mod explanations and interactions
---------------------------
**player_api**
fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
Below here assumed you stil have player_api enabled/installed:
**simple skins**
fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
**3d_armor**
You have the option to have capes on or off upto you, capes are considered armor
**3d_armor and simple_skins**
You have the option to have capes on or off upto you, capes are considered armor
**skinsdb**
Fly,swim,crouch,climb animations work, enabling capes will do nothing as you cant wear them
**3d_armor and skinsdb**
Fly,swim,crouch,climb animations work you can enable capes but they wont be visible on your character but will still provide any bonuses
**3d_armor, skinsdb and clothing 2**
Fly,swim,crouch,climb animations work, visual cape provided by clothing_2 however can also enable capes but they wont be visible and it's a bit strange wearing two capes. So recommened left disabled.
---------------------------
## Animation rules/triggers
---------------------------
Swimming - You must be in at least 2 nodes depth of liquid/flowing liquid and moving otherwise your character will simply wade through the liquid or float (stand). If you sink close to the bottom while submerged in the liquid your character will automatically stand on the bottom.
Swim through - Hold shift down while swimming, you will sink but will also now be able to swim through 1x1 tunnels.
Flying - Character/player must have fly_privs you must have at least 2 nodes of airlike nodes between you and the ground otherwise your character will simply/stand or walk ready to land. You also need some type of movement horizontally to trigger the animation otherwise your character will simply stand in the air to emulate hovering.
Falling - Character/player must have 4 nodes of airlike nodes between them and the ground. Easiest to trigger if fly_privs have been removed but you can fall with fly_privs. Max falling speed is set by terminal velocity, however holding shift down will let you exceed terminal velocity.
Crouching - Press shift while standing in the open and then walk forward.
Crouching under - Simply walk through a gap 1.5 nodes tall. Player/character must be facing the space and walking forwards.
---------------------------
## Turn off Animation or Crouch Rule
---------------------------
From MT Main Menu go to "Settings" then "All Settings" then "Mods" then "3d_armor_flyswim"
**capes_add_to_3darmor** - Default is enabled - Makes capes an armor item avaliable via 3d_armor (1 test cape included)
**example_cape** - Default is enabled - The example cape "Someones Cape" is avaliable which grants fly_privs when worn and a 100% speed increase
**fly_anim** - Default is enabled - Will show the flying animation
**fall_anim** - Default is enabled - Will show the falling animation
**fall_tv** - Default is 100 - This is terminal velocity, 100 represents approximatly 100kp/h however player/character speed will oscilate around this speed. Without this limit when falling characters simply accelerate endlessly (until they hit chunk load edge).
**swim_anim** - Default is enabled - Will show swim animation
**swim_sneak** - Default is enabled - Will allow character/player to swim through a hole 1x1 in size when underwater.
**climb_anim** - Default is enabled - Will show climb animation
**crouch_anim** - Default is enabled - Will show crouch/duck animation
**crouch_sneak** - Default is enabled - Will allow character/player to walk through a gap 1.5 nodes high when on land.
---------------------------
## Why are Capes Included?
---------------------------
I found it best to keep capes included in this mod with the option to enable or disable. This is because capes needs the new b3d player model so they
are displayed as part of armor instead of part of the player. However as I didn't want to force anyone into using capes as armor items I created a second
optional player model which keeps capes with the player textures.
The above would create a circular dependancy if capes was it's own mod. Capes would have a dependency on Fly/Swim but Fly/Swim needs to know if Capes mod
is present so as to load the correct b3d player model.
Given the above I have kept capes inside this mod with an option to enable/disable it under Settings>>All Settings>>Mods>>3d_armor_flyswim by default capes are set to Enabled/true
Capes provide minimal additional armor, about half as much as wooden boots by default.
--------------------------------------------
## What nodes are set as Flyable/Swimmable?
--------------------------------------------
Any node which has the drawtype set as "airlike", "liquid" and "flowingliquid" will automatically be flyable or swimmable.
Big thanks to Gundul for pointing out a better way to do this.
--------------------------------------------
## Headanimation is incorporated
--------------------------------------------
I have incorporated "headanim" mod content by LoneWolfHT as it was easier to include and then customise for this mod than try to interface
with "headanim". Full credit to LoneWolfHT for the functionality. I did modify the functionality a little so visually when in 3rd person view
Sams head will:
~ Regular animations when looking down Sams chin now rests on his chest (about a 60 degree angle).
~ Regular animations when looking up Sam only bends head back to the same 60 degrees.
~ Swimming and flying you can look down full 90 degrees, straight ahead 0 degrees, however head motion is restricted to 30 degrees back.
Make sure you disable "headanim" if you have it installed, although I found no serious issues with both enabled the mods
could fight one another for control of head position.

View file

@ -0,0 +1 @@
Addition of Fly and Swimming animations to 3d_armour base character model, used when swimming and flyinf or falling avaliable for other mods to make use of.

View file

@ -0,0 +1,28 @@
------------------------------------------------------------
-- ___ _ __ ___ _ --
-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
-- |__/ --
-- Crouch and Climb --
------------------------------------------------------------
-- Example Cape --
------------------------------------------------------------
armor:register_armor("3d_armor_flyswim:demo_cape", {
description = "Someones Cape",
inventory_image = "3d_armor_flyswim_demo_cape_inv.png",
groups = {armor_capes=1, physics_speed=1, armor_use=1000},
armor_groups = {fleshy=5},
damage_groups = {cracky=3, snappy=3, choppy=2, crumbly=2, level=1},
on_equip = function(player)
local privs = minetest.get_player_privs(player:get_player_name())
privs.fly = true
minetest.set_player_privs(player:get_player_name(), privs)
end,
on_unequip = function(player)
local privs = minetest.get_player_privs(player:get_player_name())
privs.fly = nil
minetest.set_player_privs(player:get_player_name(), privs)
end,
})

View file

@ -0,0 +1,186 @@
------------------------------------------------------------
-- ___ _ __ ___ _ --
-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
-- |__/ --
-- Crouch and Climb --
------------------------------------------------------------
-- Functions --
------------------------------------------------------------
----------------------------------------
-- Get Player model and textures
function armor_fly_swim.get_player_model()
-- player_api only (simple_skins uses)
local player_mod = "character_sf.b3d"
local texture = {"character.png",
"3d_armor_trans.png"}
-- 3d_armor only nil capes (simple_skins uses)
if armor_fly_swim.is_3d_armor and
not armor_fly_swim.add_capes and
not armor_fly_swim.is_skinsdb then
player_mod = "3d_armor_character_sf.b3d"
texture = {armor.default_skin..".png",
"3d_armor_trans.png",
"3d_armor_trans.png"}
end
-- 3d_armor only with capes (simple_skins uses)
if armor_fly_swim.is_3d_armor and
armor_fly_swim.add_capes and
not armor_fly_swim.is_skinsdb then
player_mod = "3d_armor_character_sfc.b3d"
texture = {armor.default_skin..".png",
"3d_armor_trans.png",
"3d_armor_trans.png"}
end
-- skins_db with 3d_armor or without
if armor_fly_swim.is_skinsdb then
player_mod = "skinsdb_3d_armor_character_5.b3d"
texture = {"blank.png",
"blank.png",
"blank.png",
"blank.png"}
end
return player_mod,texture
end
----------------------------------------
-- Get WASD, pressed = true
function armor_fly_swim.get_wasd_state(controls)
local rtn = false
if controls.up == true or
controls.down == true or
controls.left == true or
controls.right == true then
rtn = true
end
return rtn
end
----------------------------------------
-- Check specific node fly/swim
-- 1=player feet, 2=one below feet,
-- Thanks Gundul
function node_fsable(pos,num,type)
local draw_ta = {"airlike"}
local draw_tl = {"liquid","flowingliquid"}
local compare = draw_ta
local node = minetest.get_node({x=pos.x,y=pos.y-(num-1),z=pos.z})
local n_draw
if minetest.registered_nodes[node.name] then
n_draw = minetest.registered_nodes[node.name].drawtype
else
n_draw = "normal"
end
if type == "s" then
compare = draw_tl
end
for k,v in ipairs(compare) do
if n_draw == v then
return true
end
end
return false
end
-----------------------------------------------
-- Check X number nodes down fly/Swimmable
function node_down_fsable(pos,num,type)
local draw_ta = {"airlike"}
local draw_tl = {"liquid","flowingliquid"}
local i = 0
local nodes = {}
local result ={}
local compare = draw_ta
while (i < num ) do
table.insert(nodes, minetest.get_node({x=pos.x,y=pos.y-i,z=pos.z}))
i=i+1
end
if type == "s" then
compare = draw_tl
end
local n_draw
for k,v in pairs(nodes) do
local n_draw
if minetest.registered_nodes[v.name] then
n_draw = minetest.registered_nodes[v.name].drawtype
else
n_draw = "normal"
end
for k2,v2 in ipairs(compare) do
if n_draw == v2 then
table.insert(result,"t")
end
end
end
if #result == num then
return true
else
return false
end
end
------------------------------------------
-- Workaround for slab edge crouch
function crouch_wa(player,pos)
local is_slab = 0
local pos_w = {}
local angle = (player:get_look_horizontal())*180/math.pi -- Convert Look direction to angles
-- +Z North
if angle <= 45 or angle >= 315 then
pos_w={x=pos.x,y=pos.y+1,z=pos.z+1}
-- -X West
elseif angle > 45 and angle < 135 then
pos_w={x=pos.x-1,y=pos.y+1,z=pos.z}
-- -Z South
elseif angle >= 135 and angle <= 225 then
pos_w={x=pos.x,y=pos.y+1,z=pos.z-1}
-- +X East
elseif angle > 225 and angle < 315 then
pos_w={x=pos.x+1,y=pos.y+1,z=pos.z}
end
local check = minetest.get_node(pos_w)
if minetest.registered_nodes[check.name] then
local check_g = minetest.get_item_group(check.name, "slab")
if check_g ~= 0 then
is_slab = 1
end
end
-- return 1 or 0, need to update to bool
return is_slab
end

View file

@ -0,0 +1,386 @@
------------------------------------------------------------
-- ___ _ __ ___ _ --
-- | __| |_ _ / _|___ / __|_ __ _(_)_ __ --
-- | _|| | || | > _|_ _| \__ \ V V / | ' \ --
-- |_| |_|\_, | \_____| |___/\_/\_/|_|_|_|_| --
-- |__/ --
-- Crouch and Climb --
------------------------------------------------------------
-- Sirrobzeroone --
-- Licence code LGPL v2.1 --
-- Cape Textures - CC0 --
-- Blender Model/B3Ds as per base MTG - CC BY-SA 3.0 --
-- except "3d_armor_trans.png" CC-BY-SA 3.0 --
------------------------------------------------------------
----------------------------
-- Settings
armor_fly_swim = {}
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
armor_fly_swim.add_capes = minetest.settings:get_bool("capes_add_to_3darmor" ,false)
armor_fly_swim.example_cape = minetest.settings:get_bool("example_cape" ,false)
local fly_anim = minetest.settings:get_bool("fly_anim" ,true)
local fall_anim = minetest.settings:get_bool("fall_anim" ,true)
local fall_tv = tonumber(minetest.settings:get("fall_tv" ,true)) or 100
-- Convert kp/h back to number of -y blocks per 0.05 of a second.
fall_tv = -1*(fall_tv/3.7)
local swim_anim = minetest.settings:get_bool("swim_anim" ,true)
local swim_sneak = minetest.settings:get_bool("swim_sneak" ,true)
local climb_anim = minetest.settings:get_bool("climb_anim" ,true)
local crouch_anim = minetest.settings:get_bool("crouch_anim" ,true)
local crouch_sneak = minetest.settings:get_bool("crouch_sneak" ,true)
-----------------------
-- Conditional mods
armor_fly_swim.is_3d_armor = minetest.get_modpath("3d_armor")
armor_fly_swim.is_skinsdb = minetest.get_modpath("skinsdb")
-------------------------------------
-- Adding new armor item for Capes
if armor_fly_swim.add_capes == true then
if minetest.global_exists("armor") and armor.elements then
table.insert(armor.elements, "capes")
end
end
----------------------------
-- Initiate files
dofile(modpath .. "/i_functions.lua")
if armor_fly_swim.example_cape and
armor_fly_swim.add_capes and
armor_fly_swim.is_3d_armor then
dofile(modpath .. "/i_example_cape.lua")
end
-------------------------------------
-- Get Player model to use
local player_mod, texture = armor_fly_swim.get_player_model()
--------------------------------------
-- Player model with Swim/Fly/Capes
player_api.register_model(player_mod, {
animation_speed = 30,
textures = texture,
animations = {
stand = {x=0, y=79},
lay = {x=162, y=166},
walk = {x=168, y=187},
mine = {x=189, y=198},
walk_mine = {x=200, y=219},
sit = {x=81, y=160},
swim = {x=246, y=279},
swim_atk = {x=285, y=318},
fly = {x=325, y=334},
fly_atk = {x=340, y=349},
fall = {x=355, y=364},
fall_atk = {x=365, y=374},
duck_std = {x=380, y=380},
duck = {x=381, y=399},
climb = {x=410, y=429},
},
})
----------------------------------------
-- Setting model on join and clearing
-- local_animations
minetest.register_on_joinplayer(function(player)
player_api.set_model(player,player_mod)
player_api.player_attached[player:get_player_name()] = false
player:set_local_animation({},{},{},{},30)
end)
------------------------------------------------
-- Global step to check if player meets --
-- Conditions for Swimming, Flying(falling) --
-- Crouching or Climbing --
------------------------------------------------
minetest.register_globalstep(function()
for _, player in pairs(minetest.get_connected_players()) do
local controls = player:get_player_control()
local controls_wasd = armor_fly_swim.get_wasd_state(controls)
local attached_to = player:get_attach()
local privs = minetest.get_player_privs(player:get_player_name())
local pos = player:get_pos()
local pmeta = player:get_meta()
local cur_anim = player_api.get_animation(player)
local ladder = {}
ladder.n = {is = false, pos = pos}
ladder.n_a = {is = false, pos = {x=pos.x,y=pos.y +1,z=pos.z}}
ladder.n_b = {is = false, pos = {x=pos.x,y=pos.y -1,z=pos.z}}
local is_slab = crouch_wa(player,pos)
local attack = ""
local ani_spd = 30
local offset = 0
local tdebug = false
-- reset player collisionbox, eye height, speed override
player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}})
player:set_properties({eye_height = 1.47})
-- used to store and reset the players physics.speed settings
-- back to what they were before fly_swim adjusted them
if pmeta:get_int("flyswim_std_under_slab") == 1 then
player:set_physics_override({speed = pmeta:get_float("flyswim_org_phy_or")})
pmeta:set_int("flyswim_std_under_slab", 0)
end
local vel = player:get_velocity()
-- basically 3D Pythagorean Theorem km/h
local play_s = (math.sqrt(math.pow(math.abs(vel.x),2) +
math.pow(math.abs(vel.y),2) +
math.pow(math.abs(vel.z),2) ))*3.6
-- Sets terminal velocity to about 100Km/hr beyond
-- this speed chunk load issues become more noticable
--(-1*(vel.y+1)) - catch those holding shift and over
-- acceleratering when falling so dynamic end point
-- so player dosent bounce back up
if vel.y < fall_tv and controls.sneak ~= true then
local tv_offset_y = -1*((-1*(vel.y+1)) + vel.y)
player:add_player_velocity({x=0, y=tv_offset_y, z=0})
end
-- Check for Swinging/attacking and set string
if controls.LMB or controls.RMB then
attack = "_atk"
end
-- get ladder pos node
local t_node = minetest.registered_nodes[minetest.get_node(ladder.n.pos).name]
if t_node and t_node.climbable then
ladder.n.is = true
end
---------------------------------------------------------
-- Start of Animation Cases --
---------------------------------------------------------
-------------------------------------
-- Crouch Slab/Node Exception Case
-- If player still udner slab/swimming in tunnel
-- and they let go of shift this stops them
-- standing up
local node_check = minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z})
local nc_draw = "normal"
local nc_slab = 0
local nc_node = 0
if minetest.registered_nodes[node_check.name] then
nc_slab = minetest.get_item_group(node_check.name, "slab")
nc_draw = minetest.registered_nodes[node_check.name].drawtype
if nc_draw ~= "liquid" and
nc_draw ~= "flowingliquid" and
nc_draw ~= "airlike" then
nc_node = 1
end
end
if crouch_sneak and
nc_slab == 1 and
not attached_to and
not controls.sneak and
not node_fsable(pos,2,"a") then
local animiation = "duck_std"
-- when player moving
if controls_wasd then
local play_or_2 =player:get_physics_override()
pmeta:set_int("flyswim_std_under_slab", 1)
pmeta:set_float("flyswim_org_phy_or", play_or_2.speed)
player:set_physics_override({speed = play_or_2.speed*0.2})
animation = "duck"
end
if crouch_anim == true then
player_api.set_animation(player, animation ,ani_spd/2)
end
player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.45, 0.3}})
player:set_properties({eye_height = 1.27})
if tdebug then minetest.debug("crouch catch") end
elseif swim_sneak and
nc_node == 1 and
node_down_fsable(pos,1,"s") and
not attached_to and
not controls.sneak then
player_api.set_animation(player, "swim",ani_spd)
player:set_properties({collisionbox = {-0.4, 0, -0.4, 0.4, 0.5, 0.4}})
player:set_properties({eye_height = 0.7})
offset = 90
if tdebug then minetest.debug("swim through catch") end
-----------------------------
-- Climb
elseif climb_anim and
ladder.n.is and
(controls.jump or controls.sneak) then
-- Moved inside climb to save unessecary node checking
for k,def in pairs(ladder) do
if k ~= "n" then
local node = minetest.registered_nodes[minetest.get_node(def.pos).name]
if node and node.climbable then
def.is = true
end
end
end
if (controls.sneak and ladder.n_b.is) or
(controls.jump and ladder.n_a.is) then
player_api.set_animation(player, "climb",ani_spd)
if tdebug then minetest.debug("climb") end
end
-----------------------------
-- Swim
elseif swim_anim == true and
controls_wasd and
node_down_fsable(pos,2,"s") and
not attached_to then
player_api.set_animation(player,"swim"..attack ,ani_spd)
offset = 90
if tdebug then minetest.debug("swim") end
elseif swim_sneak == true and
swim_anim == true and
controls.sneak and
node_down_fsable(pos,1,"s") and
not attached_to then
player_api.set_animation(player, "swim",ani_spd)
player:set_properties({collisionbox = {-0.4, 0, -0.4, 0.4, 0.5, 0.4}})
player:set_properties({eye_height = 0.7})
offset = 90
if tdebug then minetest.debug("swim through") end
-----------------------------
-- Sneak
-- first elseif Crouch-walk workaround.
-- First slab player enters counts as a true slab
-- and has an edge. As such the shift edge detection
-- kicks in and player can't move forwards. This
-- case sets the player collision box to 1 high for that first slab
elseif crouch_anim and
controls.sneak and
controls.up and
not node_fsable(pos,2,"a") and
not attached_to and
play_s <= 1 and is_slab == 1 then
if crouch_anim == true then
player_api.set_animation(player, "duck",ani_spd/2)
player:set_properties({eye_height = 1.27})
end
if crouch_sneak == true then
-- Workaround set collision box to 1 high
player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.0, 0.3}})
end
if tdebug then minetest.debug("crouch_1") end
elseif crouch_anim and
controls.sneak and
not node_fsable(pos,2,"a") and
not attached_to then
local animation = "duck_std"
if controls_wasd then animation = "duck" end
player_api.set_animation(player, animation, ani_spd/2)
player:set_properties({eye_height = 1.27})
if crouch_sneak == true then
player:set_properties({collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.45, 0.3}})
end
if tdebug then minetest.debug("crouch_2") end
-----------------------------
-- Flying
elseif fly_anim == true and
privs.fly == true and
node_down_fsable(pos,3,"a") and
not attached_to then
-- Vel.y value is a compromise for code simplicity,
-- Flyers wont get fall animation until below -18m/s
if controls_wasd then
player_api.set_animation(player, "fly"..attack, ani_spd)
offset = 90
if tdebug then minetest.debug("fly") end
elseif fall_anim == true and
vel.y < -18.0 then
player_api.set_animation(player, "fall"..attack, ani_spd)
offset = 90
if tdebug then minetest.debug("fly_fall") end
end
-----------------------------
-- Falling
elseif fall_anim == true and
node_down_fsable(pos,5,"a") and
vel.y < -0.5 and
not attached_to then
player_api.set_animation(player, "fall"..attack, ani_spd)
offset = 90
if tdebug then minetest.debug("fall") end
end
---------------------------------------------------------
-- Post MT 5.3 Head Animation --
---------------------------------------------------------
-- this function was added in 5.3 which has the bone position
-- change break animations fix - i think (MT #9807)
-- I'm not too sure how to directly test for the bone fix/ MT version
-- so I simply check for this function.
local check_v = minetest.is_creative_enabled
if check_v ~= nil then
local look_degree = -math.deg(player:get_look_vertical())
if look_degree > 29 and offset ~= 0 then
offset = offset - (look_degree-30)
elseif look_degree > 60 and offset == 0 then
offset = offset - (look_degree-60)
elseif look_degree < -60 and offset == 0 then
offset = offset - (look_degree+60)
end
-- Code by LoneWolfHT - Headanim mod MIT Licence --
player:set_bone_position("Head", vector.new(0, 6.35, 0),vector.new(look_degree + offset, 0, 0))
-- Code by LoneWolfHT - Headanim mod MIT Licence --
end
--minetest.chat_send_all(play_s.." km/h") -- for diagnosing chunk emerge issues when falling currently unsolved
end
end)

View file

@ -0,0 +1,8 @@
name = 3d_armor_flyswim
description = Adds Flying, Swimming, Crouching and Climbing animations to base character model for 3d_armor
depends = player_api
optional_depends = 3d_armor, simple_skins, skinsdb, clothing
author = sirrobzeroone
title = Add Fly, Swim, Crouch and Climb animations
release = 13392

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -0,0 +1,32 @@
# Make capes part of 3d Armor when enabled
capes_add_to_3darmor (Add Capes to 3d Armor) bool true
# Disable the Example Cape "Someones Cape"
example_cape (The Example Cape "Someones Cape" which grants fly_privs when worn and a 100% speed increase) bool true
# Enable Fly Animation
fly_anim (Enable fly animation) bool true
# Enable Fall Animation
fall_anim (Enable fall animation) bool true
# Terminal Velocity Speed kp/h
fall_tv (Set terminal velocity speed - recommend not exceeding 100 kp/h) int 100
# Enable Swim Animation
swim_anim (Enable swim animation) bool true
# Enable Swim Sneak - swim through 1x1 hole
swim_sneak (Enable swim sneak) bool true
# Enable Climb Animation
climb_anim (Enable climb animation) bool true
# Enable Crouch/Duck Animation
crouch_anim (Enable crouch/duck animation) bool true
# Enable Crouch/Duck - through through 1.5 hole
crouch_sneak (Enable crouch/duck animation) bool true

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

15
mods/bike/README.txt Normal file
View file

@ -0,0 +1,15 @@
Minetest mod: bike
========================
See license.txt for license information.
Authors of source code
----------------------
Originally by PilzAdam (MIT)
Various Minetest developers and contributors (MIT)
GreenDimond (MIT)
Hume2 (MIT)
Authors of media (textures and model)
-------------------------------------
Textures: GreenDimond (MIT)
Model: GreenDimond (MIT)

879
mods/bike/init.lua Normal file
View file

@ -0,0 +1,879 @@
local S = minetest.get_translator(minetest.get_current_modname())
--[[ Helpers ]]--
-- Skin mod detection
local skin_mod
local skin_mods = {"skinsdb", "skins", "u_skins", "simple_skins", "wardrobe"}
-- local settings
local setting_max_speed = tonumber(minetest.settings:get("bike_max_speed")) or 6.9
local setting_acceleration = tonumber(minetest.settings:get("bike_acceleration")) or 3.16
local setting_decceleration = tonumber(minetest.settings:get("bike_decceleration")) or 7.9
local setting_friction = tonumber(minetest.settings:get("bike_friction")) or 0.79
local setting_turn_speed = tonumber(minetest.settings:get("bike_turn_speed")) or 1.58
local setting_friction_cone = tonumber(minetest.settings:get("bike_friction_cone")) or 0.4
local agility_factor = 1/math.sqrt(setting_friction_cone)
local setting_wheely_factor = tonumber(minetest.settings:get("bike_wheely_factor")) or 2.0
local setting_stepheight = tonumber(minetest.settings:get("bike_stepheight")) or 0.6
local setting_wheely_stepheight = tonumber(minetest.settings:get("bike_wheely_stepheight")) or 0.6
local setting_water_friction = tonumber(minetest.settings:get("bike_water_friction")) or 13.8
local setting_offroad_friction = tonumber(minetest.settings:get("bike_offroad_friction")) or 1.62
-- general settings
local setting_turn_look = minetest.settings:get_bool("mount_turn_player_look", false)
local setting_ownable = minetest.settings:get_bool("mount_ownable", false)
--[[ Crafts ]]--
minetest.register_craftitem("bike:wheel", {
description = S("Bike Wheel"),
inventory_image = "bike_wheel.png",
})
minetest.register_craftitem("bike:handles", {
description = S("Bike Handles"),
inventory_image = "bike_handles.png",
})
-- Set item names according to installed mods
local rubber = "group:wood"
local iron = "default:steel_ingot"
local red_dye = "dye:red"
local green_dye = "dye:green"
local blue_dye = "dye:blue"
local container = "default:glass"
if minetest.get_modpath("technic") ~= nil then
rubber = "technic:rubber"
end
if minetest.get_modpath("vessels") ~= nil then
container = "vessels:glass_bottle"
end
if minetest.get_modpath("mcl_core") ~= nil then
iron = "mcl_core:iron_ingot"
container = "mcl_core:glass"
end
if minetest.get_modpath("mcl_rubber") ~= nil then
rubber = "mcl_rubber:rubber"
end
if minetest.get_modpath("mcl_dye") ~= nil then
red_dye = "mcl_dye:red"
green_dye = "mcl_dye:green"
blue_dye = "mcl_dye:blue"
end
if minetest.get_modpath("mcl_potions") ~= nil then
container = "mcl_potions:glass_bottle"
end
for _, mod in pairs(skin_mods) do
local path = minetest.get_modpath(mod)
if path then
skin_mod = mod
end
end
local function get_player_skin(player)
local name = player:get_player_name()
local armor_tex = ""
if minetest.global_exists("armor") then
-- Filter out helmet (for bike helmet) and boots/shield (to not mess up UV mapping)
local function filter(str, find)
for _,f in pairs(find) do
str = str:gsub("%^"..f.."_(.-.png)", "")
end
return str
end
armor_tex = filter("^"..armor.textures[name].armor, {"shields_shield", "3d_armor_boots", "3d_armor_helmet"})
end
-- Return the skin with armor (if applicable)
if skin_mod == "skinsdb" then
return "[combine:64x32:0,0="..skins.get_player_skin(player):get_texture()..armor_tex
elseif (skin_mod == "skins" or skin_mod == "simple_skins") and skins.skins[name] then
return skins.skins[name]..".png"..armor_tex
elseif skin_mod == "u_skins" and u_skins.u_skins[name] then
return u_skins.u_skins[name]..".png"..armor_tex
elseif skin_mod == "wardrobe" and wardrobe.playerSkins and wardrobe.playerSkins[name] then
return wardrobe.playerSkins[name]..armor_tex
end
local skin = player:get_properties().textures[1]
-- If we just have 3d_armor enabled make sure we get the player skin properly
if minetest.global_exists("armor") and armor.get_player_skin then
skin = armor:get_player_skin(name)
end
return skin..armor_tex
end
-- Bike metal texture handling
local function is_hex(color)
if color:len() ~= 7 then return nil end
return color:match("#%x%x%x%x%x%x")
end
local function colormetal(color, alpha)
return "bike_metal_base.png^[colorize:"..(color)..":"..tostring(alpha)
end
-- Terrain checkers
local function is_water(pos)
local nn = minetest.get_node(pos).name
return minetest.get_item_group(nn, "liquid") ~= 0
end
local function is_bike_friendly(pos)
local nn = minetest.get_node(pos).name
return minetest.get_item_group(nn, "crumbly") == 0 or minetest.get_item_group(nn, "bike_friendly") ~= 0
end
-- Maths
local function get_sign(i)
if i == 0 then
return 0
else
return i / math.abs(i)
end
end
local function get_velocity(v, yaw, y)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = y, z = z}
end
local function get_v(v)
return math.sqrt(v.x ^ 2 + v.z ^ 2)
end
-- Custom hand
minetest.register_node("bike:hand", {
description = "",
-- No interaction on a bike :)
range = 0,
on_place = function(itemstack, placer, pointed_thing)
return ItemStack("bike:hand "..itemstack:get_count())
end,
-- Copy default:hand looks so it doesnt look as weird when the hands are switched
wield_image = minetest.registered_items[""].wield_image,
wield_scale = minetest.registered_items[""].wield_scale,
node_placement_prediction = "",
})
--[[ Bike ]]--
-- Default textures (overidden when mounted or colored)
local function default_tex(metaltex, alpha)
return {
"bike_metal_grey.png",
"bike_gear.png",
colormetal(metaltex, alpha),
"bike_leather.png",
"bike_chain.png",
"bike_metal_grey.png",
"bike_leather.png",
"bike_metal_black.png",
"bike_metal_black.png",
"bike_blank.png",
"bike_tread.png",
"bike_gear.png",
"bike_spokes.png",
"bike_tread.png",
"bike_spokes.png",
}
end
-- Entity
local bike = {
physical = true,
-- Warning: Do not change the position of the collisionbox top surface,
-- lowering it causes the bike to fall through the world if underwater
collisionbox = {-0.5, -0.4, -0.5, 0.5, 0.8, 0.5},
collide_with_objects = false,
visual = "mesh",
mesh = "bike.b3d",
textures = default_tex("#FFFFFF", 150),
stepheight = setting_stepheight,
driver = nil,
color = "#FFFFFF",
alpha = 150,
old_driver = {},
v = 0, -- Current velocity
last_v = 0, -- Last velocity
max_v = setting_max_speed, -- Max velocity
fast_v = 0, -- Fast adder
f_speed = 30, -- Frame speed
last_y = 0, -- Last height
up = false, -- Are we going up?
timer = 0,
removed = false,
__is_bike__ = true,
}
-- Dismont the player
local function dismount_player(bike, exit)
bike.object:set_velocity({x = 0, y = 0, z = 0})
-- Make the bike empty again
bike.object:set_properties({textures = default_tex(bike.color, bike.alpha)})
bike.v = 0
if bike.driver then
bike.driver:set_detach()
-- Reset original player properties
bike.driver:set_properties({visual_size=bike.old_driver["vsize"]})
bike.driver:set_eye_offset(bike.old_driver.eye_offset.offset_first,
bike.old_driver.eye_offset.offset_third)
bike.driver:hud_set_flags(bike.old_driver["hud"])
local pinv = bike.driver:get_inventory()
if pinv then
pinv:set_stack("hand", 1, pinv:get_stack("old_hand", 1))
if minetest.global_exists("mcl_skins") then
local node_id = mcl_skins.get_node_id_by_player(bike.driver)
pinv:set_stack("hand", 1, "mcl_meshhand:" .. node_id)
end
end
-- Is the player leaving? If so, dont do this stuff or Minetest will have a fit
if not exit then
local pos = bike.driver:get_pos()
pos = {x = pos.x, y = pos.y + 0.2, z = pos.z}
bike.driver:set_pos(pos)
end
bike.driver = nil
end
end
-- Mounting
function bike.on_rightclick(self, clicker)
if not clicker or not clicker:is_player() or clicker:get_attach() ~= nil then
return
end
if not self.driver then
local pname = clicker:get_player_name()
if setting_ownable and self.owner and pname ~= self.owner then
minetest.chat_send_player(pname, "You cannot ride " .. self.owner .. "'s bike.")
return
end
-- Make integrated player appear
self.object:set_properties({
textures = {
"bike_metal_grey.png",
"bike_gear.png",
colormetal(self.color, self.alpha),
"bike_leather.png",
"bike_chain.png",
"bike_metal_grey.png",
"bike_leather.png",
"bike_metal_black.png",
"bike_metal_black.png",
get_player_skin(clicker).."^bike_helmet.png",
"bike_tread.png",
"bike_gear.png",
"bike_spokes.png",
"bike_tread.png",
"bike_spokes.png",
},
})
-- Save the player's properties that we need to change
self.old_driver["vsize"] = clicker:get_properties().visual_size
local offset_first, offset_third = clicker:get_eye_offset()
self.old_driver.eye_offset = {
offset_first = offset_first,
offset_third = offset_third
}
self.old_driver["hud"] = clicker:hud_get_flags()
clicker:get_inventory():set_stack("old_hand", 1, clicker:get_inventory():get_stack("hand", 1))
-- Change the hand
clicker:get_inventory():set_stack("hand", 1, "bike:hand")
local attach = clicker:get_attach()
if attach and attach:get_luaentity() then
local luaentity = attach:get_luaentity()
if luaentity.driver then
luaentity.driver = nil
end
clicker:set_detach()
end
self.driver = clicker
-- Set new properties and hide HUD
clicker:set_properties({visual_size = {x=0,y=0}})
clicker:set_attach(self.object, "body", {x = 0, y = 10, z = 5}, {x = 0, y = 0, z = 0})
clicker:set_eye_offset({x=0,y=-3,z=10},{x=0,y=0,z=5})
clicker:hud_set_flags({
hotbar = false,
wielditem = false,
})
-- Look forward initially
clicker:set_look_horizontal(self.object:get_yaw())
end
end
function bike.on_activate(self, staticdata, dtime_s)
self.object:set_acceleration({x = 0, y = -9.8, z = 0})
self.object:set_armor_groups({immortal = 1})
if staticdata ~= "" then
local data = minetest.deserialize(staticdata)
if data ~= nil then
self.v = data.v
self.color = data.color
self.alpha = data.alpha
self.owner = data.owner
end
end
self.object:set_properties({textures=default_tex(self.color, self.alpha)})
self.last_v = self.v
end
-- Save velocity and color data for reload
function bike.get_staticdata(self)
local data = {
v = self.v,
color = self.color,
alpha = self.alpha,
owner = self.owner,
}
return minetest.serialize(data)
end
-- Pick up/color
function bike.on_punch(self, puncher)
local itemstack = puncher:get_wielded_item()
-- Bike painting
if itemstack:get_name() == "bike:painter" then
-- No painting while someone is riding :P
if self.driver then
return
end
-- Get color data
local meta = itemstack:get_meta()
self.color = meta:get_string("paint_color")
self.alpha = meta:get_string("alpha")
self.object:set_properties({
textures = {
"bike_metal_grey.png",
"bike_gear.png",
colormetal(self.color, self.alpha),
"bike_leather.png",
"bike_chain.png",
"bike_metal_grey.png",
"bike_leather.png",
"bike_metal_black.png",
"bike_metal_black.png",
"bike_blank.png",
"bike_tread.png",
"bike_gear.png",
"bike_spokes.png",
"bike_tread.png",
"bike_spokes.png",
},
})
return
end
if not puncher or not puncher:is_player() or self.removed then
return
end
-- Make sure no one is riding
if not self.driver then
local pname = puncher:get_player_name()
if setting_ownable and self.owner and pname ~= self.owner then
minetest.chat_send_player(pname, "You cannot take " .. self.owner .. "'s bike.")
return
end
local inv = puncher:get_inventory()
-- We can only carry one bike
if not inv:contains_item("main", "bike:bike") then
local stack = ItemStack({name="bike:bike", count=1, wear=0})
local meta = stack:get_meta()
-- Set the stack to the bike color
meta:set_string("color", self.color)
meta:set_string("alpha", self.alpha)
local leftover = inv:add_item("main", stack)
-- If no room in inventory add the bike to the world
if not leftover:is_empty() then
minetest.add_item(self.object:get_pos(), leftover)
end
else
-- Turn it into raw materials
if not (creative and creative.is_enabled_for(pname)) then
local ctrl = puncher:get_player_control()
if not ctrl.sneak then
minetest.chat_send_player(pname, "Warning: Destroying the bike gives you only some resources back. If you are sure, hold sneak while destroying the bike.")
return
end
local leftover = inv:add_item("main", iron .. " 6")
-- If no room in inventory add the iron to the world
if not leftover:is_empty() then
minetest.add_item(self.object:get_pos(), leftover)
end
end
end
self.removed = true
-- Delay remove to ensure player is detached
minetest.after(0.1, function()
self.object:remove()
end)
end
end
-- Animations
local function bike_anim(self)
-- The `self.object:get_animation().y ~= <frame>` is to check if the animation is already running
if self.driver then
local ctrl = self.driver:get_player_control()
-- Wheely
if ctrl.jump then
-- We are moving
if self.v > 0 then
if self.object:get_animation().y ~= 79 then
self.object:set_animation({x=59,y=79}, self.f_speed + self.fast_v, 0, true)
end
return
-- Else we are not
else
if self.object:get_animation().y ~= 59 then
self.object:set_animation({x=59,y=59}, self.f_speed + self.fast_v, 0, true)
end
return
end
end
-- Left or right tilt, but only if we arent doing a wheely
local t_left = false
local t_right = false
if not setting_turn_look then
t_left = ctrl.left
t_right = ctrl.right
else
local turning = math.floor(self.driver:get_look_horizontal() * 100)
- math.floor(self.object:get_yaw() * 100)
-- use threshold of 1 to determine if animation should be updated
t_left = turning > 1
t_right = turning < -1
end
if t_left then
if self.object:get_animation().y ~= 58 then
self.object:set_animation({x=39,y=58}, self.f_speed + self.fast_v, 0, true)
end
return
elseif t_right then
if self.object:get_animation().y ~= 38 then
self.object:set_animation({x=19,y=38}, self.f_speed + self.fast_v, 0, true)
end
return
end
end
-- If none of that, then we are just moving forward
if self.v > 0 then
if self.object:get_animation().y ~= 18 then
self.object:set_animation({x=0,y=18}, 30, 0, true)
end
return
-- Or not
else
if self.object:get_animation().y ~= 0 then
self.object:set_animation({x=0,y=0}, 0, 0, false)
end
end
end
-- Run every tick
function bike.on_step(self, dtime, moveresult)
-- Player checks
if self.driver then
-- Is the actual player somehow still visible?
local p_prop = self.driver:get_properties()
if p_prop and p_prop.visual_size ~= {x=0,y=0} then
self.driver:set_properties({visual_size = {x=0,y=0}})
end
-- Has the player left?
if self.driver:get_attach() == nil then
dismount_player(self, true)
return
end
end
-- Have we come to a sudden stop?
if math.abs(self.last_v - self.v) > 3 then
-- And is Minetest not being dumb
if not self.up then
minetest.log("info", "[bike] forcing stop")
self.v = 0
end
end
self.last_v = self.v
self.timer = self.timer + dtime;
if self.timer >= 0.5 then
-- Recording y values to check if we are going up
self.last_y = self.object:get_pos().y
self.timer = 0
end
-- Are we going up?
if self.last_y < self.object:get_pos().y then
self.up = true
else
self.up = false
end
-- Run animations
bike_anim(self)
-- Are we falling?
if self.object:get_velocity().y < -10 and self.driver ~= nil then
-- If so, dismount
dismount_player(self)
return
end
local current_v = get_v(self.object:get_velocity()) * get_sign(self.v)
self.v = (current_v + self.v*3) / 4
if self.driver then
local ctrl = self.driver:get_player_control()
local yaw = self.object:get_yaw()
local agility = 0
-- Sneak dismount
if ctrl.sneak then
dismount_player(self)
return
end
if self.v > setting_friction_cone then
agility = 1/math.sqrt(self.v)/agility_factor
else
agility = 1.0
end
-- Forward
if ctrl.up then
-- Are we going fast?
if ctrl.aux1 then
if self.fast_v ~= 5 then
self.fast_v = 5
end
else
if self.fast_v > 0 then
self.fast_v = self.fast_v - 0.05 * agility
end
end
self.v = self.v + dtime*setting_acceleration + (self.fast_v*0.1) * agility
-- Brakes
elseif ctrl.down then
self.v = self.v - dtime*setting_decceleration * agility
if self.fast_v > 0 then
self.fast_v = self.fast_v - dtime*setting_friction * agility
end
-- Nothin'
else
self.v = self.v - dtime*setting_friction * agility
if self.fast_v > 0 then
self.fast_v = self.fast_v - dtime*setting_friction * agility
end
end
-- Wheely will change turning speed
local turn_speed = setting_turn_speed
-- Are we doing a wheely?
if ctrl.jump then
turn_speed = setting_wheely_factor * setting_turn_speed
if self.stepheight ~= setting_wheely_stepheight then
self.object:set_properties({stepheight=setting_wheely_stepheight})
self.stepheight = setting_wheely_stepheight
end
elseif self.stepheight ~= setting_stepheight then
self.object:set_properties({stepheight=setting_stepheight})
self.stepheight = setting_stepheight
end
-- Turning
if not setting_turn_look then
if ctrl.left then
self.object:set_yaw(yaw + turn_speed*dtime * agility)
elseif ctrl.right then
self.object:set_yaw(yaw - turn_speed*dtime * agility)
end
else
self.object:set_yaw(self.driver:get_look_horizontal())
end
end
-- Movement
local velo = self.object:get_velocity()
if self.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
self.object:move_to(self.object:get_pos())
return
end
local s = get_sign(self.v)
if s ~= get_sign(self.v) then
self.object:set_velocity({x = 0, y = 0, z = 0})
self.v = 0
return
end
if self.v > self.max_v + self.fast_v then
self.v = self.max_v + self.fast_v
elseif self.v < 0 then
self.v = 0
end
local p = self.object:get_pos()
if is_water(p) then
self.v = self.v / math.pow(setting_water_friction, dtime)
end
-- Can we ride good here?
local bike_friendly = true
for _, collision in ipairs(moveresult.collisions) do
if collision.type == "node" and collision.axis == "y" then
if not is_bike_friendly(collision.node_pos) then
bike_friendly = false
break
end
end
end
if not bike_friendly then
self.v = self.v / math.pow(setting_offroad_friction, dtime)
end
local new_velo
new_velo = get_velocity(self.v, self.object:get_yaw(), self.object:get_velocity().y)
self.object:move_to(self.object:get_pos())
self.object:set_velocity(new_velo)
end
-- Check for stray bike hand
minetest.register_on_joinplayer(function(player)
local inv = player:get_inventory()
if inv:get_stack("hand", 1):get_name() == "bike:hand" then
inv:set_stack("hand", 1, inv:get_stack("old_hand", 1))
if minetest.global_exists("mcl_skins") then
local node_id = mcl_skins.get_node_id_by_player(player)
inv:set_stack("hand", 1, "mcl_meshhand:" .. node_id)
end
end
end)
-- Dismount all players on server shutdown
minetest.register_on_shutdown(function()
for _, e in pairs(minetest.luaentities) do
if (e.__is_bike__ and e.driver ~= nil) then
dismount_player(e, true)
end
end
end)
-- Register the entity
minetest.register_entity("bike:bike", bike)
-- Bike craftitem
minetest.register_craftitem("bike:bike", {
description = S("Bike"),
inventory_image = "bike_inventory.png",
wield_scale = {x = 3, y = 3, z = 2},
groups = {flammable = 2},
stack_max = 1,
on_place = function(itemstack, placer, pointed_thing)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
if pointed_thing.type ~= "node" then
return itemstack
end
local meta = itemstack:get_meta()
local sdata = {
v = 0,
-- Place bike with saved color
color = meta:get_string("color"),
alpha = tonumber(meta:get_string("alpha")),
-- Store owner
owner = placer:get_player_name(),
}
-- If it's a new bike, give it default colors
if sdata.alpha == nil then
sdata.color, sdata.alpha = "#FFFFFF", 150
end
local bike_pos = placer:get_pos()
bike_pos.y = bike_pos.y + 0.5
-- Use the saved attributes data and place the bike
bike = minetest.add_entity(bike_pos, "bike:bike", minetest.serialize(sdata))
-- Point it the right direction
if bike then
if placer then
bike:set_yaw(placer:get_look_horizontal())
end
local player_name = placer and placer:get_player_name() or ""
if not (creative and creative.is_enabled_for and
creative.is_enabled_for(player_name)) then
itemstack:take_item()
end
end
return itemstack
end,
})
--[[ Painter ]]--
-- Helpers
local function rgb_to_hex(r, g, b)
return string.format("#%02X%02X%02X", r, g, b)
end
local function hex_to_rgb(hex)
hex = hex:gsub("#","")
local rgb = {
r = tonumber("0x"..hex:sub(1,2)),
g = tonumber("0x"..hex:sub(3,4)),
b = tonumber("0x"..hex:sub(5,6)),
}
return rgb
end
-- Need to convert between 1000 units and 256
local function from_slider_rgb(value)
value = tonumber(value)
return math.floor((255/1000*value)+0.5)
end
-- ...and back
local function to_slider_rgb(value)
return 1000/255*value
end
-- Painter formspec
local function show_painter_form(itemstack, player)
local meta = itemstack:get_meta()
local color = meta:get_string("paint_color")
local alpha = tonumber(meta:get_string("alpha"))
if alpha == nil then
color, alpha = "#FFFFFF", 128
end
local rgba = hex_to_rgb(color)
rgba.a = alpha
minetest.show_formspec(player:get_player_name(), "bike:painter",
-- Init formspec
"size[6,6;true]"..
"position[0.5, 0.45]"..
-- Hex/Alpha fields
"button[1.6,5.5;2,1;set;Set paint color]"..
"field[0.9,5;2,0.8;hex;Hex Color;"..color.."]"..
"field[2.9,5;2,0.8;alpha;Alpha (0-255);"..tostring(alpha).."]"..
-- RGBA sliders
"scrollbar[0,2;5,0.3;horizontal;r;"..tostring(to_slider_rgb(rgba.r)).."]"..
"label[5.1,1.9;R: "..tostring(rgba.r).."]"..
"scrollbar[0,2.6;5,0.3;horizontal;g;"..tostring(to_slider_rgb(rgba.g)).."]"..
"label[5.1,2.5;G: "..tostring(rgba.g).."]"..
"scrollbar[0,3.2;5,0.3;horizontal;b;"..tostring(to_slider_rgb(rgba.b)).."]"..
"label[5.1,3.1;B: "..tostring(rgba.b).."]"..
"scrollbar[0,3.8;5,0.3;horizontal;a;"..tostring(to_slider_rgb(rgba.a)).."]"..
"label[5.1,3.7;A: "..tostring(rgba.a).."]"..
-- Preview
"label[1,0;Preview:]"..
"image[2,0;2,2;bike_metal_base.png^[colorize:"..color..":"..tostring(rgba.a).."]"
)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "bike:painter" then
local itemstack = player:get_wielded_item()
if fields.set then
if itemstack:get_name() == "bike:painter" then
local meta = itemstack:get_meta()
local hex = fields.hex
local alpha = tonumber(fields.alpha)
if is_hex(hex) == nil then
hex = "#FFFFFF"
end
if alpha == nil or alpha < 0 or alpha > 255 then
alpha = 128
end
-- Save color data to painter (rgba sliders will adjust to hex/alpha too!)
meta:set_string("paint_color", hex)
meta:set_string("alpha", tostring(alpha))
meta:set_string("description", "Bike Painter ("..hex:upper()..", A: "..tostring(alpha)..")")
player:set_wielded_item(itemstack)
show_painter_form(itemstack, player)
return
end
end
if fields.r or fields.g or fields.b or fields.a then
if itemstack:get_name() == "bike:painter" then
-- Save on slider adjustment (hex/alpha will adjust to match the rgba!)
local meta = itemstack:get_meta()
local function sval(value)
return from_slider_rgb(value:gsub(".*:", ""))
end
meta:set_string("paint_color", rgb_to_hex(sval(fields.r),sval(fields.g),sval(fields.b)))
meta:set_string("alpha", sval(fields.a))
-- Keep track of what this painter is painting
meta:set_string("description", "Bike Painter ("..meta:get_string("paint_color"):upper()..", A: "..meta:get_string("alpha")..")")
player:set_wielded_item(itemstack)
show_painter_form(itemstack, player)
end
end
end
end)
-- Make the actual thingy
minetest.register_tool("bike:painter", {
description = S("Bike Painter"),
inventory_image = "bike_painter.png",
wield_scale = {x = 2, y = 2, z = 1},
on_place = show_painter_form,
on_secondary_use = show_painter_form,
})
minetest.register_craft({
output = "bike:wheel 2",
recipe = {
{"", rubber, ""},
{rubber, iron, rubber},
{"", rubber, ""},
},
})
minetest.register_craft({
output = "bike:handles",
recipe = {
{iron, iron, iron},
{rubber, "", rubber},
},
})
minetest.register_craft({
output = "bike:bike",
recipe = {
{"bike:handles", "", rubber},
{iron, iron, iron},
{"bike:wheel", "", "bike:wheel"},
},
})
minetest.register_craft({
output = "bike:painter",
recipe = {
{"", container, ""},
{iron, red_dye, green_dye},
{"", rubber, blue_dye},
},
})

27
mods/bike/license.txt Normal file
View file

@ -0,0 +1,27 @@
License
-------
The MIT License (MIT)
Copyright (C) 2012-2016 PilzAdam
Copyright (C) 2012-2016 Various Minetest developers and contributors
Copyright (C) 2018 GreenDimond
Copyright (C) 2018 Hume2
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
For more details:
https://opensource.org/licenses/MIT

View file

View file

@ -0,0 +1,5 @@
# textdomain: bike
Bike Wheel=Bicikla Rado
Bike Handles=Biciklaj Teniloj
Bike=Biciklo
Bike Painter=Bicikla Pentrilo

View file

@ -0,0 +1,5 @@
# textdomain: bike
Bike Wheel=Roue de Vélo
Bike Handles=Guidon de Vélo
Bike=Vélo
Bike Painter=Peinture pour Vélo

View file

@ -0,0 +1,5 @@
# textdomain: bike
Bike Wheel=
Bike Handles=
Bike=
Bike Painter=

6
mods/bike/mod.conf Normal file
View file

@ -0,0 +1,6 @@
name = bike
description = Bike mod for Minetest
optional_depends = skinsdb, skins, u_skins, simple_skins, wardrobe
release = 17361
author = Hume2
title = Bike

BIN
mods/bike/models/bike.b3d Normal file

Binary file not shown.

BIN
mods/bike/models/bike.blend Normal file

Binary file not shown.

View file

@ -0,0 +1,38 @@
# The maximum speed of a bike in m/s
bike_max_speed (Maximum speed) float 6.9
# The acceleration of a bike in m/s^2
bike_acceleration (Acceleration) float 3.16
# The decceleration of a bike caused by braking in m/s^2
bike_decceleration (Decceleration) float 7.9
# The decceleration of a bike caused by friciton in m/s^2
bike_friction (Friction) float 0.79
# The turning speed of a stopped bike in rad/s
bike_turn_speed (Turn speed) float 1.58
# The speed in m/s bellow which the bike accelerates linearily
bike_friction_cone (Friction cone) float 0.4
# How many nodes can be climbed/jumped
bike_stepheight (Step height) float 0.6 0
# How many nodes can be climbed/jumped with wheely
bike_wheely_stepheight (Wheely step height) float 0.6 0
# The factor by which the turning speed is increased by wheely
bike_wheely_factor (Wheely factor) float 2.0
# The factor by which the bike slows down per second in water
bike_water_friction (Water friction) float 13.8
# The factor by which the bike slows down per second offroad
bike_offroad_friction (Offroad friction) float 1.62
# Bike turns with player look direction instead of a/d keys.
mount_turn_player_look (Turn with look direction) bool false
# Bikes can only be used and picked up by owners.
mount_ownable (Ownable bikes) bool false

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,7 +0,0 @@
globals = {"character_anim"}
read_globals = {
"modlib",
-- Minetest
math = {fields = {"sign"}},
"vector", "minetest"
}

View file

@ -1,184 +0,0 @@
# Character Animations (`character_anim`)
Animates the character. Resembles [`playeranim`](https://github.com/minetest-mods/playeranim) and [`headanim`](https://github.com/LoneWolfHT/headanim).
## About
Depends on [`modlib`](https://github.com/appgurueu/modlib). Code written by Lars Mueller aka LMD or appguru(eu) and licensed under the MIT license.
## Screenshot
![Image](screenshot.png)
## Links
* [GitHub](https://github.com/appgurueu/character_anim) - sources, issue tracking, contributing
* [Discord](https://discordapp.com/invite/ysP74by) - discussion, chatting
* [Minetest Forum](https://forum.minetest.net/viewtopic.php?f=9&t=25385) - (more organized) discussion
* [ContentDB](https://content.minetest.net/packages/LMD/character_anim) - releases (cloning from GitHub is recommended)
## Features
* Animates head, right arm & body
* Support for arbitrary player models, as long as `Head`, `Arm_Right` & `Body` bones exist
* If any of these bones do not exist, it will still try to animate the remaining bones
* Advantages over `playeranim`:
* Extracts exact animations and bone positions from b3d models at runtime (no complex installation)
* Also animates attached players (with restrictions on angles)
* Advantages over `headanim`:
* Provides compatibility back until Minetest 0.4.x (as opposed to `headanim` supporting only 5.3+)
* Head angles are clamped, head can tilt sideways
* Animates right arm & body as well
## Configuration
<!--modlib:conf:2-->
### `default`
#### `arm_right`
##### `radius`
Right arm spin radius
* Type: number
* Default: `10`
* &gt;= `-180`
* &lt;= `180`
##### `speed`
Right arm spin speed
* Type: number
* Default: `1000`
* &gt; `0`
* &lt;= `10000`
##### `yaw`
###### `max`
Right arm yaw (max)
* Type: number
* Default: `160`
* &gt;= `-180`
* &lt;= `180`
###### `min`
Right arm yaw (min)
* Type: number
* Default: `-30`
* &gt;= `-180`
* &lt;= `180`
#### `body`
##### `turn_speed`
Body turn speed
* Type: number
* Default: `0.2`
* &gt; `0`
* &lt;= `1000`
#### `head`
##### `pitch`
###### `max`
Head pitch (max)
* Type: number
* Default: `80`
* &gt;= `-180`
* &lt;= `180`
###### `min`
Head pitch (min)
* Type: number
* Default: `-60`
* &gt;= `-180`
* &lt;= `180`
##### `yaw`
###### `max`
Head yaw (max)
* Type: number
* Default: `90`
* &gt;= `-180`
* &lt;= `180`
###### `min`
Head yaw (min)
* Type: number
* Default: `-90`
* &gt;= `-180`
* &lt;= `180`
##### `yaw_restricted`
###### `max`
Head yaw restricted (max)
* Type: number
* Default: `45`
* &gt;= `-180`
* &lt;= `180`
###### `min`
Head yaw restricted (min)
* Type: number
* Default: `0`
* &gt;= `-180`
* &lt;= `180`
##### `yaw_restriction`
Head yaw restriction
* Type: number
* Default: `60`
* &gt;= `-180`
* &lt;= `180`
### `models`
Other models, same format as `default` model
<!--modlib:conf-->
## API
Minetest's `player:set_bone_position` is overridden so that it still works as expected.
### `character_anim.set_bone_override(player, bonename, position, rotation)`
The signature resembles that of `set_bone_position`. `bonename` must be a string. The following additional features are provided:
* Using it like `set_bone_position` by setting `rotation` and `position` to non-`nil` values and using `""` to set the root bone
* *Setting only the bone position* by setting `rotation` to `nil` - bone rotation will then be model-animation-determined
* *Setting only the bone rotation* by setting `position` to `nil` - bone position will then be model-animation-determined
* *Clearing the override* by setting both `rotation` and `position` to `nil` ("unset_bone_position")

View file

@ -1 +0,0 @@
modlib

View file

@ -1,379 +0,0 @@
assert(modlib.version >= 103, "character_anim requires at least version rolling-103 of modlib")
local workaround_model = modlib.mod.require"workaround"
character_anim = {}
character_anim.conf = modlib.mod.configuration()
local quaternion = modlib.quaternion
-- TODO deduplicate code: move to modlib (see ghosts mod)
local media_paths = modlib.minetest.media.paths
local static_model_names = {}
local animated_model_names = {}
for name in pairs(media_paths) do
if (name:find"character" or name:find"player") and name:match"%.b3d$" then
local fixed, data = pcall(workaround_model, name)
if fixed then
local static_name = "_character_anim_" .. name
minetest.dynamic_add_media({
filename = static_name,
filedata = data,
})
static_model_names[name] = static_name
animated_model_names[static_name] = name
else
minetest.log("warning", "character_anim: failed to workaround model " .. name)
end
end
end
local function find_node(root, name)
if root.name == name then return root end
for _, child in ipairs(root.children) do
local node = find_node(child, name)
if node then return node end
end
end
local models = setmetatable({}, {__index = function(self, filename)
if animated_model_names[filename] then
return self[animated_model_names[filename]]
end
local _, ext = modlib.file.get_extension(filename)
if not ext or ext:lower() ~= "b3d" then
-- Only B3D support currently
return
end
local path = assert(media_paths[filename], filename)
local stream = io.open(path, "rb")
local model = assert(modlib.b3d.read(stream))
assert(stream:read(1) == nil, "EOF expected")
stream:close()
self[filename] = model
return model
end})
function character_anim.is_interacting(player)
local control = player:get_player_control()
return minetest.check_player_privs(player, "interact") and (control.RMB or control.LMB)
end
local function get_look_horizontal(player)
return -math.deg(player:get_look_horizontal())
end
local players = {}
character_anim.players = players
local function get_playerdata(player)
local name = player:get_player_name()
local data = players[name]
if data then return data end
-- Initialize playerdata if it doesn't already exist
data = {
interaction_time = 0,
animation_time = 0,
look_horizontal = get_look_horizontal(player),
bone_positions = {}
}
players[name] = data
return data
end
function character_anim.set_bone_override(player, bonename, position, rotation)
local value = {
position = position,
euler_rotation = rotation
}
get_playerdata(player).bone_positions[bonename] = next(value) and value
end
local function nil_default(value, default)
if value == nil then return default end
return value
end
-- Forward declaration
local handle_player_animations
-- Raw PlayerRef methods
local set_bone_position, set_animation, set_local_animation
minetest.register_on_joinplayer(function(player)
get_playerdata(player) -- Initalizes playerdata if it isn't already initialized
if not set_bone_position then
local PlayerRef = getmetatable(player)
-- Keep our model hack completely opaque to the outside world
local set_properties = PlayerRef.set_properties
function PlayerRef:set_properties(props)
if not self:is_player() then
return set_properties(self, props)
end
local old_mesh = props.mesh
props.mesh = static_model_names[old_mesh] or old_mesh
set_properties(self, props)
props.mesh = old_mesh
end
local get_properties = PlayerRef.get_properties
function PlayerRef:get_properties()
if not self:is_player() then
return get_properties(self)
end
local props = get_properties(self)
if not props then return nil end
props.mesh = animated_model_names[props.mesh] or props.mesh
return props
end
set_bone_position = PlayerRef.set_bone_position
function PlayerRef:set_bone_position(bonename, position, rotation)
if self:is_player() then
character_anim.set_bone_override(self, bonename or "",
position or {x = 0, y = 0, z = 0},
rotation or {x = 0, y = 0, z = 0})
end
return set_bone_position(self, bonename, position, rotation)
end
set_animation = PlayerRef.set_animation
function PlayerRef:set_animation(frame_range, frame_speed, frame_blend, frame_loop)
if not self:is_player() then
return set_animation(self, frame_range, frame_speed, frame_blend, frame_loop)
end
local player_animation = get_playerdata(self)
if not player_animation then
return
end
local prev_anim = player_animation.animation
local new_anim = {
nil_default(frame_range, {x = 1, y = 1}),
nil_default(frame_speed, 15),
nil_default(frame_blend, 0),
nil_default(frame_loop, true)
}
player_animation.animation = new_anim
if not prev_anim or (prev_anim[1].x ~= new_anim[1].x or prev_anim[1].y ~= new_anim[1].y) then
-- Reset animation only if the animation changed
player_animation.animation_time = 0
handle_player_animations(0, player)
elseif prev_anim[2] ~= new_anim[2] then
-- Adapt time to new speed
player_animation.animation_time = player_animation.animation_time * prev_anim[2] / new_anim[2]
end
end
local set_animation_frame_speed = PlayerRef.set_animation_frame_speed
function PlayerRef:set_animation_frame_speed(frame_speed)
if not self:is_player() then
return set_animation_frame_speed(self, frame_speed)
end
frame_speed = nil_default(frame_speed, 15)
local player_animation = get_playerdata(self)
if not player_animation then
return
end
local prev_speed = player_animation.animation[2]
player_animation.animation[2] = frame_speed
-- Adapt time to new speed
player_animation.animation_time = player_animation.animation_time * prev_speed / frame_speed
end
local get_animation = PlayerRef.get_animation
function PlayerRef:get_animation()
if not self:is_player() then
return get_animation(self)
end
local anim = get_playerdata(self).animation
if anim then
return unpack(anim, 1, 4)
end
return get_animation(self)
end
set_local_animation = PlayerRef.set_local_animation
function PlayerRef:set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)
if not self:is_player() then return set_local_animation(self) end
frame_speed = frame_speed or 30
get_playerdata(self).local_animation = {idle, walk, dig, walk_while_dig, frame_speed}
end
local get_local_animation = PlayerRef.get_local_animation
function PlayerRef:get_local_animation()
if not self:is_player() then return get_local_animation(self) end
local local_anim = get_playerdata(self).local_animation
if local_anim then
return unpack(local_anim, 1, 5)
end
return get_local_animation(self)
end
end
-- First update `character_anim` with the current animation
-- which mods like `player_api` might have already set
-- (note: these two methods are already hooked)
player:set_animation(player:get_animation())
-- Then disable animation & local animation
local no_anim = {x = 0, y = 0}
set_animation(player, no_anim, 0, 0, false)
set_local_animation(player, no_anim, no_anim, no_anim, no_anim, 1)
end)
minetest.register_on_leaveplayer(function(player) players[player:get_player_name()] = nil end)
local function clamp(value, range)
if value > range.max then
return range.max
end
if value < range.min then
return range.min
end
return value
end
local function normalize_angle(angle)
return ((angle + 180) % 360) - 180
end
local function normalize_rotation(euler_rotation)
return vector.apply(euler_rotation, normalize_angle)
end
function handle_player_animations(dtime, player)
local mesh
do
local props = player:get_properties()
if not props then
-- HACK inside on_joinplayer, the player object may be invalid
-- causing get_properties() to return nothing - just ignore this
return
end
mesh = props.mesh
end
if static_model_names[mesh] then
player:set_properties{mesh = mesh}
elseif animated_model_names[mesh] then
mesh = animated_model_names[mesh]
end
local model = models[mesh]
if not model then
return
end
local conf = character_anim.conf.models[mesh] or character_anim.conf.default
local player_animation = get_playerdata(player)
local anim = player_animation.animation
if not anim then
return
end
local range, frame_speed, _, frame_loop = unpack(anim, 1, 4)
local animation_time = player_animation.animation_time
animation_time = animation_time + dtime
player_animation.animation_time = animation_time
local range_min, range_max = range.x + 1, range.y + 1
local keyframe
if range_min == range_max then
keyframe = range_min
elseif frame_loop then
keyframe = range_min + ((animation_time * frame_speed) % (range_max - range_min))
else
keyframe = math.min(range_max, range_min + animation_time * frame_speed)
end
local bones = {}
local animated_bone_props = model:get_animated_bone_properties(keyframe, true)
local body_quaternion
for _, props in ipairs(animated_bone_props) do
local bone = props.bone_name
if bone == "Body" then
body_quaternion = props.rotation
end
local position, rotation = modlib.vector.to_minetest(props.position), props.rotation
-- Invert quaternion to match Minetest's coordinate system
rotation = {-rotation[1], -rotation[2], -rotation[3], rotation[4]}
local euler_rotation = quaternion.to_euler_rotation(rotation)
bones[bone] = {position = position, rotation = rotation, euler_rotation = euler_rotation}
end
local Body = (bones.Body or {}).euler_rotation
local Head = (bones.Head or {}).euler_rotation
local Arm_Right = (bones.Arm_Right or {}).euler_rotation
local look_vertical = math.deg(player:get_look_vertical())
if Head then Head.x = -look_vertical end
local interacting = character_anim.is_interacting(player)
if interacting and Arm_Right then
local interaction_time = player_animation.interaction_time
-- Note: -90 instead of -Arm_Right.x because it looks better
Arm_Right.x = -90 - look_vertical - math.sin(-interaction_time) * conf.arm_right.radius
Arm_Right.y = Arm_Right.y + math.cos(-interaction_time) * conf.arm_right.radius
player_animation.interaction_time = interaction_time + dtime * math.rad(conf.arm_right.speed)
else
player_animation.interaction_time = 0
end
local look_horizontal = get_look_horizontal(player)
local diff = look_horizontal - player_animation.look_horizontal
if math.abs(diff) > 180 then
diff = math.sign(-diff) * 360 + diff
end
local moving_diff = math.sign(diff) * math.abs(diff) * math.min(1, dtime / conf.body.turn_speed)
player_animation.look_horizontal = player_animation.look_horizontal + moving_diff
if math.abs(moving_diff) < 1e-6 then
player_animation.look_horizontal = look_horizontal
end
local lag_behind = diff - moving_diff
local attach_parent, _, _, attach_rotation = player:get_attach()
if attach_parent then
local parent_rotation
if attach_parent.get_rotation then
parent_rotation = attach_parent:get_rotation()
else -- 0.4.x doesn't have get_rotation(), only yaw
parent_rotation = {x = 0, y = attach_parent:get_yaw(), z = 0}
end
if attach_rotation and parent_rotation then
parent_rotation = vector.apply(parent_rotation, math.deg)
local total_rotation = normalize_rotation(vector.subtract(attach_rotation, parent_rotation))
local function rotate_relative(euler_rotation)
if not euler_rotation then return end
euler_rotation.y = euler_rotation.y + look_horizontal
local new_rotation = normalize_rotation(vector.subtract(euler_rotation, total_rotation))
modlib.table.add_all(euler_rotation, new_rotation)
end
rotate_relative(Head)
if interacting then rotate_relative(Arm_Right) end
end
elseif Body and not modlib.table.nilget(rawget(_G, "player_api"), "player_attached", player:get_player_name()) then
Body.y = Body.y + lag_behind
if Head then Head.y = Head.y + lag_behind end
if interacting and Arm_Right then Arm_Right.y = Arm_Right.y + lag_behind end
end
-- HACK this essentially only works for very character.b3d-like models;
-- it tries to find the (sole) X-rotation of the body relative to a subsequent (180°) Y-rotation.
if body_quaternion then
local body_rotation = assert(assert(find_node(model.node, "Body")).rotation)
local body_x = quaternion.to_euler_rotation(modlib.quaternion.compose(body_rotation, body_quaternion)).x
if Head then Head.x = normalize_angle(Head.x - body_x) end
if interacting and Arm_Right then Arm_Right.x = normalize_angle(Arm_Right.x - body_x) end
end
if Head then
Head.x = clamp(Head.x, conf.head.pitch)
Head.y = clamp(Head.y, conf.head.yaw)
if math.abs(Head.y) > conf.head.yaw_restriction then
Head.x = clamp(Head.x, conf.head.yaw_restricted)
end
end
if Arm_Right then Arm_Right.y = clamp(Arm_Right.y, conf.arm_right.yaw) end
-- Replace animation with serverside bone animation
for bone, values in pairs(bones) do
local overridden_values = player_animation.bone_positions[bone]
overridden_values = overridden_values or {}
set_bone_position(player, bone,
overridden_values.position or values.position,
overridden_values.euler_rotation or values.euler_rotation)
end
end
minetest.register_globalstep(function(dtime)
for _, player in pairs(minetest.get_connected_players()) do
handle_player_animations(dtime, player)
end
end)

View file

@ -1,8 +0,0 @@
name = character_anim
title = Character Animations
description = Animates the character
author = LMD
depends = modlib
# these mods are supported, but don't necessarily need to load before character_anim
optional_depends = player_api, 3d_armor, skinsdb
release = 29484

View file

@ -1,65 +0,0 @@
local function angle(description, default)
return { type = "number", range = { min = -180, max = 180 }, description = description, default = default }
end
local range = function(description, default_min, default_max)
return {
type = "table",
entries = {
min = angle(description .. " (min)", default_min),
max = angle(description .. " (max)", default_max)
},
func = function(range)
if range.max < range.min then return "Minimum range value is not <= maximum range value" end
end
}
end
local model = {
type = "table",
entries = {
body = {
type = "table",
entries = {
turn_speed = {
type = "number",
range = { min_exclusive = 0, max = 1e3 },
description = "Body turn speed",
default = 0.2
}
}
},
head = {
type = "table",
entries = {
pitch = range("Head pitch", -60, 80),
yaw = range("Head yaw", -90, 90),
yaw_restricted = range("Head yaw restricted", 0, 45),
yaw_restriction = angle("Head yaw restriction", 60)
}
},
arm_right = {
type = "table",
entries = {
radius = angle("Right arm spin radius", 10),
speed = {
type = "number",
range = { min_exclusive = 0, max = 1e4 },
description = "Right arm spin speed",
default = 1e3
},
yaw = range("Right arm yaw", -30, 160)
}
}
}
}
return {
type = "table",
entries = {
default = model,
models = {
type = "table",
keys = { type = "string" },
description = "Other models, same format as `default` model"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,53 +0,0 @@
[character_anim.default]
[*character_anim.default.arm_right]
# Right arm spin radius
character_anim.default.arm_right.radius (Character anim Default Arm right Radius) float 10 -180.000000 180.000000
# Right arm spin speed
character_anim.default.arm_right.speed (Character anim Default Arm right Speed) float 1000 0.000000 10000.000000
[**character_anim.default.arm_right.yaw]
# Right arm yaw (max)
character_anim.default.arm_right.yaw.max (Character anim Default Arm right Yaw Max) float 160 -180.000000 180.000000
# Right arm yaw (min)
character_anim.default.arm_right.yaw.min (Character anim Default Arm right Yaw Min) float -30 -180.000000 180.000000
[*character_anim.default.body]
# Body turn speed
character_anim.default.body.turn_speed (Character anim Default Body Turn speed) float 0.2 0.000000 1000.000000
[*character_anim.default.head]
# Head yaw restriction
character_anim.default.head.yaw_restriction (Character anim Default Head Yaw restriction) float 60 -180.000000 180.000000
[**character_anim.default.head.pitch]
# Head pitch (max)
character_anim.default.head.pitch.max (Character anim Default Head Pitch Max) float 80 -180.000000 180.000000
# Head pitch (min)
character_anim.default.head.pitch.min (Character anim Default Head Pitch Min) float -60 -180.000000 180.000000
[**character_anim.default.head.yaw]
# Head yaw (max)
character_anim.default.head.yaw.max (Character anim Default Head Yaw Max) float 90 -180.000000 180.000000
# Head yaw (min)
character_anim.default.head.yaw.min (Character anim Default Head Yaw Min) float -90 -180.000000 180.000000
[**character_anim.default.head.yaw_restricted]
# Head yaw restricted (max)
character_anim.default.head.yaw_restricted.max (Character anim Default Head Yaw restricted Max) float 45 -180.000000 180.000000
# Head yaw restricted (min)
character_anim.default.head.yaw_restricted.min (Character anim Default Head Yaw restricted Min) float 0 -180.000000 180.000000
[character_anim.models]

View file

@ -1,54 +0,0 @@
-- See https://github.com/luanti-org/luanti/issues/15692
local mod = modlib.mod
local b3d = modlib.b3d
local media_paths = modlib.minetest.media.paths
local function is_perfect(quat)
local mat = modlib.matrix4.rotation(quat)
local diag_abs_sum = 0
for i = 1, 3 do
diag_abs_sum = diag_abs_sum + math.abs(mat[i][i])
end
return math.abs(diag_abs_sum - 3) < 1e-5
end
return function(name)
local stream = assert(io.open(media_paths[name], "rb"))
local character = assert(b3d.read(stream))
stream:close()
local function wiggle_rotation(quat)
if math.abs(quat[1]) + math.abs(quat[2]) + math.abs(quat[3]) < 1e-5 then return quat end -- identity
if not is_perfect(quat) then return quat end
local wiggled = {}
for i = 1, 4 do
wiggled[i] = quat[i] + 1e-3
end
wiggled = modlib.quaternion.normalize(wiggled)
if not is_perfect(wiggled) then return wiggled end
for i = 1, 4 do
wiggled[i] = quat[i] - 1e-3
end
wiggled = modlib.quaternion.normalize(wiggled)
if not is_perfect(wiggled) then return wiggled end
return quat -- this shouldn't happen
end
local function wiggle_rotations(node)
node.rotation = wiggle_rotation(node.rotation)
node.keys = {}
for _, child in ipairs(node.children) do
wiggle_rotations(child)
end
end
wiggle_rotations(character.node)
local rope = {}
character:write({write = function(_, str)
table.insert(rope, str)
end})
return table.concat(rope)
end

13
mods/cozylights/LICENSE Normal file
View file

@ -0,0 +1,13 @@
For code:
Copyright 2024 SingleDigitIq
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Media licenses:
default_glass.png is by Krock (CC0 1.0)
my debug textures are WTFPL if anything

256
mods/cozylights/README.md Normal file
View file

@ -0,0 +1,256 @@
# Cozy Lights
Improves the way light sources(torches etc) behave and allows to create huge lights, literal projectors with just a mouse click, light map will be computed for you.
Early alpha, but at least NotSoWow, Sumi, MisterE, Agura and Sharp have expressed curiosity, that already makes six of us, good enough for release. Feedback, suggestions, bug reports are very welcome. At this dev stage Cozy Lights can be good for builders in creative mode, singleplayer survival is somewhat ok, multiplayer is not yet recommended, unless it's 2-5 players or just schematics with cozy lights and no functionality.
**Light sources illuminate bigger area with default settings:**
![cozy nodecore](https://raw.githubusercontent.com/SingleDigitIQ/media/main/cozy_nodecore.gif)
Voxel light maps are a complete game changer - it is almost like going from 2d to 3d in terms of depth. You now have 14 shades for every visible building block, and it does not have to register 14 versions of every building block. Cobble only challenge has got a whole lot easier, something fun to look at with the least fun texture is possible now with just this mod :> Disabling smooth lighting might can make for an interesting aesthetic in some cases.
You can also build these lights just like you do with any structures, in other words, place invisible blocks of light of all possible engine light levels block-by-block. Tools are coming soon to make this process more user-friendly, right now you will need to make them visible and interactable in debug mode.
It is eventually supposed to become accurate enough so that if you learn how to draw, you will have an easier time understanding how depth and shadows work and what can be done with them.
**Cozy wielded light:**
![cozy wielded light](https://raw.githubusercontent.com/SingleDigitIQ/media/main/wielded_cozy_light_compressed.gif)
**WARNING:**
**1. after removing Cozy Lights from your world you will be left with spheres of unknown nodes. Easiest could be to reenable the mod and call ```/clearlights``` in all locations Cozy Lights are active.**
**2. if you have override_engine_lights enabled, then in case you ever remove Cozy Lights mod from your world, you will be left with broken lights. To fix it, you will need to use the mod fixmap or anything that updates/fixes engine lights. override_engine_lights is disabled by default, so it should be safe.**
## Known issues
1. worldedit:placeholder nodes can prevent light map from generating correctly and this currenly happens without notice or options provided. Current workaround is to define a worldedit region and run ```//replace worldedit:placeholder air``` before adding lights to the scene. This issue also involves cozy wielded light, wordedit placeholders can appear anywhere if the mod is active. There can be other invisible nodes from some mods and games which would interfere with light map.
2. You will have to disable K Ambient Light to use Cozy Lights, together, they are not recommended for now.
3. Light emitting liquids always straight up ignored, light emitting airlikes too
4. When there are too many light sources in a generated area, it gets ignored. If you run /rebuildlights in such area, it will attempt to do so, but probably would need too much time
5. If you are moving too fast(creative or falling from above) and its first time you visit many areas, generation will not look like it's immediate
6. Some lights are still being missed in generated mapblocks
*For what it does it's quite fast, it is supposed to somehow get even faster. I have recently discovered that my CPU is 10(!) years old and it's actually usable on my PC. Would appreciate if somebody with a beast PC would try this mod out and post a couple of benchmarks, and also if some phone poster will try to do the same*
## Light Brush
![creating a massive light with a click](https://raw.githubusercontent.com/SingleDigitIQ/media/main/light_brush_early_alpha_optimized.gif)
*Click or hold left mouse button* to draw light with given settings. Light Brush' reach is 100 nodes, so you can have perspective. Note: with radiuses over 30 nodes as of now mouse hold won't have an effect.
*On right click* settings menu opens up. The menu has hopefully useful tooltips for each setting. You can set radius, brightness, strength and draw mode. There are 6 draw modes so far: default, erase, override, lighten, darken and blend.
![light brush settings](https://raw.githubusercontent.com/SingleDigitIQ/media/main/concise_light_brush_settings_smol.jpg)
## Chat Commands
```/cozysettings``` opens a global settings menu for cozy lights, here you can adjust node light sources like torches, meselamps, fireflies, etc to make it work better with potato or make light reach mad far and stuff. Some settings which you can find in Minetest game settings for the mod are still not present here(like override_engine_lights which makes everything nicer). These changes persist after exiting and re-entering the world again.
![Global Cozy Lights Settings](https://raw.githubusercontent.com/SingleDigitIQ/media/main/cozysettings_or_zs_smol.jpg)
Currently max radius is 120 for commands below, if your value is invalid it will adjust to closest valid or throw an error. Some potatoes might experience issues with big radiuses. Eventually max radius will be much bigger.
```/clearlights <number>``` removes invisible light nodes in area with specified radius. Helpful to remove lights created with light brush. Example usage: ```/clearlights 120```
```/rebuildlights <number>``` rebuilds light map in an area with specified radius. Useful in case you changed the settings or accidentally broke some lights by other commands or by mining in debug. This can be slow if there are lots of light sources in the area with far reaching light. Example usage: ```/rebuildlights 40```
```/fixedges <number>``` fixes obstacles' opposite edges for light map in an area with specified radius. Default algorithm sacrifices accuracy for speed, because of that the lights can still go through diagonal walls if they are only one node thick, and as of now they can sometimes light up an edge(1 block from a corner) of the opposite side of an obstacle. With this command you are supposed to be able to fix it, but currently it's weird, broken. You can use it but the result wont necessarily look good.
```/cozydebugon <number>``` makes all cozy light nodes visible and interactable in an area with a specified radius. With it you can also basically build lights just as you would with any other structures before the tools for that are available.
```/cozydebugoff <number>``` makes all cozy light nodes invisible and non-interactable again in an area with a specified radius.
```/optimizeformobile <number>``` removes all cozy light nodes which do not touch a surface of some visible node, like cobble for example. It is maybe useful, because default algo spreads light in a sphere and lights up the air above the ground too, which might be a bit challenging for potato and mobile to render reliably, they might experience FPS drops. Good if you are building a schematic for a multiplayer server. This option might slightly decrease the quality of light map, example: you have a light node with strength of 7 above the ground, and that ground is visible because of that, but after using this option that light node will be removed, so that part of the ground might be left in complete darkness. Basically might make some places darker.
```/spawnlight <brightness float> <reach_factor float> <dim_factor float>``` spawn a light at your position which does not use user friendly light brush algo, but ambient light algo. "float" means it can be with some arbitrary amount of decimals, or simple integer
```/daynightratio <ratio float>``` change Minetest engine day_night_ratio for the player who used the command. ```0``` is the darkest night possible, you can observe how dark it can be on the screenshots, was useful in testing, probably will help with building too. ```1``` is the brightest possible day. Some gradations in between are maybe under appreciated and seem pretty moody, I guess that would depend on a texture pack.
```/cozyadjust <size number> <adjust_by number> <keep_map number>``` change brightness of all cozy light nodes by adjust_by value in the area of size. Adjust_by can be negative. Keep_map is 1 by default and can be omitted, when it's 1 and adjust_by will result in a value out of bounds of 1-14(engine light levels) even for one node in the area, the command will revert(have no effect at all), so that light map will be preserved. If you are ok with breaking light map, type 0 for keep_map.
Shortcuts for all commands follow a convention to easier memorize them:
```zcl``` - clearlights
```zrl``` - rebuildlights
```zfe``` - fixedges
```zdon``` - cozydebugon
```zdoff``` - cozydebugoff
```zofm``` - optimizeformobile
```zsl``` - spawnlight
```zs``` - cozysettings
```zdnr``` - daynightratio
```za``` - cozyadjust
## Supported mods and games
Most of the most popular ones on paper, but its early alpha, so it can still be broken. It's not just popular ones, actually no idea how many it supports, some of them are not even on ContentDB.
For definitely supported games, check the section of supported games on ContentDB, or mod.conf, if the game is in a list then support is full for what the mod can currently do, current *known* exceptions are:
**Nodecore** - partial support, light map does not update for dynamic light sources(the ones that change brightness over time)
**Age of Mending** - partial support, too many light sources in caves sometimes, and so far Cozy Lights cant process that without completely freezing everything for some time
**Piranesi** - does not seem to work at all, probably something schematic related
**Shadow Forest** - it works as intended, but there is only campfire to make cozy, wont feel like an upgrade
If a mod or a game you like is not supported or there are some problems not listed here, tell me immediately. You can just drop a list of games/mods you have issues with in review. Eventually cozy lights' support will attempt to balance the overall feel and look of the game/mod with meticulous consideration, but we are not at that stage yet.
## For Developers
There are like I think 5 algo versions of drawing lights or I refactored that, because I never heard of DRY, never happened. All algos sacrifice accuracy for speed and miss some nodes for huge spheres.
*Plans for API:*
- You will be able to override cozylights' global step, disable it and call it from your global step
- You will be able to override any default settings
- Register unique settings for specific nodes
## Todo
- is it possible to have trees grow within the radius of a light block like torches
- add undo
- figure out what to do about lights going through diagonal, one node thick walls. also still somehow manage to keep algo cheap
- Optimize memory usage, use several voxel manipulators for biggest lights, will be slower but much more stable, also increase max radius to even more mentally challenged value
- see what can be done with race condition of wielded light and node light
- save brush settings in item metadata and change icon somehow to resemble the settings
- add /disableongen
- all queues should be saved in case of server shutdown, so they can be resumed
- add /ignore certain block
- algo for many adjacent lights
- see what can be done about snow and slabs not passing the light through
- make dropped items emit cozy light if they have light_source above 0, just like in original wielded light mod
- make sure bigger lights wont go unnoticed in on_generated and schematic placement. apparnetly on generated can support lights up to 80 if max area radius is 120
- stress test it with heavily modded worlds, possible problem: luajit ram limit for default luajit on linux?
- illuminate transparent liquids too if possible without making it look weird, except dont make floodable light sources work underwater just like in original wielded light
- fix nodecore dynamic light source not updating the brightness/radius
- add privileges so schematics can be used on multiplayer server
- parse minetest forum for optional_depends
- add inventory images for lights and debug lights, make them only available in creative
- make darkness nodes, wielded darkness, Darkness Brush
- add static natural scene(stop the time, fix the sun/moon in one position, update the area accordingly)
- raytracing
- allow people to run cpu and memory-friendly minimal schematic support version, for multiplayer servers for example
- if certain treshold of light source commonality in an area is reached, those light sources should be ignored
- would it be possible without too much work to programatically determine global commonality of a node from mapgen?
- add optional more pleasant day/night cycle
- add optional sky textures
- add multiplayer/mobile settings(very little light nodes, very simple light map), and mid settings(more or less okayish), max is default
- move to base "unsafe" methods for tables? seems like luajit optimizes it all away and it's useless to bother?
- try spread work over several loops and try vector.add
- maybe three types of darkness nodes, ones that are completely overridable with cozylights, and ones that arent(make a darker light shade), and ones that completely ignore cozylights
- lights auto rebuild on first load after settings change?
- make a table for existing decoration nodes
- make sure spheres of big sizes dont miss too many blocks
- give light sources metadata, so when nearby light sources are destroyed you can find and rebuild easily, also give metadata to light brush epicenter for the same reason
- maintain files in which you record light source positions, which can be quickly grabbed to rebuild lights if there is a removal
- add cone light blocks, so those lights can be built on top of each other to make static lights from old games
- add light grabber tool, Light Excavation Tool 9000 TURBO V3, so that the light wont be selectable without it
- add Consumer Grade Reality Bending Device to create preset nodes with chosen qualities
- add global step override api, ability to implement cozylights global step into a game/other mod global step more efficiently, maybe add generic global step call like mainloop or mainstep, see what other games do with it, choose or create convention for this i guess
- add handle_async where it makes sense
- ci for optional_depends auto update according to content db mods/games updates and releases
### Some expensive notes stackoverflow will never tell about LuaJIT to you or to future me. Summing up my discord rambling because COVID made me forget some of Lua I tried before, so I am writing it down for now.
TLDR: LuaJIT is certainly impressive in some parts, however I would rather refrain from using it for absolutely anything that implies even a bit of performance, and unless there is no way to avoid it, deprecate Lua as a terrible inconvenience and never look back. It's too slow, and when you try to squeeze anything out of it, it loses most of its appeal/narrative, it even loses purpose. If still too many words, remember just this about Lua: never try to optimize Lua too much, it's never worth it, and, just let Lua iterate.
1. While being smol, it still fails to outperform another state of the art JIT - JS V8. And thats given that JS V8 is big tech kind state of the art, which means there is certainly at the very least a significant room for improvement. Advantage of Lua in comparison to V8: less RAM consuption for small programs, so it's reasonable to run a bit of LuaJIT on weak hardware, like phones, watches, some smart-whatever, robots. In that case it's not the worst choice.
2. Readability syntax is a meme, I am here to code, not to shitpost, I prefer completely different state of mind from that, something very different, like curly braces and what not.
3. Lua bytecode, same way as Python, keeps function and variable names uncompressed. You could argue but hey that means we can at least restore the original file almost one-to-one from bytecode? Who needs that really, when RAM efficiency is 25%(!) better after using a minifier, and if you use minifier, you abandon debugging and readability(just like in Python, which is a meme language too, and it's typical very readable one letter long variable names). This is how as codebase grows, Lua loses it's only advantage over V8. Hence technically peak Lua is a joke.
3. Peak performance Lua cant be, without a lot of effort, transpiled and rewritten into peak performance compiled language, since it's behavior and optimization techniques are drastically different and by that I mean next level drastically different. Therefore it's not as good for prototyping as JS V8, unless you denounce the very idea of optimizing Lua as heresy.
4. LuaJIT does so much behind your back, so that performance becomes exhaustingly unpredictable. Stuff that works in nearly any other language does not work here, and you will often have to rely on profiler no matter how good you are with Lua, rather than act according to assumptions based on fundamentals. You could even say that what you have to do when Lua performance is concerned, is literal trial and error. You then find a sweet spot and never touch that part of the code again, because it's impossible to reason with/reliably reproduce/improve upon.
5. While you could cope that CPU just does not have enough cache and all, clearly, LuaJIT is best at optimizing *simple* loops. Branches, hash look ups, math? Try your best to decrease the amount for all of those in a loop. It appears that Lua would rather iterate uselessly over and over again the same entries, than have a branch to cut amount of iterations/operations in general. Optimization tip is basically this: try to break down a complicated loop into several simpler loops. LuaJIT is ridiculously fast with simple loops.
6. It appears that most popular object positioned most efficiently in memory. I am not entirely certain how exactly does that happen, because I didn't study LuaJIT source much since it's underwhelming performance in anything remotely complicated leaves me feeling powerless, so it's not fun. A hack could be a loop that interacts with an object on startup, if you call/interact with the object enough times it will be slightly faster. It is noticeable in massively expensive loops.
7. If you know you are guaranteeed to have an object consume more RAM during runtime, you may want to preallocate if the codebase is complex enough. Well, at least this behavior can be fully expected based on fundamentals.
8. Refrain from having too many hash look-ups, it's not slow by itself, but apparently it clogs cache fast, so sometimes adding just one hash look-up can result in a massive drop in performance. Surprisingly, refrain from having too much of one of the most simplest parts of LuaJIT - math, best is to pull numbers out of Lua' ass, like in Cozy Lights. Luckily predictably this time, branches are the worst in a loop, however this time they are bad even if they cut a massive amount of operations. So you have to balance here, peak performance Lua demands abandoning DRY completely, but that also means you have to consume more RAM. Ideal Lua loop is when it does nothing at all, just iterates. Just let Lua iterate.
9. You can obviously somewhat control cache with local variables, but there is a catch, it only gives somewhat coherent performance results if the loop is very simple.
10. Apparently because of memory allocation being complex in LuaJIT, it can crash during trying to allocate too much in one go as if it's in the earliest dev stage and not ready for prod. JS V8 maybe leaks, but at least does not crash just like that.
11. To make any good use of ffi types, you have to be aware of the fact that amount of types in function context will affect it's performance. More types = slower. So same as V8, it might optimize smaller functions better, but not necessarily, it depends: if it's one lua type number and there is a lot of work to do for that type, then you better off having a big one surely. Ffi is not a simple plug-and-play for previously optimized pure Lua algo, you may need to restructure your code to ensure more types dont clog the cache. And with Minetest API it may end up being useless.
12. Offloading work to C has it's caveats. If you are doing it through ffi and need to manipulate a lot of data, like vm_data in Cozy Lights example, while your algorithm itself will be faster, if like with Minetest example, the api expects lua table and only that, you will have to interpret C results, run a loop to make a lua table, and that part is so extremely slow, you might end up with slower code overall. In less complicated cases it's useful.
## LICENSE
MIT+(you are not legally allowed to infect it with GPL, AGPL or EUPL) for my code.
Will appreciate reasonable attribution, as in, dont be a typical open source dev who takes a good part of some other open source project and only mentions it in code, so that not only most of the devs, users have no way of ever learning about that.
And there is a texture from MTG, which will be eventually replaced:
default_glass.png is by Krock (CC0 1.0)
my debug textures are WTFPL if anything

View file

@ -0,0 +1,536 @@
local c_air = minetest.get_content_id("air")
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local c_light14 = c_lights[14]
local c_light_debug1 = minetest.get_content_id("cozylights:light_debug1")
local c_light_debug14 = c_light_debug1 + 13
local mf = math.floor
local clearlights = {
params = "<size>",
description = "removes cozy and debug light nodes. max radius is 120 for now",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /clearlights "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
cozylights:clear(pos,size)
return true, "Done."
end,
}
local rebuildlights = {
params = "<size>",
description = "force rebuilds lights in the area. max radius is 120 for now",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /rebuildlights "..size.." at position: "..cozylights:dump(pos))
if size > 120 then return false, "Radius is too big" end
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
for i in a:iterp(minp,maxp) do
local node_name = minetest.get_name_from_content_id(data[i])
local cozy_item = cozylights.cozy_items[node_name]
if cozy_item ~= nil then
local radius, _ = cozylights:calc_dims(cozy_item)
local posrebuild = a:position(i)
if vector.in_area(vector.subtract(posrebuild, radius), minp, maxp)
and vector.in_area(vector.add(posrebuild, radius), minp, maxp)
then
cozylights:draw_node_light(posrebuild, cozy_item, vm, a, data, param2data)
else
local single_light_queue = cozylights.single_light_queue
single_light_queue[#single_light_queue+1] = {
pos=posrebuild,
cozy_item=cozy_item
}
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
return true, "Done."
end,
}
local fixedges = {
params = "<size>",
description = "same as rebuild lights but additionally fixes edges for all lights in the area, regardless of always_fix_edges setting. max radius is 120",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /fixedges "..size.." at position: "..cozylights:dump(pos))
if size > 120 then return false, "Radius is too big" end
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
for i in a:iterp(minp,maxp) do
local node_name = minetest.get_name_from_content_id(data[i])
local cozy_item = cozylights.cozy_items[node_name]
if cozy_item ~= nil then
local radius, _ = cozylights:calc_dims(cozy_item)
local posrebuild = a:position(i)
if vector.in_area(vector.subtract(posrebuild, radius), minp, maxp)
and vector.in_area(vector.add(posrebuild, radius), minp, maxp)
then
cozylights:draw_node_light(posrebuild, cozy_item, vm, a, data, param2data, true)
else
local single_light_queue = cozylights.single_light_queue
single_light_queue[#single_light_queue+1] = {
pos=posrebuild,
cozy_item=cozy_item
}
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
return true, "Done."
end,
}
local cozydebugon = {
params = "<size>",
description = "replaces cozy light nodes with debug light nodes which are visible and interactable in an area",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /cozydebugon "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
local minp,maxp,vm,data,_,a = cozylights:getVoxelManipData(pos,size)
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cid >= c_light1 and cid <= c_light14 then
data[i] = cid + 14
end
end
cozylights:setVoxelManipData(vm,data)
return true, "Done."
end,
}
local cozydebugoff = {
params = "<size>",
description = "replaces debug light nodes back with cozy light nodes in an area",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /cozydebugoff "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
local minp,maxp,vm,data,_,a = cozylights:getVoxelManipData(pos,size)
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cid >= c_light14 + 1 and cid <= c_light_debug14 then
data[i] = cid - 14
end
end
cozylights:setVoxelManipData(vm,data)
return true, "Done."
end,
}
local optimizeformobile = {
params = "<size>",
description = "optimizes schematic for mobile and potato gpu",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size = tonumber(param) or cozylights.default_size
minetest.log("action", name .. " uses /optimizeformobile "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
local zstride, ystride = a.zstride, a.ystride
local function keep(i)
local keep = false
for inz = -1,1 do
for iny = -1,1 do
for inx = -1,1 do
local inidx = i + inx + iny*ystride + inz*zstride
if a:containsi(inidx) then
local incid = data[inidx]
if incid ~= c_air and (incid < c_light1 or incid > c_light14 + 14) then
keep = true
break
end
end
end
end
end
return keep
end
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cid >= c_light1 and cid <= c_light14 + 14 then
if not keep(i) then
data[i] = c_air
param2data[i] = 0
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
return true, "Done."
end,
}
local spawnlight = {
params = "<brightness> <radius> <strength>",
description = "spawns light_brush-like light with given characteristics at player position",
func = function(name, param)
local brightness, radius, strength = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
local pos = vector.round(minetest.get_player_by_name(name):getpos())
minetest.log("action", name .. " uses /optimizeformobile "..brightness.." "..radius.." "..strength.." at position: "..cozylights:dump(pos))
brightness = mf(tonumber(brightness) or 0)
if brightness < 0 then brightness = 0 end
if brightness > 14 then brightness = 14 end
radius = tonumber(radius) or 0
if radius < 0 then radius = 0 end
if radius > 120 then radius = 120 end
strength = tonumber(strength) or 0
if strength < 0 then strength = 0 end
if strength > 1 then strength = 1 end
local lb = {brightness=brightness,radius=radius,strength=strength,mode=0,cover_only_surfaces=0}
cozylights:draw_brush_light(pos, lb)
return true, "Done."
end,
}
local cozysettingsgui = {
privs = {},
description = "changes global ambient light settings",
func = function(name)
local settings_formspec = {
"formspec_version[4]",
--"size[6,6.4]",
"size[5.2,8.6]",
"label[0.95,0.5;Global Cozy Lights Settings]",
"label[0.95,1.35;Wielded Light Radius]",
"field[3.5,1.1;0.8,0.5;wielded_light_radius;;"..cozylights.max_wield_light_radius.."]",
"tooltip[0.95,1.1;3.4,0.5;If radius is -1 cozy wielded light is disabled, if 0 then only one node will be lit up just like in familiar Minetest wielded light mod.\n"..
"If not zero then it's a sphere of affected nodes with specified radius.\n"..
"Max radius is 30 as of now. If you run a potato - you may want to decrease it.]",
"label[0.95,2.05;Wield Light Step]",
"field[3.5,1.8;0.8,0.5;wield_step;;"..cozylights.wield_step.."]",
"tooltip[0.95,1.8;3.4,0.5;Cozy Lights Wield Light step - smaller value will result in more frequent, fluid wield light update.\n"..
"Smaller values might be too expensive for potato. Valid values are from 0.01 to 10.00]",
"label[0.95,2.75;Brush Hold Step]",
"field[3.5,2.5;0.8,0.5;brush_hold_step;;"..cozylights.brush_hold_step.."]",
"tooltip[0.95,2.5;3.4,0.5;Brush Hold Step - smaller value will result in more frequent, fluid light update for light brush on mouse hold.\n"..
"Smaller values wont necessarily result in a more fluid update even on a beast PC, because Minetest architecture is too complicated for it to not be janky. Valid values are from 0.01 to 10.00]",
"label[0.95,3.45;On_Gen() Step]",
"field[3.5,3.2;0.8,0.5;on_gen_step;;"..cozylights.on_gen_step.."]",
"tooltip[0.95,3.2;3.4,0.5;Generated areas step - smaller value will result in more frequent, fluid light update for newly generated map chunks and schematics if they have light sources, it will also rebuild lights faster when something was destroyed.\n"..
"Small values will be too expensive for potato. Valid values are from 0.01 to 10.00]",
"label[0.95,4.15;Brightness Factor]",
"field[3.5,3.9;0.8,0.5;brightness_factor;;"..cozylights.brightness_factor.."]",
"tooltip[0.95,3.9;3.4,0.5;Brightness factor determines how bright overall(relative to own light source brightness) the light will be.\n"..
"Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
"Valid values are from -10.0 to 10.0.\n"..
"Brightness factor is not an equivalent of light source brightness(from 1 to 14), it is very low key, affects lights slightly.]",
"label[0.95,4.85;Reach Factor]",
"field[3.5,4.6;0.8,0.5;reach_factor;;"..cozylights.reach_factor.."]",
"tooltip[0.95,4.6;3.4,0.5;Reach factor determines how far light of all light source nodes will reach.\n"..
"Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
"Valid values are from 0.0 to 10.0.\n"..
"Not recommended to change if you are not willing to spend probably a lot of time tuning lights.\n"..
"Not recommended to Increase if you run a potato.]",
"label[0.95,5.55;Dim Factor]",
"field[3.5,5.3;0.8,0.5;dim_factor;;"..cozylights.dim_factor.."]",
"tooltip[0.95,5.3;3.4,0.5;Dim factor determines how quickly the light fades farther from the source.\n"..
"Affects placed nodes(like torches, mese lamps, etc) and wielded light, but not light brush.\n"..
"Valid values are from 0.0 to 10.0.\n"..
"Not recommended to change if you are not willing to spend probably a lot of time tuning lights.\n"..
"Not recommended to Decrease if you run a potato.]",
"label[0.95,6.25;Uncozy mode]",
"field[3.5,6;0.8,0.5;uncozy_mode;;"..cozylights.uncozy_mode.."]",
"tooltip[0.95,6;3.4,0.5;Uncozy mode if above 0 removes all cozy lights in an area with specified radius around all players continuously.\n"..
"Useful before Cozy Lights mod uninstall. If you uninstall without prior running this, you will be left with spheres of unknown nodes in an area where Cozy Lights were active.\n"..
"Valid values are from 0 to 120. As of now can crash with out of memory error if you move too fast and the radius is too high.]",
"label[0.95,6.95;Crispy Potato]",
"checkbox[3.5,6.95;crispy_potato;;"..(cozylights.crispy_potato == true and "true" or "false").."]",
"tooltip[0.95,6.7;3.4,0.4;Crispy Potato when checked will auto adjust wielded light and generated area step times to keep potato alive.]",
"button_exit[1.1,7.5;3,0.8;confirm;Confirm]",
}
minetest.show_formspec(name, "cozylights:settings",table.concat(settings_formspec, ""))
return true
end,
}
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= ("cozylights:settings") then return end
if player == nil then return end
if fields.wielded_light_radius then
local wielded_light_radius = tonumber(fields.wielded_light_radius) > 30 and 30 or tonumber(fields.wielded_light_radius)
wielded_light_radius = wielded_light_radius < -1 and -1 or mf(wielded_light_radius or -1)
cozylights:set_wielded_light_radius(wielded_light_radius)
cozylights:switch_wielded_light(wielded_light_radius ~= -1)
end
if fields.wield_step then
local wield_step = tonumber(fields.wield_step) > 1 and 1 or tonumber(fields.wield_step)
wield_step = wield_step < 0.01 and 0.01 or wield_step
cozylights:set_wield_step(wield_step)
end
if fields.brush_hold_step then
local brush_hold_step = tonumber(fields.brush_hold_step) > 1 and 1 or tonumber(fields.brush_hold_step)
brush_hold_step = brush_hold_step < 0.01 and 0.01 or brush_hold_step
cozylights:set_brush_hold_step(brush_hold_step)
end
if fields.on_gen_step then
local on_gen_step = tonumber(fields.on_gen_step) > 1 and 1 or tonumber(fields.on_gen_step)
on_gen_step = on_gen_step < 0.01 and 0.01 or on_gen_step
cozylights:set_on_gen_step(on_gen_step)
end
if fields.brightness_factor then
local brightness_factor = tonumber(fields.brightness_factor) > 10 and 10 or tonumber(fields.brightness_factor)
cozylights.brightness_factor = brightness_factor < -10 and -10 or brightness_factor or 3
minetest.settings:set("cozylights_brightness_factor",cozylights.brightness_factor)
end
if fields.reach_factor then
local reach_factor = tonumber(fields.reach_factor) > 10 and 10 or tonumber(fields.reach_factor)
cozylights.reach_factor = reach_factor < 0 and 0 or reach_factor or 4
minetest.settings:set("cozylights_reach_factor",cozylights.reach_factor)
end
if fields.dim_factor then
local dim_factor = tonumber(fields.dim_factor) > 10 and 10 or tonumber(fields.dim_factor)
cozylights.dim_factor = dim_factor < 0 and 0 or dim_factor or 9
minetest.settings:set("cozylights_dim_factor",cozylights.dim_factor)
end
if fields.crispy_potato then
cozylights.crispy_potato = fields.crispy_potato == "true" and true or false
minetest.settings:set_bool("cozylights_crispy_potato", cozylights.crispy_potato)
end
if fields.uncozy_mode then
local uncozy_mode = tonumber(fields.uncozy_mode) > 120 and 120 or tonumber(fields.uncozy_mode)
cozylights.uncozy_mode = uncozy_mode < 0 and 0 or uncozy_mode
minetest.settings:set("cozylights_uncozy_mode", cozylights.uncozy_mode)
end
end)
local cozysettings = {
params = "<brightness> <reach_factor> <dim_factor>",
privs = {},
description = "changes global ambient light settings",
func = function(_, param)
local brightness_, reach_factor_, dim_factor_ = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
brightness_ = tonumber(brightness_)
if brightness_ ~= nil then
cozylights.brightness_factor = brightness_
minetest.settings:set("cozylights_brightness_factor",brightness_)
end
reach_factor_ = tonumber(reach_factor_)
if reach_factor_ ~= nil then
cozylights.reach_factor = reach_factor_
minetest.settings:set("cozylights_reach_factor",reach_factor_)
end
dim_factor_ = tonumber(dim_factor_)
if dim_factor_ ~= nil then
cozylights.dim_factor = dim_factor_
minetest.settings:set("cozylights_dim_factor",dim_factor_)
end
return true, "set brightness as "..brightness_..", reach_factor as "..reach_factor_..", dim_factor as "..dim_factor_
end,
}
local daynightratio = {
params = "<ratio>",
description = "fixes old schematic torches alignment to walls and what not",
func = function(name, param)
local player = minetest.get_player_by_name(name)
local pos = vector.round(player:getpos())
local ratio = tonumber(param)
minetest.log("action", name .. " uses /daynightratio "..ratio.." at position: "..cozylights:dump(pos))
if ratio > 1.0 then ratio = 1 end
if ratio < 0 then ratio = 0 end
player:override_day_night_ratio(ratio)
return true, "Done."
end,
}
local cozyadjust = {
params = "<size> <adjust_by> <keep_map>",
description = "adjust brightness of all light nodes in the area.\n"..
"keep_map is 1 by default and can be omitted, when it's 1 and adjust_by will result in a value out of bounds of 1-14(engine light levels) "..
"even for one node in the area, the command will revert(have no effect at all), so that light map will be preserved\n"..
"if you are ok with breaking light map, type 0 for third value",
func = function(name, param)
local pos = vector.round(minetest.get_player_by_name(name):getpos())
local size, adjust_by, keep_map = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
size = mf(tonumber(size) or cozylights.default_size)
adjust_by = mf(tonumber(adjust_by) or 1)
keep_map = mf(tonumber(keep_map) or 1)
minetest.log("action", name .. " uses /clearlights "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cid >= c_light1 and cid <= c_light14 then
local precid = cid + adjust_by
if precid >= c_light1 and precid <= c_light14 then
data[i] = precid
param2data[i] = precid
elseif keep_map == 1 then
return false, "Aborted to preserve light map."
elseif precid > c_light14 then
data[i] = c_light14
param2data[i] = c_light14
else
data[i] = c_air
param2data[i] = 0
end
elseif cid >= c_light_debug1 and cid <= c_light_debug14 then
local precid = cid + adjust_by
if precid >= c_light_debug1 and precid <= c_light_debug14 then
data[i] = precid
param2data[i] = precid
elseif keep_map == 1 then
return false, "Aborted to preserve light map."
elseif precid > c_light_debug14 then
data[i] = c_light_debug14
param2data[i] = c_light_debug14
else
data[i] = c_air
param2data[i] = 0
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
return true, "Done."
end,
}
local fixtorches = {
params = "<size>",
description = "fixes old schematic torches alignment to walls and what not",
func = function(name, param)
local placer = minetest.get_player_by_name(name)
local pos = vector.round(placer:getpos())
local size = mf(tonumber(param) or cozylights.default_size)
minetest.log("action", name .. " uses /fixtorches "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
local c_torch = minetest.get_content_id("default:torch")
local c_torch_wall = c_torch + 1
local c_torch_ceiling = c_torch + 2
local ystride, zstride = a.ystride, a.zstride
for i in a:iterp(minp, maxp) do
if data[i] == c_torch then
local above = minetest.registered_nodes[minetest.get_name_from_content_id(data[i-ystride])]
if above.walkable == true then
data[i] = c_torch_ceiling
param2data[i] = 0
end
local below = minetest.registered_nodes[minetest.get_name_from_content_id(data[i-ystride])]
if below.walkable == true then
data[i] = c_torch
param2data[i] = 1
end
if above.walkable == false and below.walkable == false then
local plusz = minetest.registered_nodes[minetest.get_name_from_content_id(data[i + zstride])]
data[i] = c_torch_wall
if plusz.walkable == true then param2data[i] = 4 end
local minusz = minetest.registered_nodes[minetest.get_name_from_content_id(data[i - zstride])]
if minusz.walkable == true then param2data[i] = 5 end
local plusx = minetest.registered_nodes[minetest.get_name_from_content_id(data[i + 1])]
if plusx.walkable == true then param2data[i] = 2 end
local minusx = minetest.registered_nodes[minetest.get_name_from_content_id(data[i - 1])]
if minusx.walkable == true then param2data[i] = 3 end
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
return true, "Done."
end,
}
local uncozymode = {
params = "<size>",
description = "clears lights every 5 seconds around every player in area of given radius.\n "..
"Useful before Cozy Lights uninstall or for those who is uncertain about the mod.\n",
func = function(name, param)
local placer = minetest.get_player_by_name(name)
local pos = vector.round(placer:getpos())
local size = mf(tonumber(param) or cozylights.default_size)
minetest.log("action", name .. " uses /uncozymode "..size.." at position: "..cozylights:dump(pos))
if size > 120 then
return false, "Radius is too big"
end
cozylights.uncozy_mode = size
minetest.settings:set("cozylights_uncozy_mode",size)
return true, "Done."
end,
}
local cozymode = {
description = "stops /uncozymode.",
func = function(name)
local placer = minetest.get_player_by_name(name)
local pos = vector.round(placer:getpos())
minetest.log("action", name .. " uses /cozymode at position: "..cozylights:dump(pos))
cozylights.uncozy_mode = 0
minetest.settings:set("cozylights_uncozy_mode",0)
return true, "Done."
end,
}
minetest.register_chatcommand("clearlights", clearlights)
minetest.register_chatcommand("zcl", clearlights)
minetest.register_chatcommand("rebuildlights", rebuildlights)
minetest.register_chatcommand("zrl", rebuildlights)
minetest.register_chatcommand("fixedges", fixedges)
minetest.register_chatcommand("zfe", fixedges)
minetest.register_chatcommand("cozydebugon", cozydebugon)
minetest.register_chatcommand("zdon", cozydebugon)
minetest.register_chatcommand("cozydebugoff", cozydebugoff)
minetest.register_chatcommand("zdoff", cozydebugoff)
minetest.register_chatcommand("optimizeformobile", optimizeformobile)
minetest.register_chatcommand("zofm", optimizeformobile)
minetest.register_chatcommand("spawnlight", spawnlight)
minetest.register_chatcommand("zsl", spawnlight)
minetest.register_chatcommand("cozysettings", cozysettingsgui)
minetest.register_chatcommand("zs", cozysettingsgui)
minetest.register_chatcommand("daynightratio", daynightratio)
minetest.register_chatcommand("zdnr", daynightratio)
minetest.register_chatcommand("cozyadjust", cozyadjust)
minetest.register_chatcommand("za", cozyadjust)
minetest.register_chatcommand("fixtorches", fixtorches)
minetest.register_chatcommand("zft", fixtorches)
minetest.register_chatcommand("uncozymode", uncozymode)
minetest.register_chatcommand("uzm", uncozymode)
minetest.register_chatcommand("cozymode", cozymode)
minetest.register_chatcommand("zm", cozymode)

View file

@ -0,0 +1,41 @@
function cozylights:dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. cozylights:dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function cozylights:finalize(table)
return setmetatable({}, {
__index = table,
__newindex = nil
})
end
function cozylights:prealloc(table, amount, default_val)
for i = 1, amount do
table[i] = default_val
end
end
function cozylights:mod_loaded(str)
if minetest.get_modpath(str) ~= nil then
return true
end
return false
end
function cozylights:findIn(value,array)
for i=1, #array do
if array[i] == value then
return true
end
end
return false
end

688
mods/cozylights/init.lua Normal file
View file

@ -0,0 +1,688 @@
cozylights = {
-- constant size values and tables
version = "0.2.8",
default_size = tonumber(minetest.settings:get("mapfix_default_size")) or 40,
brightness_factor = tonumber(minetest.settings:get("cozylights_brightness_factor")) or 8,
reach_factor = tonumber(minetest.settings:get("cozylights_reach_factor")) or 2,
dim_factor = tonumber(minetest.settings:get("cozylights_dim_factor")) or 9.5,
wield_step = tonumber(minetest.settings:get("cozylights_wield_step")) or 0.01,
brush_hold_step = tonumber(minetest.settings:get("cozylights_brush_hold_step")) or 0.07,
on_gen_step = tonumber(minetest.settings:get("cozylights_on_gen_step")) or 0.7,
max_wield_light_radius = tonumber(minetest.settings:get("cozylights_wielded_light_radius")) or 17,
override_engine_lights = minetest.settings:get_bool("cozylights_override_engine_lights", false),
always_fix_edges = minetest.settings:get_bool("cozylights_always_fix_edges", false),
uncozy_mode = tonumber(minetest.settings:get("cozylights_uncozy_mode")) or 0,
crispy_potato = minetest.settings:get_bool("cozylights_crispy_potato", true),
-- this is a table of modifiers for global light source settings.
-- lowkeylike and dimlike usually assigned to decorations in hopes to make all ambient naturally occuring light sources weaker
-- this is for two reasons:
-- 1. performance: never know how many various nice looking blocks which emit light will be there, or for example computing lights for
-- every node of a lava lake would be extremely expensive if those would reach far/would be very bright
-- 2. looks: they were made with default engine lighting in mind, so usually are very frequent, with such frequency default cozylights
-- settings will make the environment look blunt
coziest_table = {
--"dimlike"
[1] = {
brightness_factor = 0,
reach_factor = 0,
dim_factor = 0
},
--"lowkeylike" almost the same as dimlike, but reaches much farther with its barely visible light
[2] = {
brightness_factor = 0,
reach_factor = 2,
dim_factor = -3
},
-- "candlelike" something-something
[3] = {
brightness_factor = 0,
reach_factor = 2,
dim_factor = -3
},
-- "torchlike" torches, fires, flames. made much dimmer than what default engine lights makes them
[4] = {
brightness_factor = -2,
reach_factor = 0,
dim_factor = 0
},
-- "lamplike" a bright source, think mese lamp(actually turned out its like a projector, and below is even bigger projector)
[5] = {
brightness_factor = 0,
reach_factor = 3,
dim_factor = 4
},
-- "projectorlike" a bright source with massive reach
[6] = {
brightness_factor = 1,
reach_factor = 3,
dim_factor = 4
},
},
-- appears nodes and items might not necessarily be the same array
source_nodes = nil,
cozy_items = nil,
-- dynamic size tables, okay now what about functions
cozycids_sunlight_propagates = {},
cozycids_light_sources = {},
cozyplayers = {},
area_queue = {},
single_light_queue = {},
modpath = minetest.get_modpath(minetest.get_current_modname())
}
local modpath = minetest.get_modpath(minetest.get_current_modname())
dofile(modpath.."/helpers.lua")
-- backrooms test attempts to resolve mt engine lights problem with invisible lights, default settings will result
-- in many places being very well lit
-- me thinks ideal scenery with cozy lights in particular can be achieved with removal of all invisible lights
-- it also looks interesting after maybe a two thirds of light sources are broken
-- however the backrooms idea is not about broken windows theory at all, more about supernatural absence of any life
-- in a seemingly perfectly functioning infinite manmade mess, or idk i am not mentally masturbating any further,
-- some of the internets do that way too often, way too much
if cozylights:mod_loaded("br_core") then
cozylights.brightness_factor = cozylights.brightness_factor - 6
end
--if cozylights:mod_loaded("default") then
-- default.can_grow = function(pos)
-- local node_under = minetest.get_node_or_nil({x = pos.x, y = pos.y - 1, z = pos.z})
-- if not node_under then
-- return false
-- end
-- if minetest.get_item_group(node_under.name, "soil") == 0 then
-- return false
-- end
-- local light_level = minetest.get_node_light(pos)
-- if not light_level or light_level < 13 then
-- return false
-- end
-- return true
-- end
--end
dofile(modpath.."/nodes.lua")
dofile(modpath.."/shared.lua")
dofile(modpath.."/chat_commands.lua")
--ffi = require("ffi")
dofile(modpath.."/wield_light.lua")
dofile(modpath.."/node_light.lua")
dofile(modpath.."/light_brush.lua")
local c_air = minetest.get_content_id("air")
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local mf = math.floor
------------------------------------------
minetest.register_on_mods_loaded(function()
local source_nodes = {}
local cozy_items = {}
local cozycids_sunlight_propagates = {}
local cozycids_light_sources = {}
local override = cozylights.override_engine_lights
for _,def in pairs(minetest.registered_items) do
if def.light_source and def.light_source > 1
and def.drawtype ~= "airlike" and def.drawtype ~= "liquid"
and string.find(def.name, "lava_flowing") == nil
and string.find(def.name, "lava_source") == nil
--and def.liquid_renewable == nil and def.drowning == nil
then
-- here we are going to define more specific skips and options for sus light sources
local skip = false
if string.find(def.name, "everness:") ~= nil and def.groups.vine ~= nil then
skip = true -- like goto continue
end
if skip == false then
local mods = nil
--if def.drawtype == "plantlike" then
-- mods = 1
--end
--if string.find(def.name,"torch") then
-- mods = 3
--end
cozy_items[def.name] = {light_source= def.light_source or 0,floodable=def.floodable or false,modifiers=mods}
if not string.find(def.name, "cozylights:light") then
source_nodes[#source_nodes+1] = def.name
end
end
end
end
for node,def in pairs(minetest.registered_nodes) do
if def.sunlight_propagates == true then
local cid = minetest.get_content_id(def.name)
cozycids_sunlight_propagates[cid] = true
end
if def.light_source and def.light_source > 1
and def.drawtype ~= "airlike" and def.drawtype ~= "liquid"
and not string.find(def.name, "lava_flowing")
and not string.find(def.name, "lava_source")
--and def.liquid_viscosity == nil and def.liquid_renewable == nil and def.drowning == nil
then
local cid = minetest.get_content_id(def.name)
if cid < c_lights[1] or cid > c_lights[14]+14 then
local skip = false
if string.find(def.name, "everness:") ~= nil and def.groups.vine ~= nil then
skip = true -- like goto :continue:
end
if skip == false then
cozycids_light_sources[cid] = true
if def.on_destruct then
local base_on_destruct = def.on_destruct
minetest.override_item(node,{
on_destruct = function(pos)
base_on_destruct(pos)
print(cozylights:dump(pos))
print(def.name.." is destroyed")
cozylights:destroy_light(pos, cozy_items[def.name])
end,
})
else
minetest.override_item(node,{
on_destruct = function(pos)
print(cozylights:dump(pos))
print(def.name.." is destroyed1")
cozylights:destroy_light(pos, cozy_items[def.name])
end,
})
end
if def.on_construct ~= nil then
local base_on_construct = def.on_construct
local light = override == true and 1 or def.light_source
if def.name == "br_core:ceiling_light_1" then
light = def.light_source - 7
end
minetest.override_item(node,{
light_source = light,
use_texture_alpha= def.use_texture_alpha or "clip",
on_construct = function(pos)
base_on_construct(pos)
cozylights:draw_node_light(pos, cozy_items[def.name])
end,
})
else
local light = override == true and 1 or def.light_source
if def.name == "br_core:ceiling_light_1" then
light = def.light_source - 7
end
minetest.override_item(node,{
light_source = light,
use_texture_alpha= def.use_texture_alpha or "clip",
on_construct = function(pos)
cozylights:draw_node_light(pos, cozy_items[def.name])
end,
})
end
end
end
end
end
cozylights.source_nodes = source_nodes
cozylights.cozy_items = cozy_items
cozylights.cozycids_sunlight_propagates = cozycids_sunlight_propagates
cozylights.cozycids_light_sources = cozycids_light_sources
end)
--clean up possible stale wielded light on join, since on server shutdown we cant execute on_leave
--todo: make it more normal and less of a hack
function cozylights:on_join_cleanup(pos, radius)
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
local data = vm:get_data()
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local param2data = vm:get_param2_data()
local max_radius = radius * (radius + 1)
for z = -radius, radius do
for y = -radius, radius do
for x = -radius, radius do
--local p = vector.add(pos,{x=x,y=y,z=z})
local p = {x=x+pos.x,y=y+pos.y,z=z+pos.z}
local idx = a:indexp(p)
local squared = x * x + y * y + z * z
if data[idx] >= c_lights[1] and data[idx] <= c_lights[14] and param2data[idx] == 0 and squared <= max_radius then
data[idx] = c_air
end
end
end
end
vm:set_data(data)
vm:update_liquids()
vm:write_to_map()
end
minetest.register_on_joinplayer(function(player)
if not player then return end
local pos = vector.round(player:getpos())
pos.y = pos.y + 1
cozylights:on_join_cleanup(pos, 30)
cozylights.cozyplayers[player:get_player_name()] = {
name=player:get_player_name(),
pos_hash=pos.x + (pos.y)*100 + pos.z*10000,
wielded_item=0,
last_pos=pos,
last_wield="",
prev_wielded_lights={},
lbrush={
brightness=6,
radius=0,
strength=0.5,
mode=1,
cover_only_surfaces=0,
pos_hash=0,
}
}
end)
minetest.register_on_leaveplayer(function(player)
if not player then return end
local name = player:get_player_name()
for i=1,#cozylights.cozyplayers do
if cozylights.cozyplayers[i].name == name then
cozylights:wielded_light_cleanup(player,cozylights.cozyplayers[i],30)
table.remove(cozylights.cozyplayers,i)
end
end
end)
minetest.register_on_shutdown(function()
for i=1,#cozylights.cozyplayers do
local player = minetest.get_player_by_name(cozylights.cozyplayers[i].name)
if player ~= nil then
cozylights:wielded_light_cleanup(player,cozylights.cozyplayers[i],30)
end
end
end)
local agent_total = 0
local agent_count = 0
local recently_updated = {}
local function build_lights_after_generated(minp,maxp,sources)
local t = os.clock()
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(vector.subtract(minp, 1), vector.add(maxp, 1))
local data = vm:get_data()
local param2data = vm:get_param2_data()
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
if sources then
for i=1, #sources do
local s = sources[i]
--local hash = minetest.hash_node_position(s.pos)
local hash = s.pos.x + (s.pos.y)*100 + s.pos.z*10000
if recently_updated[hash] == nil then
recently_updated[hash] = true
cozylights:draw_node_light(s.pos, s.cozy_item, vm, a, data, param2data)
end
end
else
local cozycids_light_sources = cozylights.cozycids_light_sources
for i in a:iterp(minp,maxp) do
local cid = data[i]
if cozycids_light_sources[cid] then
local cozy_item = cozylights.cozy_items[minetest.get_name_from_content_id(cid)]
-- check if radius is not too big
local radius, _ = cozylights:calc_dims(cozy_item)
local p = a:position(i)
if a:containsp(vector.subtract(p,radius)) and a:containsp(vector.add(p,radius))
then
cozylights:draw_node_light(p,cozy_item,vm,a,data,param2data)
else
table.insert(cozylights.single_light_queue, { pos=p, cozy_item=cozy_item })
end
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
agent_total = agent_total + mf((os.clock() - t) * 1000)
agent_count = agent_count + 1
print("Av build after generated time: "..
mf(agent_total/agent_count).." ms. Sample of: "..agent_count..". Areas left: "..#cozylights.area_queue
)
end
--idk, size should be smarter than a constant
local size = 85
local function place_schem_but_real(pos, schematic, rotation, replacements, force_placement, flags)
if tonumber(schematic) ~= nil or type(schematic) == "string" then -- schematic.data
cozylights.area_queue[#cozylights.area_queue+1]={
minp=vector.subtract(pos, size),
maxp=vector.add(pos, size),
sources=nil
}
return
end
local sd = schematic.data
local update_needed = false
for i, node in pairs(sd) do
-- todo: account for replacements
if cozylights.cozy_items[node.name] then
-- rotation can be random so we cant know the position
-- todo: account for faster cases when its not random
update_needed = true
break
end
end
if update_needed == true then
local cozycids_light_sources = cozylights.cozycids_light_sources
print("UPDATE NEEDED")
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos, size)
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cozycids_light_sources[cid] then
local cozy_item = cozylights.cozy_items[minetest.get_name_from_content_id(cid)]
-- check if radius is not too big
local radius, _ = cozylights:calc_dims(cozy_item)
local p = a:position(i)
if a:containsp(vector.subtract(p,radius)) and a:containsp(vector.add(p,radius))
then
cozylights:draw_node_light(p,cozy_item,vm,a,data,param2data)
else
table.insert(cozylights.single_light_queue, { pos=p, cozy_item=cozy_item })
end
end
end
cozylights:setVoxelManipData(vm,data,param2data,true)
end
end
--a feeble attempt to cover schematics placements
local placeschemthatisnotreal = minetest.place_schematic
--todo: if its a village(several schematics) dont rebuild same lights
--todo: schematic exception table, if we have discovered for a fact somehow that a particular schematic
--cant possibly have any kind of lights then we ignore
--if not in runtime, then a constant table,
--might require additional tools to load all schematics on contentdb to figure this out
local schem_queue = {}
minetest.place_schematic = function(pos, schematic, rotation, replacements, force_placement, flags)
if not placeschemthatisnotreal(pos, schematic, rotation, replacements, force_placement, flags) then return end
-- now totally real stuff starts to happen
schem_queue[#schem_queue+1] = {
pos = pos,
schematic = schematic,
rotation = rotation,
replacements = replacements,
force_placement = force_placement,
flags = flags
}
end
local place_schematic_on_vmanip_nicely = minetest.place_schematic_on_vmanip
minetest.place_schematic_on_vmanip = function(vmanip, minp, filename, rotation, replacements, force_placement,flags)
if not place_schematic_on_vmanip_nicely(vmanip, minp, filename, rotation, replacements, force_placement,flags) then return end
schem_queue[#schem_queue+1] = {
pos = minp,
schematic = filename,
rotation = rotation,
replacements = replacements,
force_placement = force_placement,
flags = flags
}
end
local createschemthatisveryreadable = minetest.create_schematic
minetest.create_schematic = function(p1, p2, probability_list, filename, slice_prob_list)
if not createschemthatisveryreadable(p1, p2, probability_list, filename, slice_prob_list) then return end
-- unreadable stuff happens here
cozylights.area_queue[#cozylights.area_queue+1] = {
minp = p1,
maxp = p2,
sources = nil
}
end
local wield_light_enabled = cozylights.max_wield_light_radius > -1 and true or false
local wield_step = cozylights.wield_step
local brush_hold_step = cozylights.brush_hold_step
local on_gen_step = cozylights.on_gen_step
function cozylights:switch_wielded_light(enabled)
wield_light_enabled = enabled
end
function cozylights:set_wield_step(_time)
wield_step = _time
minetest.settings:set("cozylights_wield_step",_time)
cozylights.wield_step = _time
end
function cozylights:set_brush_hold_step(_time)
brush_hold_step = _time
minetest.settings:set("cozylights_brush_hold_step",_time)
cozylights.brush_hold_step = _time
end
function cozylights:set_on_gen_step(_time)
on_gen_step = _time
minetest.settings:set("cozylights_on_gen_step",_time)
cozylights.on_gen_step = _time
end
local brush_hold_dtime = 0
local wield_dtime = 0
local on_gen_dtime = 0
local total_brush_hold_time = 0
local total_brush_hold_step_count = 0
local total_wield_time = 0
local total_wield_step_count = 0
local uncozy_queue = {}
local function on_brush_hold(player,cozyplayer,pos,t)
local control_bits = player:get_player_control_bits()
if control_bits < 128 or control_bits >= 256 then return end
local lb = cozyplayer.lbrush
if lb.radius > 10 then return end
local look_dir = player:get_look_dir()
local endpos = vector.add(pos, vector.multiply(look_dir, 100))
local hit = minetest.raycast(pos, endpos, false, false):next()
if not hit then return end
local nodenameunder = minetest.get_node(hit.under).name
local nodedefunder = minetest.registered_nodes[nodenameunder]
local above = hit.above
if nodedefunder.buildable_to == true then
above.y = above.y - 1
end
local above_hash = above.x + (above.y)*100 + above.z*10000
if above_hash ~= lb.pos_hash or lb.mode == 2 or lb.mode == 4 or lb.mode == 5 then
lb.pos_hash = above_hash
cozylights:draw_brush_light(above, lb)
local exe_time = os.clock() - t
total_brush_hold_time = total_brush_hold_time + mf(exe_time * 1000)
total_brush_hold_step_count = total_brush_hold_step_count + 1
print("Av cozy lights brush step time " .. mf(total_brush_hold_time/total_brush_hold_step_count) .. " ms. Sample of: "..total_brush_hold_step_count)
--if exe_time > brush_hold_step then
-- minetest.chat_send_all("brush hold step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
-- brush_hold_step = exe_time*2
--end
end
end
minetest.register_globalstep(function(dtime)
if wield_light_enabled then
wield_dtime = wield_dtime + dtime
if wield_dtime > wield_step then
wield_dtime = 0
for _,cozyplayer in pairs(cozylights.cozyplayers) do
local t = os.clock()
local player = minetest.get_player_by_name(cozyplayer.name)
local pos = vector.round(player:getpos())
pos.y = pos.y + 1
local wield_name = player:get_wielded_item():get_name()
-- simple hash, collision will result in a rare minor barely noticeable glitch if a user teleports:
-- if in collision case right after teleport the player does not move, wielded light wont work until the player starts moving
local pos_hash = pos.x + (pos.y)*100 + pos.z*10000
if pos_hash == cozyplayer.pos_hash and cozyplayer.last_wield == wield_name then
goto next_player
end
if cozylights.cozy_items[wield_name] ~= nil then
local vel = vector.round(vector.multiply(player:get_velocity(),wield_step))
cozylights:draw_wielded_light(
pos,
cozyplayer.last_pos,
cozylights.cozy_items[wield_name],
vel,
cozyplayer
)
else
cozylights:wielded_light_cleanup(player,cozyplayer,cozyplayer.last_wield_radius or 0)
end
cozyplayer.pos_hash = pos_hash
cozyplayer.last_pos = pos
cozyplayer.last_wield = wield_name
local exe_time = (os.clock() - t)
total_wield_time = total_wield_time + mf(exe_time * 1000)
total_wield_step_count = total_wield_step_count + 1
print("Av wielded cozy light step time " .. mf(total_wield_time/total_wield_step_count) .. " ms. Sample of: "..total_wield_step_count)
if cozylights.crispy_potato and exe_time > wield_step then
cozylights:set_wielded_light_radius(cozylights.max_wield_light_radius - 1)
minetest.chat_send_all("wield light step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
wield_step = exe_time*2
end
::next_player::
end
end
end
brush_hold_dtime = brush_hold_dtime + dtime
if brush_hold_dtime > brush_hold_step then
brush_hold_dtime = 0
for _,cozyplayer in pairs(cozylights.cozyplayers) do
local t = os.clock()
local player = minetest.get_player_by_name(cozyplayer.name)
local pos = vector.round(player:getpos())
pos.y = pos.y + 1
local wield_name = player:get_wielded_item():get_name()
--todo: checking against a string is expensive, what do
if wield_name == "cozylights:light_brush" then
on_brush_hold(player,cozyplayer,pos,t)
end
end
end
on_gen_dtime = on_gen_dtime + dtime
if on_gen_dtime > on_gen_step then
on_gen_dtime = 0
if cozylights.uncozy_mode == 0 then
if #schem_queue > 0 then
local s = schem_queue[1]
place_schem_but_real(s.pos, s.schematic, s.rotation, s.replacements, s.force_placement, s.flags)
table.remove(schem_queue, 1)
end
if #cozylights.area_queue ~= 0 then
local ar = cozylights.area_queue[1]
table.remove(cozylights.area_queue, 1)
print("build_lights_after_generated: "..cozylights:dump(ar.minp))
build_lights_after_generated(ar.minp,ar.maxp,ar.sources)
else
cozylights:rebuild_light()
if #recently_updated > 0 then
recently_updated = {}
end
end
else
for _,cozyplayer in pairs(cozylights.cozyplayers) do
local player = minetest.get_player_by_name(cozyplayer.name)
local pos = vector.round(player:getpos())
pos.y = pos.y + 1
-- simple hash, collision will result in a rare minor barely noticeable glitch if a user teleports:
-- if in collision case right after teleport the player does not move, wielded light wont work until the player starts moving
local pos_hash = pos.x + (pos.y)*100 + pos.z*10000
if pos_hash == cozyplayer.pos_hash then
goto next_player
end
cozyplayer.pos_hash = pos_hash
cozyplayer.last_pos = pos
uncozy_queue[#uncozy_queue+1] = pos
::next_player::
end
if #uncozy_queue > 0 then
local exe_time = cozylights:clear(uncozy_queue[1], cozylights.uncozy_mode)
table.remove(uncozy_queue, 1)
if cozylights.crispy_potato and exe_time > on_gen_step then
minetest.chat_send_all("on_generated step was adjusted to "..(exe_time*2).." secs to help crispy potato.")
on_gen_step = exe_time*2
end
end
end
end
end)
local gent_total = 0
local gent_count = 0
minetest.register_on_generated(function(minp, maxp)
local pos = vector.add(minp, vector.floor(vector.divide(vector.subtract(maxp,minp), 2)))
local light_sources = minetest.find_nodes_in_area(minp,maxp,cozylights.source_nodes)
if #light_sources == 0 then return end
if #light_sources > 1000 then
print("Error: too many light sources around "..cozylights:dump(pos).." Report this to Cozy Lights dev")
return
end
--local minp_exp,maxp_exp,_,data,_,a = cozylights:getVoxelManipData(pos, size)
--local t = os.clock()
local sources = {}
local a = VoxelArea:new{
MinEdge = minp,
MaxEdge = maxp
}
local minp_exp, maxp_exp = minp, maxp
for _, p in pairs(light_sources) do
local name = minetest.get_node(p).name--get_name_from_content_id(cid)
local cozy_item = cozylights.cozy_items[name]
local radius, _ = cozylights:calc_dims(cozy_item)
local min_rad = vector.subtract(p,radius)
local max_rad = vector.add(p,radius)
if a:containsp(min_rad) and a:containsp(max_rad) then
sources[#sources+1] = {
pos=p,
cozy_item=cozy_item
}
else
minp_exp = {
x = minp_exp.x > min_rad.x and min_rad.x or minp_exp.x,
y = minp_exp.y > min_rad.y and min_rad.y or minp_exp.y,
z = minp_exp.z > min_rad.z and min_rad.z or minp_exp.z,
}
maxp_exp = {
x = maxp_exp.x < max_rad.x and max_rad.x or maxp_exp.x,
y = maxp_exp.y < max_rad.y and max_rad.y or maxp_exp.y,
z = maxp_exp.z < max_rad.z and max_rad.z or maxp_exp.z,
}
a = VoxelArea:new{
MinEdge = minp_exp,
MaxEdge = maxp_exp
}
sources[#sources+1] = {
pos=p,
cozy_item=cozy_item
}
--print("adding "..name.." to single_light_queue")
--table.insert(cozylights.single_light_queue, {
-- pos=p,
-- cozy_item=cozy_item
--})
end
end
--gent_total = gent_total + mf((os.clock() - t) * 1000)
--gent_count = gent_count + 1
--print("Av mapchunk generation time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
if #sources > 0 then
print("on_generated adding area:"..cozylights:dump({minp=minp_exp,maxp=maxp_exp, volume=a:getVolume()}))
cozylights.area_queue[#cozylights.area_queue+1]={
minp=minp_exp,
maxp=maxp_exp,
sources=sources
}
end
end)

View file

@ -0,0 +1,341 @@
local mf = math.floor
local function on_secondary_use(user)
local lb = cozylights.cozyplayers[user:get_player_name()].lbrush
local settings_formspec = {
"formspec_version[4]",
--"size[6,6.4]",
"size[5.2,5]",
"label[1.45,0.5;Light Brush Settings]",
"label[0.95,1.35;Radius]",
"field[3.6,1.1;0.7,0.5;radius;;"..lb.radius.."]",
"tooltip[0.95,1.1;3.4,0.5;If radius is 0 then only one node will be affected by the brush.\n"..
"If not zero then it's a sphere of affected nodes with specified radius.\n"..
"As of now max radius is only 120.\n"..
"With radiuses over 30 mouse hold as of now does not work, only point and click]",
"label[0.95,2.05;Brightness]",
"field[3.6,1.8;0.7,0.5;brightness;;"..lb.brightness.."]",
"tooltip[0.95,1.8;3.4,0.5;Brightness - for most brush modes values are from 1 to 14, corresponding to engine light levels.\n"..
"If brush mode is 'darken' or 'override' then 0 will replace lowest light levels with air.]",
"label[0.95,2.75;Strength]",
"field[3.6,2.5;0.7,0.5;strength;;"..lb.strength.."]",
"tooltip[0.95,2.5;3.4,0.5;Strength, can be from 0 to 1, decimal values of any precision are valid.\n"..
"Determines how bright(relative to brightness setting) light nodes in affected area will be.]",
"label[0.95,3.45;Brush Mode]",
"dropdown[2.8,3.2;1.5,0.5;mode;default,erase,override,lighten,darken,blend;"..lb.mode.."]",
"tooltip[0.95,3.2;3.4,0.5;\nDefault - replace only dimmer light nodes or air with brush.\n\n"..
"Erase - inverse of default, replaces only lighter nodes with darker nodes or air if brightness is 0.\n\n"..
"Override - set light nodes as brush settings dictate regardless of difference in brigthness.\n\n"..
"Lighten - milder than default mode.\n\n"..
"Darken - milder erase, does not darken below light 1(does not replace with air).\n\n"..
"Blend - blend affected nodes' brigthness with brush brigthness.\n"..
"Even though behaves correctly, as of now looks weird and unintuitive if radius is not 0.]",
--"checkbox[1.7,4.6;cover_only_surfaces;cover only surfaces;"..(lb.cover_only_surfaces == 1 and "true" or "false").."]",
--"tooltip[1.7,4.4;2.6,0.4;if enabled brush will not fill up the air with light above the ground;"..bgcolor..";#FFFFFF]",
--"button_exit[1,5.1;4,0.8;confirm;Confirm]",
"button_exit[1.1,4;3,0.8;confirm;Confirm]",
}
minetest.show_formspec(user:get_player_name(), "cozylights:brush_settings",table.concat(settings_formspec, ""))
end
minetest.register_tool("cozylights:light_brush", {
description = "Light Brush",
inventory_image = "light_brush.png",
wield_image = "light_brush.png^[transformR90",
tool_capabilities = {
full_punch_interval = 0.3,
max_drop_level = 1,
},
range = 100.0,
on_use = function(itemstack, user, pointed_thing)
if pointed_thing.under then
local nodenameunder = minetest.get_node(pointed_thing.under).name
local nodedefunder = minetest.registered_nodes[nodenameunder]
local lb = cozylights.cozyplayers[user:get_player_name()].lbrush
local above = pointed_thing.above
if nodenameunder ~= "air" and nodedefunder.buildable_to == true then
above.y = above.y - 1
end
local above_hash = above.x + (above.y)*100 + above.z*10000
lb.pos_hash = above_hash
cozylights:draw_brush_light(pointed_thing.above, lb)
end
end,
on_place = function(_, placer)
on_secondary_use(placer)
end,
on_secondary_use = function(_, user)
on_secondary_use(user)
end,
sound = {breaks = "default_tool_breaks"}
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= ("cozylights:brush_settings") then return end
if player == nil then return end
local lb = cozylights.cozyplayers[player:get_player_name()].lbrush
if fields.brightness then
local brightness = tonumber(fields.brightness) > 14 and 14 or tonumber(fields.brightness)
lb.brightness = brightness < 0 and 0 or mf(brightness or 0)
end
if fields.radius then
local radius = tonumber(fields.radius) > 200 and 200 or tonumber(fields.radius)
lb.radius = radius < 0 and 0 or mf(radius or 0)
end
if fields.strength then
local strength = tonumber(fields.strength) > 1 and 1 or tonumber(fields.strength)
lb.strength = strength < 0 and 0 or strength
end
if fields.mode then
local mode = fields.mode
local idx = 6
if mode == "default" then
idx = 1
elseif mode == "erase" then
idx = 2
elseif mode == "override" then
idx = 3
elseif mode == "lighten" then
idx = 4
elseif mode == "darken" then
idx = 5
end
lb.mode = idx
end
if fields.cover_only_surfaces then
lb.cover_only_surfaces = fields.cover_only_surfaces == "true" and 1 or 0
end
end)
local function calc_dims_for_brush(brightness, radius, strength, even)
local dim_levels = {}
--- this gradient attempts to get more colors, but that looks like a super weird monochrome rainbow and immersion braking
--strength = (strength+0.05)*2
--
--local current_brightness = brightness
--local step = math.sqrt(radius/brightness)
--local initial_step = step
--for i = 1, radius do
-- dim_levels[i] = current_brightness
-- if i>step then
-- step = step*strength + math.sqrt(i)
-- current_brightness = current_brightness - 1
-- end
--end
--- this gradient drops brightness fast but spreads dimmer lights over farther
if strength == 1 then
even = true
end
strength = strength*5
dim_levels[1] = brightness
if even ~= true then
for i = 2, radius do
local dim = math.sqrt(math.sqrt(i)) * (6-strength)
local light_i = mf(brightness - dim)
if light_i > 0 then
if light_i < 15 then
dim_levels[i] = light_i
else
dim_levels[i] = 14
end
else
dim_levels[i] = 1
end
end
else
for i = 2, radius do
dim_levels[i] = brightness
end
end
return dim_levels
end
local c_air = minetest.get_content_id("air")
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local gent_total = 0
local gent_count = 0
local function draw_one_node(pos,lb)
local node = minetest.get_node(pos)
local brightness = lb.brightness
local new_node_name = "cozylights:light"..brightness
if brightness == 0 then
new_node_name = "air"
end
if node.name == "air" and new_node_name ~= node.name then
minetest.set_node(
pos,
{
name=new_node_name,
param2=brightness
}
)
return
end
if string.find(node.name,"cozylights:") then
if lb.mode == 1 and brightness <= node.param2 then return end
if lb.mode == 2 and brightness >= node.param2 then return end
if lb.mode == 4 then
if brightness <= node.param2 then return end
brightness = mf((brightness+node.param2)/2+0.5)
if brightness < 1 then return end
new_node_name = "cozylights:light"..brightness
elseif lb.mode == 5 then
if brightness >= node.param2 then return end
brightness = mf((brightness+node.param2)/2)
new_node_name = "cozylights:light"..brightness
if brightness < 1 then
brightness = 0
new_node_name = "air"
end
elseif lb.mode == 6 then
brightness = mf((brightness+node.param2)/2+0.5)
new_node_name = "cozylights:light"..brightness
if brightness < 0 then
brightness = 0
new_node_name = "air"
end
end
minetest.set_node(
pos,
{
name=new_node_name,
param2=brightness
}
)
end
end
--this function pulls numbers out of its ass instead of seriously computing everything, so its faster
--some nodes are being missed for big spheres
function cozylights:draw_brush_light(pos, lb)
local t = os.clock()
local radius = lb.radius
if radius == 0 then
draw_one_node(pos,lb)
return
end
local mode = lb.mode
local brightness = lb.brightness
local dim_levels = calc_dims_for_brush(brightness,radius,lb.strength, mode==2 and true or false)
print("dim_levels:"..cozylights:dump(dim_levels))
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
local data = vm:get_data()
local param2data = vm:get_param2_data()
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local sphere_surface = cozylights:get_sphere_surface(radius)
local ylvl = 1
local cid = data[a:index(pos.x,pos.y-1,pos.z)]
local cida = data[a:index(pos.x,pos.y+1,pos.z)]
if cid and cida then
if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
then
ylvl = -1
end
else
return
end
pos.y = pos.y + ylvl
if mode == 1 then
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
elseif mode == 2 then
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_erase_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_erase(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
elseif mode == 3 then
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_override_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_override(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
elseif mode == 4 then
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_lighten_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_lighten(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
elseif mode == 5 then
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_darken_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_darken(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
else
if cozylights.always_fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_blend_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_blend(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
end
vm:set_data(data)
vm:set_param2_data(param2data)
vm:update_liquids()
vm:write_to_map()
gent_total = gent_total + mf((os.clock() - t) * 1000)
gent_count = gent_count + 1
print("Av draw time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
end

8
mods/cozylights/mod.conf Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,192 @@
local c_air = minetest.get_content_id("air")
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local gent_total = 0
local gent_count = 0
local remt_total = 0
local remt_count = 0
local mf = math.floor
local dirfloor = 0.5
--- raycast but normal
local function darknesscast(pos, dir, radius,data,param2data, a)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local c_light1 = c_lights[1]
local c_light14 = c_lights[14]
for i = 1, radius do
local x = mf(dx*i+dirfloor) + px
local y = mf(dy*i+dirfloor) + py
local z = mf(dz*i+dirfloor) + pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cid and (cid == c_air or (cid >= c_light1 and cid <= c_light14+14)) then
data[idx] = c_air
param2data[idx] = 0
else
break
end
end
end
function cozylights:draw_node_light(pos,cozy_item,vm,a,data,param2data,fix_edges)
local t = os.clock()
local update_needed = 0
local radius, dim_levels = cozylights:calc_dims(cozy_item)
--print("cozy_item:"..cozylights:dump(cozy_item))
--print("dim_levels: "..cozylights:dump(dim_levels))
--print("spreading light over a sphere with radius of "..radius)
if vm == nil then
_,_,vm,data,param2data,a = cozylights:getVoxelManipData(pos,radius)
update_needed = 1
end
local sphere_surface = cozylights:get_sphere_surface(radius)
local ylvl = 1
local cid = data[a:index(pos.x,pos.y-1,pos.z)]
local cida = data[a:index(pos.x,pos.y+1,pos.z)]
if cid and cida then
if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
then
ylvl = -1
end
else
return
end
pos.y = pos.y + ylvl
fix_edges = fix_edges == nil and cozylights.always_fix_edges or fix_edges
if fix_edges == true then
local visited_pos = {}
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights:lightcast_fix_edges(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels,visited_pos)
end
else
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
cozylights.dir = vector.direction(pos, end_pos)
cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
if update_needed == 1 then
cozylights:setVoxelManipData(vm,data,param2data,true)
end
gent_total = gent_total + mf((os.clock() - t) * 1000)
gent_count = gent_count + 1
print("Av illum time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
end
-- handle_async?
function cozylights:rebuild_light()
local single_light_queue = cozylights.single_light_queue
if #single_light_queue == 0 then
return
end
print("#single_light_queue is: "..#single_light_queue)
cozylights:draw_node_light(single_light_queue[1].pos, single_light_queue[1].cozy_item)
table.remove(single_light_queue, 1)
end
function cozylights:destroy_light(pos, cozy_item)
local t = os.clock()
local radius = cozylights:calc_dims(cozy_item)
local _,_,vm,data,param2data,a = cozylights:getVoxelManipData(pos, radius)
local sphere_surface = cozylights:get_sphere_surface(radius)
local ylvl = 1
local cid = data[a:index(pos.x,pos.y-1,pos.z)]
local cida = data[a:index(pos.x,pos.y+1,pos.z)]
if cid and cida then
if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
then
ylvl = -1
end
else
return
end
pos.y = pos.y + ylvl
for i,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
darknesscast(pos, vector.direction(pos, end_pos),radius,data,param2data, a)
end
cozylights:setVoxelManipData(vm,data,param2data)
local rebuild_range = 78
local rebuild_minp = vector.subtract(pos, rebuild_range)
local rebuild_maxp = vector.add(pos, rebuild_range)
local posrebuilds = minetest.find_nodes_in_area(
rebuild_minp,
rebuild_maxp,
cozylights.source_nodes
)
local pos_hash = pos.x + (pos.y-ylvl)*100 + pos.z*10000
local sources = {}
if #posrebuilds > 0 then
local single_light_queue = cozylights.single_light_queue
for i=1,#posrebuilds do
local posrebuild = posrebuilds[i]
local posrebuild_hash = posrebuild.x + posrebuild.y*100 + posrebuild.z*10000
if posrebuild_hash ~= pos_hash then
local node = minetest.get_node(posrebuild)
local rebuild_radius, _ = cozylights:calc_dims(cozylights.cozy_items[node.name])
local max_distance = rebuild_radius + radius
if max_distance > vector.distance(pos,posrebuild) then
if vector.in_area(vector.subtract(posrebuild,rebuild_radius), rebuild_minp, rebuild_maxp)
and vector.in_area(vector.add(posrebuild,rebuild_radius), rebuild_minp, rebuild_maxp)
then
sources[#sources+1] = {
pos=posrebuild,
cozy_item=cozylights.cozy_items[node.name]
}
else
cozylights.single_light_queue[#single_light_queue+1] = {
pos=posrebuilds[i],
cozy_item=cozylights.cozy_items[node.name]
}
end
end
end
end
end
if #sources > 0 then
cozylights.area_queue[#cozylights.area_queue+1]={
minp=rebuild_minp,
maxp=rebuild_maxp,
sources=sources
}
end
remt_total = remt_total + mf((os.clock() - t) * 1000)
remt_count = remt_count + 1
print("Av light removal time " .. mf(remt_total/remt_count) .. " ms. Sample of: "..remt_count)
end
--[[
function cozylights:rebuild_light(pos, cozy_item,vm,a,data,param2data)
local radius, dim_levels = cozylights:calc_dims(cozy_item)
print("rebuilding light for position "..cozylights:dump(pos))
local sphere_surface = cozylights:get_sphere_surface(radius)
local ylvl = 1
local cid = data[a:index(pos.x,pos.y-1,pos.z)]
local cida = data[a:index(pos.x,pos.y+1,pos.z)]
if cid and cida then
if (cid == c_air or (cid >= c_lights[1] and cid <= c_lights[14]))
and cida ~= c_air and (cida < c_lights[1] or cida > c_lights[14])
then
ylvl = -1
end
else
return
end
pos.y = pos.y + ylvl
for _,pos2 in ipairs(sphere_surface) do
local end_pos = {x=pos.x+pos2.x,y=pos.y+pos2.y,z=pos.z+pos2.z}
if a:containsp(end_pos) then
cozylights:lightcast(pos, vector.direction(pos, end_pos),radius,data,param2data,a,dim_levels)
end
end
end]]

38
mods/cozylights/nodes.lua Normal file
View file

@ -0,0 +1,38 @@
-- All possible light levels
for i=1, minetest.LIGHT_MAX do
minetest.register_node("cozylights:light"..i, {
description = "Light Source "..i,
paramtype = "light",
light_source = i,
--tiles ={"invisible.png"},
drawtype = "airlike",
walkable = false,
sunlight_propagates = true,
is_ground_content = false,
buildable_to = true,
pointable = false,
groups = {dig_immediate=3,not_in_creative_inventory=1},
floodable = true,
use_texture_alpha="clip",
})
end
-- two separate loops to keep content ids in order
for i=1, minetest.LIGHT_MAX do
minetest.register_node("cozylights:light_debug"..i, {
description = "Light Source "..i,
paramtype = "light",
light_source = i,
tiles ={"default_glass.png"},
drawtype = "glasslike",
walkable = false,
sunlight_propagates = true,
is_ground_content = false,
buildable_to = false,
pointable = true,
groups = {dig_immediate=3,not_in_creative_inventory=1},
floodable = true,
use_texture_alpha="clip",
})
end

View file

@ -0,0 +1,31 @@
[**Global Node Sources]
# if none given, this default value will be used
cozylights_default_size (Default chat command radius) int 40 5 120
# max brightness of surrounding light. does not affect a light_source node base value. attention: if light is too bright, the scene can lose nuance
cozylights_brightness_factor (Global ambient light source brightness modifier) float 3.0 -10.0 10.0
# affects max radius of the light but only when its bright enough, if its very dim the setting will do nothing
cozylights_reach_factor (Global ambient light source reach factor) float 4.0 0.0 10.0
# how fast light dims further away from the source, higher means farther dim lights will persist for longer
cozylights_dim_factor (Global ambient light source dim factor) float 9.0 0.0 10.0
# -1 means wielded light is disabled
# 0 means only one node is affected, so it basically acts like typical wielded light in Minetest
# if it's more than 0 then it's a sphere in which light will spread
cozylights_wielded_light_radius (Cozy wielded light radius) int 19 -1 30
# sets all light sources to 1 so that the engine will not render anticlimactic squares for torches
# and such. if a player removes cozylights from a world while this is set to true, fixmap mod for existing lights will be required, therefore default is set
# to false, so you will need to enable it yourself after you decide that you like cozylights more.
cozylights_override_engine_lights (Override engine light sources) bool false
# makes all edges stop lights properly, cozylights algo is much faster without it enabled, so if for example
# you need to first place a lot of lights all over the place, it would be easier to first place those lights and then run
# /fixedges manually
cozylights_always_fix_edges (Override engine light sources) bool false
# if higher, then it will update slower and stress potato CPU less
cozylights_step_time (Cozy Lights Global Step time) float 0.1 0.01 1.0

649
mods/cozylights/shared.lua Normal file
View file

@ -0,0 +1,649 @@
local sphere_surfaces = {[19]=nil}
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local c_light14 = c_lights[14]
local c_light_debug1 = c_light14 + 1
local c_light_debug14 = c_light_debug1 + 13
local c_air = minetest.get_content_id("air")
local mf = math.floor
function cozylights:clear(pos,size)
local t = os.clock()
local minp,maxp,vm,data,param2data,a = cozylights:getVoxelManipData(pos,size)
local count = 0
for i in a:iterp(minp, maxp) do
local cid = data[i]
if cid >= c_light1 and cid <= c_light_debug14 then
data[i] = c_air
param2data[i] = 0
count = count + 1
end
end
minetest.chat_send_all("cleared "..count.." cozy light nodes in area around pos: "..cozylights:dump(pos).." of radius: "..size)
if count> 0 then
cozylights:setVoxelManipData(vm,data,param2data,true)
end
return (os.clock() - t)
end
function cozylights:getVoxelManipData(pos, size)
local minp = vector.subtract(pos, size)
local maxp = vector.add(pos, size)
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(vector.subtract(minp, 1), vector.add(maxp, 1))
local data = vm:get_data()
local param2data = vm:get_param2_data()
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
return minp,maxp,vm,data,param2data,a
end
function cozylights:setVoxelManipData(vm,data,param2data,update_liquids)
vm:set_data(data)
if param2data ~= nil then
vm:set_param2_data(param2data)
end
if update_liquids == true then
vm:update_liquids()
end
vm:write_to_map()
end
--todo: 6 directions of static slices or dynamic slices if its faster somehow(it wasnt so far)
function cozylights:slice_cake(surface,radius)
local sliced = {}
for k,v in pairs(surface) do
-- full sphere except for a cone from center to max -y of 45 degrees or like pi/2 radians or something
if v.y > -radius*0.7071 then
table.insert(sliced,v)
end
end
return sliced
end
-- radius*radius = x*x + y*y + z*z
function cozylights:get_sphere_surface(radius,sliced)
if sphere_surfaces[radius] == nil then
local sphere_surface = {}
local rad_pow2_min, rad_pow2_max = radius * (radius - 1), radius * (radius + 1)
for z = -radius, radius do
for y = -radius, radius do
for x = -radius, radius do
local pow2 = x * x + y * y + z * z
if pow2 >= rad_pow2_min and pow2 <= rad_pow2_max then
-- todo: could arrange these in a more preferable for optimization order
sphere_surface[#sphere_surface+1] = {x=x,y=y,z=z}
end
end
end
end
local t = {
full = sphere_surface
}
if radius < 30 then
t.minusyslice = cozylights:slice_cake(sphere_surface,radius) --typical wielded light
sphere_surfaces[radius] = t
if sliced == true then
return t.minusyslice
end
end
return sphere_surface
else
if sliced == true and sphere_surfaces[radius].minusyslice ~= nil then
return sphere_surfaces[radius].minusyslice
end
return sphere_surfaces[radius].full
end
end
function cozylights:calc_dims(cozy_item)
local brightness_mod = 0
local reach_mod = 0
local dim_mod = 0
if cozy_item.modifiers ~= nil then
brightness_mod = cozylights.coziest_table[cozy_item.modifiers].brightness
reach_mod = cozylights.coziest_table[cozy_item.modifiers].reach_factor
dim_mod = cozylights.coziest_table[cozy_item.modifiers].dim_factor
end
local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod)
local r = mf(max_light*max_light/10*(cozylights.reach_factor+reach_mod))
--print("initial r: "..r)
local r_max = 0
local dim_levels = {}
local dim_factor = cozylights.dim_factor + dim_mod
for i = r , 1, -1 do
local dim = math.sqrt(math.sqrt(i)) * dim_factor
local light_i = max_light + 1 - mf(dim)
if light_i < 1 then
--light_i = 1
r_max = i
else
if light_i > 14 then
light_i = 14
end
dim_levels[i] = light_i
end
end
-- we cut the r only if max_r found is lower than r, so that we keep the ability to have huge radiuses
if r_max > 0 and r_max < r then
return r_max-1,dim_levels
end
return r,dim_levels
end
local cozycids_sunlight_propagates = {}
-- ensure cozy position in memory
-- default amount of lights sources: 194
-- in default game with moreblocks mod: 5134
--cozylights:prealloc(cozycids_sunlight_propagates, 194, true)
--cozycids_sunlight_propagates = {}
minetest.after(1, function()
cozycids_sunlight_propagates = cozylights.cozycids_sunlight_propagates
cozylights:finalize(cozycids_sunlight_propagates)
print(#cozycids_sunlight_propagates)
cozylights.cozycids_sunlight_propagates = {}
local version_welcome = minetest.settings:get("version_welcome")
if version_welcome ~= cozylights.version then
minetest.settings:set("version_welcome",cozylights.version)
minetest.chat_send_all(">.< Running Cozy Lights "..cozylights.version.." alpha. Some features are still missing or might not work properly and might be fixed tomorrow or next week."..
"\n>.< To learn more about what it can do check ContentDB page: https://content.minetest.net/packages/SingleDigitIQ/cozylights/"..
"\n>.< If you experience problems, appreciate if you report them on ContentDB, Minetest forum, Github or Discord."..
"\n>.< If you need more of original ideas and blazingly fast code in open source - leave a positive review on ContentDB or/and add to favorites."..
"\n>.< To open mod settings type in chat /cozysettings or /zs, hopefully tooltips are useful."..
"\n>.< This message displays only once per new downloaded update for Cozy Lights mod."..
"\n>.< Have fun :>"
)
end
end)
-- adjusting dirfloor might help with some nodes missing. probably the only acceptable way to to eliminate node
-- misses and not sacrifice performance too much or at all
local dirfloor = 0.5
-- raycast but normal
-- todo: if radius higher than i think 15, we need to somehow grab more nodes, without it it's not entirely accurate
-- i hope a cheaply computed offset based on dir will do
-- not to forget: what i mean by that is that + 0.5 in mf has to become a variable
-- while we have the opportunity to cut the amount of same node reruns in this loop,
-- we avoid that because luajit optimization breaks with one more branch and hashtable look up
-- at least on my machine, and so it becomes slower to run and at the same time grabs more memory
-- todo: actually check for the forth time the above is real
function cozylights:lightcast(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
local dim = (dim_levels[i] - light_nerf) >= 1 and (dim_levels[i] - light_nerf) or 1
local light = c_lights[dim]
if light > cid or param2data[idx] == 0 then
data[idx] = light
param2data[idx] = dim
end
else
light_nerf = light_nerf + 1
end
else
break
end
end
end
function cozylights:lightcast_erase(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid >= c_light1 and cid <= c_light14 then
local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
local light = dim > 0 and c_lights[dim] or c_air
if light < cid then
data[idx] = light
param2data[idx] = dim
end
elseif cid ~= c_air then
light_nerf = light_nerf + 1
end
else
break
end
end
end
function cozylights:lightcast_override(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
data[idx] = c_lights[dim]
param2data[idx] = dim
else
light_nerf = light_nerf + 1
end
else
break
end
end
end
function cozylights:lightcast_lighten(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] > cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2+0.5)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
else
light_nerf = light_nerf + 1
end
else
break
end
end
end
function cozylights:lightcast_darken(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid >= c_light1 and cid <= c_light14 then
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] < cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
elseif cid ~= c_air then
light_nerf = light_nerf + 1
end
else
break
end
end
end
function cozylights:lightcast_blend(pos, dir, radius,data,param2data,a,dim_levels)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
for i = 1, radius do
local x,y,z = mf(dx*i+dirfloor)+px, mf(dy*i+dirfloor)+py, mf(dz*i+dirfloor)+pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local original_light = cid - c_light1 --param2data[idx]
dim = mf((dim + original_light)/2+0.5)
if dim < 1 then break end
data[idx] = c_lights[dim]
param2data[idx] = dim
else
light_nerf = light_nerf + 1
end
else
break
end
end
end
-- removes some lights that light up the opposite side of an obstacle
-- it is weird and inaccurate as of now, i can make it accurate the expensive way,
-- still looking for a cheap way
function cozylights:lightcast_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius,2 do
local x,y,z = next_x, next_y, next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local light = c_lights[dim]
if light > cid or param2data[idx] == 0 then
data[idx] = light
param2data[idx] = dim
end
end
else
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local light = c_lights[dim]
if light > cid or param2data[idx] == 0 then
data[idx] = light
param2data[idx] = dim
end
end
else
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
--local next_idx = a:index(next_x,next_y,next_z)
--for n = 1, 6 do
-- if cozycids_sunlight_propagates[data[next_idx+dirs[n]]] == nil then
-- braking_brak = true
-- break
-- end
--end
--next_x,next_y,next_z = mf(dx*(i+2)+dirfloor)+px, mf(dy*(i+2)+dirfloor)+py, mf(dz*(i+2)+dirfloor)+pz
--local next_adj_indxs = {
-- a:index(next_x,y,z),
-- a:index(x,y,next_z),
-- a:index(x,next_y,z),
-- a:index(next_x,next_y,z),
-- a:index(x,next_y,next_z),
--}
--for _, j in pairs(next_adj_indxs) do
-- if cozycids_sunlight_propagates[data[j]] ~= true then
-- braking_brak = true
-- break
-- end
--end
end
end
function cozylights:lightcast_erase_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius do
local x,y,z = next_x, next_y, next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid >= c_light1 and cid <= c_light14 then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
local light = dim > 0 and c_lights[dim] or c_air
if light < cid then
data[idx] = light
param2data[idx] = dim
end
end
else
local dim = (dim_levels[i] - light_nerf) >= 0 and (dim_levels[i] - light_nerf) or 0
local light = dim > 0 and c_lights[dim] or c_air
if light < cid then
data[idx] = light
param2data[idx] = dim
end
end
elseif cid ~= c_air then
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
end
end
function cozylights:lightcast_override_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius do
local x = next_x
local y = next_y
local z = next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil -- they are probably still allocated though
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
data[idx] = c_lights[dim]
param2data[idx] = dim
end
else
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
data[idx] = c_lights[dim]
param2data[idx] = dim
end
else
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
end
end
function cozylights:lightcast_lighten_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius do
local x = next_x
local y = next_y
local z = next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil -- they are probably still allocated though
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] > cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2+0.5)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
end
else
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] > cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2+0.5)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
end
else
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
end
end
function cozylights:lightcast_darken_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius do
local x = next_x
local y = next_y
local z = next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil -- they are probably still allocated though
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] < cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
end
else
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
if c_lights[dim] < cid then
local original_light = cid - c_light1
dim = mf((dim + original_light)/2)
data[idx] = c_lights[dim]
param2data[idx] = dim
end
end
else
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
end
end
function cozylights:lightcast_blend_fix_edges(pos, dir, radius,data,param2data,a,dim_levels,visited_pos)
local dirs = { -1*a.ystride, 1*a.ystride,-1,1,-1*a.zstride,1*a.zstride}
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local light_nerf = 0
local halfrad, braking_brak = radius/2, false
local next_x, next_y, next_z = mf(dx+dirfloor) + px, mf(dy+dirfloor) + py, mf(dz+dirfloor) + pz
for i = 1, radius do
local x = next_x
local y = next_y
local z = next_z
local idx = a:index(x,y,z)
for n = 1, 6 do
if cozycids_sunlight_propagates[data[idx+dirs[n]]] == nil then
braking_brak = true
break
end
end
if braking_brak == true then break end
x,y,z = nil,nil,nil -- they are probably still allocated though
local cid = data[idx]
if cozycids_sunlight_propagates[cid] == true then
-- appears that hash lookup in a loop is as bad as math
if cid == c_air or (cid >= c_light1 and cid <= c_light14) then
if i < halfrad then
if not visited_pos[idx] then
visited_pos[idx] = true
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local original_light = cid - c_light1
dim = mf((dim + original_light)/2+0.5)
if dim < 1 then break end
data[idx] = c_lights[dim]
param2data[idx] = dim
end
else
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local original_light = cid - c_light1
dim = mf((dim + original_light)/2+0.5)
if dim < 1 then break end
data[idx] = c_lights[dim]
param2data[idx] = dim
end
else
light_nerf = light_nerf + 1
end
else
break
end
next_x,next_y,next_z = mf(dx*(i+1)+dirfloor)+px, mf(dy*(i+1)+dirfloor)+py, mf(dz*(i+1)+dirfloor)+pz
end
end

View file

@ -0,0 +1,95 @@
dofile("../helpers.lua")
dofile("../../../builtin/common/vector.lua")
-- this exists to basically find sweet spot for dirfloor in fastest way i could come up with to spread light.
-- dirfloor should change somehow cheaply according to radius maybe or
-- according to ray angles, or, dirfloor should be split in x,y,z axis equivalents
-- and those should be adjusted.
-- some node misses start to appear from radius 6
local dirfloor = 0.51
local mf = math.floor
-- radius*radius = x*x + y*y + z*z
local function get_full_sphere(radius)
local sphere = {}
local count, offset, rad_pow2, stride_y = 0, 1+radius, radius * (radius + 1), radius+2
local stride_z = stride_y * stride_y
for z = -radius, radius do
for y = -radius, radius do
for x = -radius, radius do
local pow2 = x * x + y * y + z * z
if pow2 <= rad_pow2 then
local i = (z + offset) * stride_z + (y + offset) * stride_y + x + offset + 1
if sphere[i] ~= true then
sphere[i] = true
count = count + 1
end
end
end
end
end
return sphere, count
end
local function get_sphere_surface(radius)
local sphere_surface = {}
local rad_pow2_min, rad_pow2_max = radius * (radius - 1), radius * (radius + 1)
for z = -radius, radius do
for y = -radius, radius do
for x = -radius, radius do
local squared = x * x + y * y + z * z
if squared >= rad_pow2_min and squared <= rad_pow2_max then
sphere_surface[#sphere_surface+1] = {x=x,y=y,z=z}
end
end
end
end
return sphere_surface
end
local function raycast(dir, radius)
local ray = {}
local stride_z, stride_y = (radius+2)*radius+2, radius+2
local dx, dy, dz = dir.x, dir.y, dir.z
for i = 1, radius do
local x = mf(dx*i+dirfloor)
local y = mf(dy*i+dirfloor)
local z = mf(dz*i+dirfloor)
local idx = (z+1+radius)*stride_z+1+(y+1+radius)*stride_y+(x+1+radius)
if not ray[idx] then
ray[idx] = true
end
end
return ray
end
local function reconstruct_sphere(radius)
local pos = {x=0,y=0,z=0}
local sphere, sphere_len = get_full_sphere(radius)
local sphere_surface = get_sphere_surface(radius)
local reconstructed_sphere = {}
local reconstructed_sphere_len = 0
for _,pos2 in ipairs(sphere_surface) do
local ray = raycast(vector.direction(pos, pos2),radius)
for i,_ in pairs(ray) do
if not reconstructed_sphere[i] then
reconstructed_sphere[i] = true
reconstructed_sphere_len = reconstructed_sphere_len + 1
end
end
end
print("#sphere: "..sphere_len)
print("#reconstructed_sphere: "..reconstructed_sphere_len)
--print(cozylights:dump(sphere))
--print(cozylights:dump(reconstructed_sphere))
end
for i=1,1 do
dirfloor = dirfloor - 0.01
print("running with dirfloor: "..dirfloor)
reconstruct_sphere(1)
end

View file

@ -0,0 +1,3 @@
default_glass.png is by Krock (CC0 1.0)
my debug textures are WTFPL if anything

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View file

@ -0,0 +1,30 @@
#!/bin/bash
# This script is naive: does not try to find mod.conf recursevily, so it sometimes can miss something,
# and also it can add something unrelated, that is just some logic for light_sources and not a nodedef,
# and probably some ancient mod' file extension won't be picked up if it's a thing, aside from gorillions of
# other problems. Does enough so far
match="light_source"
games_directory="../../../games"
mods_directory="../../"
game_files=$(grep -l -R --include="*.lua" $match $games_directory)
mod_files=$(grep -l -R --include="*.lua" $match $mods_directory)
files=("${game_files[@]}""${mod_files[@]}")
mod_names=""
i=0
for file in $files
do
directory=$(dirname $file)
mod_conf=$(find $directory -name "*.conf")
if [[ $mod_conf != "" ]]; then
dir_name_comma="$(basename $directory),"
if [[ $mod_names != *$dir_name_comma* ]]; then
let i++;
mod_names="${mod_names} $dir_name_comma"
fi
fi
done
echo "mod_names array:" $mod_names
echo "length:" $i

View file

@ -0,0 +1,262 @@
local c_air = minetest.get_content_id("air")
local c_light1 = minetest.get_content_id("cozylights:light1")
local c_lights = { c_light1, c_light1 + 1, c_light1 + 2, c_light1 + 3, c_light1 + 4, c_light1 + 5, c_light1 + 6,
c_light1 + 7, c_light1 + 8, c_light1 + 9, c_light1 + 10, c_light1 + 11, c_light1 + 12, c_light1 + 13 }
local gent_total = 0
local gent_count = 0
local mf = math.floor
--local cozycids_sunlight_propagates = cozylights.cozycids_sunlight_propagates
local function destroy_stale_wielded_light(data,param2data,a,cozyplayer)
local c_light1 = c_lights[1]
local c_light14 = c_lights[14]
for j,p in ipairs(cozyplayer.prev_wielded_lights) do
if a and a:containsp(p) then
local idx = a:indexp(p)
local cid = data[idx]
if cid >= c_light1 and cid <= c_light14 then
if param2data[idx] > 0 and param2data[idx] <= 14 then
data[idx] = c_light1 + param2data[idx] - 1
else
data[idx] = c_air
end
end
else
local node = minetest.get_node(p)
if string.find(node.name, "cozylights:light") then
if node.param2 == 0 then
minetest.set_node(p,{name="air"})
else
minetest.set_node(p,{name="cozylights:light"..node.param2})
end
end
end
end
cozyplayer.prev_wielded_lights = {}
end
--- Like normal raycast but only covers surfaces, faster for large distances, somewhat less accurate
local function lightcast_lite(pos, dir, dirs, radius,data, param2data, a,dim_levels,cozyplayer)
local px, py, pz, dx, dy, dz = pos.x, pos.y, pos.z, dir.x, dir.y, dir.z
local c_light14 = c_lights[14]
local light_nerf = 0
for i = 1, radius do
local x = mf(dx*i+0.5) + px
local y = mf(dy*i+0.5) + py
local z = mf(dz*i+0.5) + pz
local idx = a:index(x,y,z)
local cid = data[idx]
if cid and (cid == c_air or (cid >= c_light1 and cid <= c_light14)) then
for n = 1, 6 do
local adj_idx = idx+dirs[n]
local adj_cid = data[adj_idx]
if adj_cid and ((adj_cid < c_light1 and adj_cid ~= c_air)or adj_cid > c_light14) then
local dim = (dim_levels[i] - light_nerf) > 0 and (dim_levels[i] - light_nerf) or 1
local light = c_lights[dim]
if light > cid then
data[idx] = light
table.insert(cozyplayer.prev_wielded_lights, {x=x,y=y,z=z})
if cid == c_air and param2data[idx] > 0 then
param2data[idx] = 0
end
end
break
end
end
else
break
end
end
end
function cozylights:wielded_light_cleanup(player,cozyplayer,radius)
local pos = vector.round(player:getpos())
local vm = minetest.get_voxel_manip()
local emin, emax
local last_pos = cozyplayer.last_pos
local distance = vector.distance(pos,last_pos)
if distance < 20 then
local pos1 = {
x=pos.x < last_pos.x and pos.x or last_pos.x,
y=pos.y < last_pos.y and pos.y or last_pos.y,
z=pos.z < last_pos.z and pos.z or last_pos.z,
}
local pos2 = {
x=pos.x > last_pos.x and pos.x or last_pos.x,
y=pos.y > last_pos.y and pos.y or last_pos.y,
z=pos.z > last_pos.z and pos.z or last_pos.z,
}
emin, emax = vm:read_from_map(vector.subtract(pos1, radius+1), vector.add(pos2, radius+1))
else
emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
end
local data = vm:get_data()
local a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
local param2data = vm:get_param2_data()
destroy_stale_wielded_light(data,param2data,a,cozyplayer)
cozylights:setVoxelManipData(vm,data,nil,true)
end
local max_wield_light_radius = cozylights.max_wield_light_radius
function cozylights:set_wielded_light_radius(_radius)
max_wield_light_radius = _radius
minetest.settings:set("cozylights_wielded_light_radius",_radius)
cozylights.max_wield_light_radius = _radius
end
--ffi.cdef([[
--typedef struct {float x, y, z;} v3float;
--typedef struct {int16_t x, y, z;} v3;
--typedef struct {uint16_t* data; uint8_t* param2data;} vm_data;
--vm_data l_ttt(
-- v3* sphere_surface, int sphere_surface_length, v3 pos, v3 minp, v3 maxp, uint16_t radius, uint16_t* data, uint8_t* param2data,
-- uint8_t* dim_levels, bool* cozycids_sunlight, int c_air, uint16_t* c_lights
--);
--]])
--local ctest = ffi.load(cozylights.modpath.."/liblight.so")
function cozylights:draw_wielded_light(pos, last_pos, cozy_item,vel,cozyplayer,vm,a,data,param2data,emin,emax)
local t = os.clock()
local update_needed = 0
local radius, dim_levels = cozylights:calc_dims(cozy_item)
radius = radius > max_wield_light_radius and max_wield_light_radius or radius
if radius == 0 then
destroy_stale_wielded_light(data,param2data,a,cozyplayer)
local node = minetest.get_node(pos)
if node.name == "air" or string.match(node.name,"cozylights:") then
local brightness_mod = cozy_item.modifiers ~= nil and cozylights.coziest_table[cozy_item.modifiers].brightness or 0
local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) > 0 and mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) or 0
max_light = max_light > 14 and 14 or max_light
local cid = minetest.get_content_id("cozylights:light"..max_light)
if cid > minetest.get_content_id(node.name) then
minetest.set_node(pos,{name="cozylights:light"..max_light,param2=node.param2})
cozyplayer.prev_wielded_lights[#cozyplayer.prev_wielded_lights+1] = pos
end
else
pos.y = pos.y - 1
local n_name = minetest.get_node(pos).name
if n_name == "air" or string.match(n_name,"cozylights:") then
local brightness_mod = cozy_item.modifiers ~= nil and cozylights.coziest_table[cozy_item.modifiers].brightness or 0
local max_light = mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) > 0 and mf(cozy_item.light_source + cozylights.brightness_factor + brightness_mod) or 0
max_light = max_light > 14 and 14 or max_light
local cid = minetest.get_content_id("cozylights:light"..max_light)
if cid > minetest.get_content_id(node.name) then
minetest.set_node(pos,{name="cozylights:light"..max_light,param2=node.param2})
cozyplayer.prev_wielded_lights[#cozyplayer.prev_wielded_lights+1] = pos
end
end
end
return
end
local possible_pos = vector.add(pos,vel)
local node = minetest.get_node(possible_pos)
if node.name == "air" or string.match(node.name, "cozylights:light") then
pos = possible_pos
end
if vm == nil then
vm = minetest.get_voxel_manip()
local distance = vector.distance(pos,last_pos)
if distance < 20 then
local pos1 = {
x=pos.x < last_pos.x and pos.x or last_pos.x,
y=pos.y < last_pos.y and pos.y or last_pos.y,
z=pos.z < last_pos.z and pos.z or last_pos.z,
}
local pos2 = {
x=pos.x > last_pos.x and pos.x or last_pos.x,
y=pos.y > last_pos.y and pos.y or last_pos.y,
z=pos.z > last_pos.z and pos.z or last_pos.z,
}
emin, emax = vm:read_from_map(vector.subtract(pos1, radius+1), vector.add(pos2, radius+1))
else
emin, emax = vm:read_from_map(vector.subtract(pos, radius+1), vector.add(pos, radius+1))
end
data = vm:get_data()
param2data = vm:get_param2_data()
a = VoxelArea:new{
MinEdge = emin,
MaxEdge = emax
}
update_needed = 1
end
destroy_stale_wielded_light(data,param2data,a,cozyplayer)
local c_light14 = c_lights[14]
local sphere_surface = cozylights:get_sphere_surface(radius)
local px = pos.x
local py = pos.y
local pz = pos.z
local y_below = py - 1
local y_above = py + 1
local cidb = data[a:index(px,y_below,pz)]
local cida = data[a:index(px,y_above,pz)]
if cidb and cida then
if (cidb == c_air or (cidb >= c_light1 and cidb <= c_light14))
and cida ~= c_air and (cida < c_light1 or cida > c_light14)
then
py = py - 1
end
else
return
end
local zstride, ystride = a.zstride, a.ystride
local dirs = { -1*ystride, 1*ystride,-1,1,-1*zstride,1*zstride}
--[[--cdata experiments, so if we offload heavy lifting on c, it will actually be slower by 20%
--not even a bit faster, so i d rather not continue on this
--because vm:set_data works with lua state and expects lua table,
--and interpreting c types back to lua table seems to be ridiculously expensive to bother
--basically lua is useless and helpless without lua state
minetest.chat_send_all("jit.status() "..cozylights:dump(jit.status()))
local csphere_surface = ffi.new("v3struct["..(#sphere_surface+1).."]", sphere_surface)
local cpos = ffi.new("v3struct", pos)
local cemin = ffi.new("v3struct",emin)
local cemax = ffi.new("v3struct",emax)
local cradius = ffi.new("int",radius)
local testcdata = ffi.new("uint16_t["..(#data).."]")
local cdim_levels = ffi.new("uint16_t["..(#dim_levels+1).."]", dim_levels)
local cc_air = ffi.new("int",c_air)
local cc_lights = ffi.new("uint16_t["..(#c_lights+1).."]", c_lights)
for i = 1, #data do
testcdata[i-1] = ffi.new("uint16_t",data[i])
end
local cparam2data = ffi.new("uint16_t["..#param2data.."]")
for i = 1, #param2data do
cparam2data[i-1] = ffi.new("uint16_t",param2data[i])
end
local ccozycids = ffi.new("bool["..#cozycids_sunlight_propagates.."]",cozycids_sunlight_propagates)
local length = ffi.new("int",#sphere_surface)
local idk = (ctest.l_ttt(csphere_surface,length, cpos,cemin,cemax,cradius,testcdata,cparam2data,cdim_levels,ccozycids,cc_air,cc_lights))
idk = idk.data
if idk ~= nil then
for i=0,#data do
local incoming = tonumber(idk[i])
if data[i+1] ~= incoming then
data[i+1] = incoming
table.insert(cozyplayer.prev_wielded_lights, a:position(i+1))
end
end
end
for i = 1, #param2data do
param2data[i] = tonumber(cparam2data[i-1])
end]]
for i,pos2 in ipairs(sphere_surface) do
lightcast_lite(pos, vector.direction(pos,{x=px+pos2.x,y=py+pos2.y,z=pz+pos2.z}),dirs,radius,data,param2data,a,dim_levels,cozyplayer)
end
if update_needed == 1 then
cozylights:setVoxelManipData(vm,data,param2data,true)
end
cozyplayer.last_wield_radius = radius
gent_total = gent_total + mf((os.clock() - t) * 1000)
gent_count = gent_count + 1
print("Av wield illum time " .. mf(gent_total/gent_count) .. " ms. Sample of: "..gent_count)
end

View file

@ -205,7 +205,7 @@ minetest.register_craft({
minetest.register_craft({
output = "cucina_vegana:pizza_funghi_raw",
recipe = { {"", "group:food_oil", "cucina_vegana:rosemary"},
{"flowers:mushroom_brown", "cucina_vegana:imitation_meat", "flowers:mushroom_brown"},
{"group:food_mushroom", "cucina_vegana:imitation_meat", "group:food_mushroom"},
{"", "group:pizza_dough", ""}
},
replacements = {

View file

@ -216,14 +216,14 @@ if minetest.get_modpath("bbq") then
minetest.register_craft( {
output = "bbq:stuffed_chop_raw 3",
type = "shapeless",
recipe = {"group:food_onion", "group:food_bread", "flowers:mushroom_brown", "mobs:pork_raw", "default:apple"}
recipe = {"group:food_onion", "group:food_bread", "group:food_mushroom", "mobs:pork_raw", "default:apple"}
})
--Stuffed Mushroom Craft Recipe
minetest.register_craft( {
output = "bbq:stuffed_mushroom_raw 2",
type = "shapeless",
recipe = {"group:food_tomato", "group:food_bread", "flowers:mushroom_brown"}
recipe = {"group:food_tomato", "group:food_bread", "group:food_mushroom"}
})
--Stuffed Pepper Craft Recipe

View file

@ -0,0 +1,17 @@
name: luacheck
on: [push, pull_request, workflow_dispatch]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: apt
run: sudo apt-get install -y luarocks
- name: luacheck install
run: luarocks install --local luacheck
- name: luacheck run
run: $HOME/.luarocks/bin/luacheck -q ./

17
mods/emote/.luacheckrc Normal file
View file

@ -0,0 +1,17 @@
unused_args = false
allow_defined_top = true
read_globals = {
"DIR_DELIM",
"minetest", "core",
"dump",
"vector", "nodeupdate",
"VoxelManip", "VoxelArea",
"PseudoRandom", "ItemStack",
"intllib",
}
globals = {
"player_api",
}

502
mods/emote/LICENSE.txt Normal file
View file

@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

69
mods/emote/README.md Normal file
View file

@ -0,0 +1,69 @@
Emote - a player emote API
##########################
This mod aims to provide an API for player model animations such
as sitting, waving, lying down, as well as some providing chat
commands for the player to use these "emotes".
## API
The emote API consists of several functions that allow mods to
manipulate the emote state of each player.
`bool emote.start(player, emotestring)`
Start the named emote for the named player. Returns false if
the emote is unknown or the emote could not be started. Returns
true otherwise.
`emote.stop(player)`
Stops any emote for the named player.
`emote.list()`
Lists known emotestring values.
`emote.attach_to_node(player, pos, locked)`
Attach the player to the node at pos. The attachment will be made using the
parameters provided in the `emote` table in the nodedef:
```
nodedef.emote = {
emotestring = "sit",
eye_offset = {x = 0, y = 0, z = 0},
player_offset = {x = 0, y = 1/2, z = 0},
look_horizontal_offset = 0,
}
```
if `locked` is `true`, then the player is fixed to the node and can only
move until he presses `jump`. While sitting the player can look around but
his character does not turn.
The player offset vector will be rotated to account for the node facedir.
## Commands
The emotes are all usable by players using chat commands:
/lay, /sleep, /sit, /point, /freeze, etc.
## TODO
The API currently only allows unattached emotes (ones where the
player can just move and cancel the emote). The API needs to
provide an additional function to allow attached emotes with
rotation and offset so that players can easily sit on chairs,
lie on beds or emote-interact with machines (e.g. point emote
when interacting with a node).
## Can I sit on stair blocks with this?
The patch file `sit-on-stairs.patch` in this project is an example
patch for minetest_game that will allow a player to right-click stair
nodes and sit on them as if they were seats.
## License
Copyright (C) 2016 - Auke Kok <sofar@foo-projects.org>
LGPL-2.1

149
mods/emote/api.lua Normal file
View file

@ -0,0 +1,149 @@
local S = emote.S
local facedir_to_look_horizontal = emote.util.facedir_to_look_horizontal
local vector_rotate_xz = emote.util.vector_rotate_xz
emote.emotes = {}
emote.attached_to_node = {}
emote.emoting = {}
-- API functions
function emote.register_emote(name, def)
emote.emotes[name] = def
minetest.register_chatcommand(name, {
description = S("Makes your character perform the @1 emote", name),
func = function(playername)
local player = minetest.get_player_by_name(playername)
if emote.start(player, name) then
if not emote.settings.announce_in_chat then
return true, S("You @1", name)
end
else
if not emote.settings.announce_in_chat then
return false, S("You failed to @1", name)
end
end
end,
})
end
function emote.start(player, emote_name)
if not minetest.is_player(player) then
emote.emoting[player] = nil
return
end
local emote_def = emote.emotes[emote_name]
if not emote_def then
return false
end
local player_name = player:get_player_name()
player_api.set_animation(player, emote_def.anim_name, emote_def.speed)
if emote_name == "stand" then
emote.emoting[player] = nil
player_api.player_attached[player_name] = nil
else
emote.emoting[player] = true
player_api.player_attached[player_name] = true
end
if emote_def.eye_offset then
player:set_eye_offset(emote_def.eye_offset, emote_def.eye_offset)
else
player:set_eye_offset()
end
if emote.settings.announce_in_chat then
minetest.chat_send_all(("* %s %s"):format(player_name, emote_def.description))
end
if emote_def.stop_after then
minetest.after(emote_def.stop_after, emote.stop, player)
end
return true
end
function emote.stop(player)
emote.start(player, "stand")
end
function emote.list()
local r = {}
for emote_name, _ in pairs(emote.emotes) do
table.insert(r, emote_name)
end
return r
end
function emote.attach_to_node(player, pos, locked)
local node = minetest.get_node(pos)
if node.name == "ignore" then
return false
end
if emote.attached_to_node[player] then
return
end
local def = minetest.registered_nodes[node.name].emote or {}
local emotedef = {
eye_offset = def.eye_offset or {x = 0, y = 1/2, z = 0},
player_offset = def.player_offset or {x = 0, y = 0, z = 0},
look_horizontal_offset = def.look_horizontal_offset or 0,
emotestring = def.emotestring or "sit",
}
local look_horizontal = facedir_to_look_horizontal(node.param2)
local offset = vector_rotate_xz(emotedef.player_offset, look_horizontal)
local rotation = look_horizontal + emotedef.look_horizontal_offset
local new_pos = vector.add(pos, offset)
emote.start(player, emotedef.emotestring)
if locked then
local object = minetest.add_entity(new_pos, "emote:attacher")
if object then
object:get_luaentity():init(player)
object:set_yaw(rotation)
player:set_attach(object, "", emotedef.eye_offset, minetest.facedir_to_dir(node.param2))
emote.attached_to_node[player] = object
end
else
player:set_pos(new_pos)
player:set_eye_offset(emotedef.eye_offset, {x = 0, y = 0, z = 0})
end
player:set_look_horizontal(rotation)
end
function emote.attach_to_entity(player, emotestring, obj)
-- not implemented yet.
end
function emote.detach(player)
if emote.attached_to_node[player] then
emote.attached_to_node[player]:detach()
end
-- check if attached?
player:set_eye_offset(vector.new(), vector.new())
emote.stop(player)
end
minetest.register_globalstep(function()
for player in pairs(emote.emoting) do
local ctrl = player:get_player_control()
if ctrl and (ctrl.jump or ctrl.up or ctrl.down or ctrl.left or ctrl.right) then
emote.stop(player)
end
end
end)

42
mods/emote/entity.lua Normal file
View file

@ -0,0 +1,42 @@
-- entity for locked emotes (attached to nodes, etc)
local attacher = {
description = "Attachment entity for emotes",
physical = false,
visual = "upright_sprite",
visual_size = {x = 1/16, y = 1/16},
spritediv = {x = 1/16, y = 1/16},
collisionbox = {-1/16, -1/16, -1/16, 1/16, 1/16, 1/16},
textures = {"emote_blank.png"},
static_save = false,
init = function(self, player)
self.player = player
end,
}
function attacher:on_step()
if not minetest.is_player(self.player) then
self.object:remove()
return
end
local ctrl = self.player:get_player_control()
if ctrl and ctrl.jump then
self:detach()
end
end
function attacher:detach()
emote.attached[self.player] = nil
if not minetest.is_player(self.player) then
return
end
self.player:set_detach()
self.object:remove()
emote.stop(self.player)
end
minetest.register_entity("emote:attacher", attacher)

85
mods/emote/init.lua Normal file
View file

@ -0,0 +1,85 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local S = minetest.get_translator(modname)
emote = {
modname = modname,
modpath = modpath,
S = S,
log = function(level, messagefmt, ...)
return minetest.log(level, ("[%s] %s"):format(modname, messagefmt:format(...)))
end,
dofile = function(...)
return dofile(table.concat({modpath, ...}, DIR_DELIM) .. ".lua")
end,
}
emote.dofile("settings")
emote.dofile("util")
emote.dofile("api")
emote.dofile("entity")
local model = player_api.registered_models["character.b3d"]
emote.register_emote("stand", {
anim_name = "stand",
speed = 30,
description = S("stands up")
})
emote.register_emote("sit", {
anim_name = "sit",
speed = 30,
description = S("sits"),
})
emote.register_emote("lay", {
anim_name = "lay",
speed = 30,
description = S("lies down"),
})
emote.register_emote("sleep", { -- alias for lay
anim_name = "lay",
speed = 30,
description = S("falls asleep"),
})
model.animations.wave = {x = 192, y = 196, override_local = true}
emote.register_emote("wave", {
anim_name = "wave",
speed = 15,
stop_after = 4,
description = S("waves")
})
model.animations.point = {x = 196, y = 196, override_local = true}
emote.register_emote("point", {
anim_name = "point",
speed = 30,
description = S("points")
})
model.animations.freeze = {x = 205, y = 205, override_local = true}
emote.register_emote("freeze", {
anim_name = "freeze",
speed = 30,
description = S("freezes")
})
--[[
-- testing tool - punch any node to test attachment code
]]--
minetest.register_tool("emote:sleep", {
description = "use me on a bed bottom",
groups = {not_in_creative_inventory = 1},
on_use = function(itemstack, user, pointed_thing)
-- the delay here is weird, but the client receives a mouse-up event
-- after the punch and switches back to "stand" animation, undoing
-- the animation change we're doing.
minetest.after(0.5, emote.attach_to_node, user, pointed_thing.under)
end
})

6
mods/emote/mod.conf Normal file
View file

@ -0,0 +1,6 @@
name = emote
description = Provides player emotes and an api to make the player model sit, lie down and perform other actions.
depends = player_api
release = 24880
author = sofar
title = Emote

BIN
mods/emote/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

5
mods/emote/settings.lua Normal file
View file

@ -0,0 +1,5 @@
emote.settings = {
announce_in_chat = minetest.settings:get_bool("emote.announce_in_chat", false),
}

View file

@ -0,0 +1,3 @@
emote.announce_in_chat (announce emote in chat) bool false

View file

@ -0,0 +1,20 @@
diff --git a/mods/stairs/init.lua b/mods/stairs/init.lua
index 191c78d..5759ce5 100644
--- a/mods/stairs/init.lua
+++ b/mods/stairs/init.lua
@@ -77,6 +77,15 @@ function stairs.register_stair(subname, recipeitem, groups, images, description,
return minetest.item_place(itemstack, placer, pointed_thing, param2)
end,
+ emote = {
+ emotestring = "sit",
+ eye_offset = {x = 0, y = 9, z = 0},
+ player_offset = {x = 3/16, y = 1/16, z = 0},
+ look_horizontal_offset = math.pi,
+ },
+ on_rightclick = function(pos, node, clicker)
+ emote.attach_to_node(clicker, pos, true)
+ end,
})
-- for replace ABM

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

27
mods/emote/util.lua Normal file
View file

@ -0,0 +1,27 @@
local util = {}
-- helper functions
function util.facedir_to_look_horizontal(dir)
if dir == 0 then
return 0
elseif dir == 1 then
return math.pi * 3/2
elseif dir == 2 then
return math.pi
elseif dir == 3 then
return math.pi / 2
else
return nil
end
end
function util.vector_rotate_xz(vec, angle)
local a = angle - (math.pi * 3/2)
return {
x = (vec.z * math.sin(a)) - (vec.x * math.cos(a)),
y = vec.y,
z = (vec.z * math.cos(a)) - (vec.x * math.sin(a))
}
end
emote.util = util

View file

@ -249,8 +249,8 @@ ethereal.add_eatable("ethereal:hearty_stew", 10)
minetest.register_craft({
output = "ethereal:hearty_stew",
recipe = {
{"group:food_onion","flowers:mushroom_brown", "group:food_tuber"},
{"","flowers:mushroom_brown", ""},
{"group:food_onion","group:food_mushroom", "group:food_tuber"},
{"","group:food_mushroom", ""},
{"","group:food_bowl", ""}
}
})
@ -262,8 +262,8 @@ if fredo then
minetest.register_craft({
output = "ethereal:hearty_stew",
recipe = {
{"group:food_onion","flowers:mushroom_brown", "group:food_beans"},
{"","flowers:mushroom_brown", ""},
{"group:food_onion","group:food_mushroom", "group:food_beans"},
{"","group:food_mushroom", ""},
{"","group:food_bowl", ""}
}
})

11
mods/fmod/.cdb.json Normal file
View file

@ -0,0 +1,11 @@
{
"type": "MOD",
"name": "fmod",
"title": "fmod",
"license": "LGPL-3.0-or-later",
"media_license": "CC-BY-SA-4.0",
"repo": "https://github.com/fluxionary/minetest-fmod.git",
"website": "https://github.com/fluxionary/minetest-fmod",
"issue_tracker": "https://github.com/fluxionary/minetest-fmod/issues",
"short_description": "flux's mod boilerplate"
}

3
mods/fmod/.check_date.sh Normal file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
grep $(date -u -I) mod.conf
exit $?

Some files were not shown because too many files have changed in this diff Show more