Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst
2
mods/3d_armor_flyswim/.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
9
mods/3d_armor_flyswim/LICENSE.txt
Normal 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
|
||||||
|
|
||||||
|
|
147
mods/3d_armor_flyswim/README.MD
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---------------------------
|
||||||
|
## 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.
|
1
mods/3d_armor_flyswim/description.txt
Normal 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.
|
28
mods/3d_armor_flyswim/i_example_cape.lua
Normal 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,
|
||||||
|
})
|
186
mods/3d_armor_flyswim/i_functions.lua
Normal 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
|
386
mods/3d_armor_flyswim/init.lua
Normal 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)
|
8
mods/3d_armor_flyswim/mod.conf
Normal 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
|
BIN
mods/3d_armor_flyswim/models/3d_armor_character.b3d
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sf.b3d
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sf.blend
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sf.blend1
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sfc.b3d
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend
Normal file
BIN
mods/3d_armor_flyswim/models/3d_armor_character_sfc.blend1
Normal file
BIN
mods/3d_armor_flyswim/models/character_sf.b3d
Normal file
BIN
mods/3d_armor_flyswim/models/character_sf.blend
Normal file
BIN
mods/3d_armor_flyswim/models/character_sf.blend1
Normal file
BIN
mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.b3d
Normal file
BIN
mods/3d_armor_flyswim/models/skinsdb_3d_armor_character_5.blend
Normal file
BIN
mods/3d_armor_flyswim/screenshot.png
Normal file
After Width: | Height: | Size: 64 KiB |
32
mods/3d_armor_flyswim/settingtypes.txt
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
mods/3d_armor_flyswim/swimming_animated.gif
Normal file
After Width: | Height: | Size: 533 KiB |
BIN
mods/3d_armor_flyswim/textures/3d_armor_flyswim_demo_cape.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 7 KiB |
BIN
mods/3d_armor_flyswim/textures/3d_armor_trans.png
Normal file
After Width: | Height: | Size: 274 B |
BIN
mods/3d_armor_flyswim/textures/screenshot.xcf
Normal file
15
mods/bike/README.txt
Normal 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
|
@ -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
|
@ -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
|
0
mods/bike/locale/.gitkeep
Normal file
5
mods/bike/locale/bike.eo.tr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# textdomain: bike
|
||||||
|
Bike Wheel=Bicikla Rado
|
||||||
|
Bike Handles=Biciklaj Teniloj
|
||||||
|
Bike=Biciklo
|
||||||
|
Bike Painter=Bicikla Pentrilo
|
5
mods/bike/locale/bike.fr.tr
Normal 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
|
5
mods/bike/locale/template.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# textdomain: bike
|
||||||
|
Bike Wheel=
|
||||||
|
Bike Handles=
|
||||||
|
Bike=
|
||||||
|
Bike Painter=
|
6
mods/bike/mod.conf
Normal 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
BIN
mods/bike/models/bike.blend
Normal file
38
mods/bike/settingtypes.txt
Normal 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
|
BIN
mods/bike/textures/bike_blank.png
Normal file
After Width: | Height: | Size: 107 B |
BIN
mods/bike/textures/bike_chain.png
Normal file
After Width: | Height: | Size: 184 B |
BIN
mods/bike/textures/bike_gear.png
Normal file
After Width: | Height: | Size: 252 B |
BIN
mods/bike/textures/bike_handles.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
mods/bike/textures/bike_helmet.png
Normal file
After Width: | Height: | Size: 713 B |
BIN
mods/bike/textures/bike_inventory.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
mods/bike/textures/bike_leather.png
Normal file
After Width: | Height: | Size: 439 B |
BIN
mods/bike/textures/bike_metal_base.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
mods/bike/textures/bike_metal_black.png
Normal file
After Width: | Height: | Size: 339 B |
BIN
mods/bike/textures/bike_metal_grey.png
Normal file
After Width: | Height: | Size: 398 B |
BIN
mods/bike/textures/bike_painter.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
mods/bike/textures/bike_spokes.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
mods/bike/textures/bike_spokes.xcf
Normal file
BIN
mods/bike/textures/bike_tread.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
mods/bike/textures/bike_tread2.png
Normal file
After Width: | Height: | Size: 250 B |
BIN
mods/bike/textures/bike_wheel.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
|
@ -1,7 +0,0 @@
|
||||||
globals = {"character_anim"}
|
|
||||||
read_globals = {
|
|
||||||
"modlib",
|
|
||||||
-- Minetest
|
|
||||||
math = {fields = {"sign"}},
|
|
||||||
"vector", "minetest"
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
##### `speed`
|
|
||||||
|
|
||||||
Right arm spin speed
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `1000`
|
|
||||||
* > `0`
|
|
||||||
* <= `10000`
|
|
||||||
|
|
||||||
##### `yaw`
|
|
||||||
|
|
||||||
###### `max`
|
|
||||||
|
|
||||||
Right arm yaw (max)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `160`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
###### `min`
|
|
||||||
|
|
||||||
Right arm yaw (min)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `-30`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### `body`
|
|
||||||
|
|
||||||
##### `turn_speed`
|
|
||||||
|
|
||||||
Body turn speed
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `0.2`
|
|
||||||
* > `0`
|
|
||||||
* <= `1000`
|
|
||||||
|
|
||||||
|
|
||||||
#### `head`
|
|
||||||
|
|
||||||
##### `pitch`
|
|
||||||
|
|
||||||
###### `max`
|
|
||||||
|
|
||||||
Head pitch (max)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `80`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
###### `min`
|
|
||||||
|
|
||||||
Head pitch (min)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `-60`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
|
|
||||||
##### `yaw`
|
|
||||||
|
|
||||||
###### `max`
|
|
||||||
|
|
||||||
Head yaw (max)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `90`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
###### `min`
|
|
||||||
|
|
||||||
Head yaw (min)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `-90`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
|
|
||||||
##### `yaw_restricted`
|
|
||||||
|
|
||||||
###### `max`
|
|
||||||
|
|
||||||
Head yaw restricted (max)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `45`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
###### `min`
|
|
||||||
|
|
||||||
Head yaw restricted (min)
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `0`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `180`
|
|
||||||
|
|
||||||
|
|
||||||
##### `yaw_restriction`
|
|
||||||
|
|
||||||
Head yaw restriction
|
|
||||||
|
|
||||||
* Type: number
|
|
||||||
* Default: `60`
|
|
||||||
* >= `-180`
|
|
||||||
* <= `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")
|
|
|
@ -1 +0,0 @@
|
||||||
modlib
|
|
|
@ -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)
|
|
|
@ -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
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 27 KiB |
|
@ -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]
|
|
|
@ -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
|
@ -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
|
@ -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:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
536
mods/cozylights/chat_commands.lua
Normal 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)
|
41
mods/cozylights/helpers.lua
Normal 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
|
@ -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)
|
||||||
|
|
341
mods/cozylights/light_brush.lua
Normal 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
192
mods/cozylights/node_light.lua
Normal 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
|
@ -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
|
31
mods/cozylights/settingtypes.txt
Normal 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
|
@ -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
|
95
mods/cozylights/test/accuracy.lua
Normal 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
|
||||||
|
|
||||||
|
|
3
mods/cozylights/textures/attribution
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
default_glass.png is by Krock (CC0 1.0)
|
||||||
|
|
||||||
|
my debug textures are WTFPL if anything
|
BIN
mods/cozylights/textures/default_glass.png
Normal file
After Width: | Height: | Size: 270 B |
BIN
mods/cozylights/textures/light_brush.png
Normal file
After Width: | Height: | Size: 624 B |
30
mods/cozylights/util/get_optional_depends.sh
Normal 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
|
262
mods/cozylights/wield_light.lua
Normal 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
|
||||||
|
|
|
@ -205,7 +205,7 @@ minetest.register_craft({
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
output = "cucina_vegana:pizza_funghi_raw",
|
output = "cucina_vegana:pizza_funghi_raw",
|
||||||
recipe = { {"", "group:food_oil", "cucina_vegana:rosemary"},
|
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", ""}
|
{"", "group:pizza_dough", ""}
|
||||||
},
|
},
|
||||||
replacements = {
|
replacements = {
|
||||||
|
|
|
@ -216,14 +216,14 @@ if minetest.get_modpath("bbq") then
|
||||||
minetest.register_craft( {
|
minetest.register_craft( {
|
||||||
output = "bbq:stuffed_chop_raw 3",
|
output = "bbq:stuffed_chop_raw 3",
|
||||||
type = "shapeless",
|
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
|
--Stuffed Mushroom Craft Recipe
|
||||||
minetest.register_craft( {
|
minetest.register_craft( {
|
||||||
output = "bbq:stuffed_mushroom_raw 2",
|
output = "bbq:stuffed_mushroom_raw 2",
|
||||||
type = "shapeless",
|
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
|
--Stuffed Pepper Craft Recipe
|
||||||
|
|
17
mods/emote/.github/workflows/luacheck.yml
vendored
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
After Width: | Height: | Size: 93 KiB |
5
mods/emote/settings.lua
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
emote.settings = {
|
||||||
|
announce_in_chat = minetest.settings:get_bool("emote.announce_in_chat", false),
|
||||||
|
}
|
3
mods/emote/settingtypes.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
emote.announce_in_chat (announce emote in chat) bool false
|
||||||
|
|
20
mods/emote/sit-on-stairs.patch
Normal 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
|
BIN
mods/emote/textures/emote_blank.png
Normal file
After Width: | Height: | Size: 68 B |
27
mods/emote/util.lua
Normal 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
|
|
@ -249,8 +249,8 @@ ethereal.add_eatable("ethereal:hearty_stew", 10)
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
output = "ethereal:hearty_stew",
|
output = "ethereal:hearty_stew",
|
||||||
recipe = {
|
recipe = {
|
||||||
{"group:food_onion","flowers:mushroom_brown", "group:food_tuber"},
|
{"group:food_onion","group:food_mushroom", "group:food_tuber"},
|
||||||
{"","flowers:mushroom_brown", ""},
|
{"","group:food_mushroom", ""},
|
||||||
{"","group:food_bowl", ""}
|
{"","group:food_bowl", ""}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -262,8 +262,8 @@ if fredo then
|
||||||
minetest.register_craft({
|
minetest.register_craft({
|
||||||
output = "ethereal:hearty_stew",
|
output = "ethereal:hearty_stew",
|
||||||
recipe = {
|
recipe = {
|
||||||
{"group:food_onion","flowers:mushroom_brown", "group:food_beans"},
|
{"group:food_onion","group:food_mushroom", "group:food_beans"},
|
||||||
{"","flowers:mushroom_brown", ""},
|
{"","group:food_mushroom", ""},
|
||||||
{"","group:food_bowl", ""}
|
{"","group:food_bowl", ""}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
11
mods/fmod/.cdb.json
Normal 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
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
grep $(date -u -I) mod.conf
|
||||||
|
exit $?
|