write something there

This commit is contained in:
N-Nachtigal 2025-05-04 16:01:41 +02:00
commit b4b6c08f4f
8546 changed files with 309825 additions and 0 deletions

View file

@ -0,0 +1,13 @@
MIT License
Copyright © 2024 EmptyStar <https://github.com/EmptyStar>
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.
---
Texture file `effervescence_particles/textures/effervescence_petals.png` is derived from a texture by Liil/Wilhelmine/Skandarella, licensed MIT. A copy of its license accompanies the texture.

View file

@ -0,0 +1,40 @@
# Effervescence
Add a subtle touch of life to your world with Effervescence! This mod adds ambient particle effects to any world: bustling grasses, blossoming flowers, crumbling stone, and much more.
## Effects
Effervescence comes with the following default effects:
- **dusty** - dust particles that swirl about from dry, dusty nodes
- **crumbly** - bits of loose dirt that fall from ceilings made of stone, dirt, or moss
- **bustling** - grass, pollen, and spores that lift up from the ground
- **snowy** - snowflake squalls that gust up from the ground
- **leafy** - gently falling leaves from trees and bushes
- **blossoming** - flower petals blown away from flowers
- **sporogenic** - bursts of spores shed by mushrooms and certain types of vines and plants
- **sparkly** - fine sparkles generated by crystals and ice
- **bubbling** - tiny bubbles that form at the surface of water
- **walking** - bits of dirt and grass kicked up under players' feet as they walk
## Advanced Usage
Effervescence is actually a modpack that comes with three mods:
- The core `effervescence` mod which provides an API and trigger mechanism for particle effects
- `effervescence_decorators` which defines a set of default *decorators* used to place node metadata during mapgen
- `effervescence_particles` which defines the default particle effects bundled with Effervescence
The secondary mods can be disabled in order to remove the bundled particle effects, and the mods can be individually depended upon for adding custom particle effects or decorators. The mod settings can be used to adjust mod behavior for performance, and the API can be used to control the mod programmatically.
## Notes
- This mod is primarily targeted at [Asuna](https://content.luanti.org/packages/EmptyStar/asuna/) but will mostly work for other typical sandbox games
- This mod utilizes mapgen to determine where particles can occur in the environment which means that environmental particles will only appear in previously unexplored areas
- Particle effects will not be applied to nodes placed by players although this can be achieved by adding `effervescence.particles` node metadata, an advanced task ~~covered in the API documentation~~
## Caveats
- Particle effects may cause lag on older hardware, especially in areas with lots of dense particle-emitting nodes
- This mod adds metadata to the world to determine where particle effects can occur; uninstalling this mod will leave its metadata behind which causes no noticeable effect but will cause your world to permanently take up more space on your device
- The color of particles may be wrong for nodes that use hardware coloring

View file

@ -0,0 +1,273 @@
-- Settings loaded from Asuna
local asuna_amount = asuna.settings.particles.amount
local asuna_enabled = asuna_amount ~= "none" and true or false
local asuna_chance = (function()
if asuna_amount == "less" then
return 29
elseif asuna_amount == "more" then
return 70
elseif asuna_amount == "maximum" then
return 100
else
return 1
end
end)()
local asuna_interval = (function()
if asuna_amount == "less" then
return 5.25
elseif asuna_amount == "more" then
return 3.5
elseif asuna_amount == "maximum" then
return 2.0
else
return 60.0
end
end)()
effervescence = {
-- Settings loaded from settingtypes.txt
settings = {
environmental = {
enabled = asuna_enabled,
interval = asuna_interval,
chance = asuna_chance,
radius_x = tonumber(core.settings:get("effervescence.environmental.radius_x",18) or 18),
radius_y = tonumber(core.settings:get("effervescence.environmental.radius_y",6) or 6),
radius_z = tonumber(core.settings:get("effervescence.environmental.radius_z",18) or 18),
look_dir_bias = tonumber(core.settings:get("effervescence.environmental.look_dir_bias",4) or 4),
},
player = {
enabled = asuna_enabled,
interval = tonumber(core.settings:get("effervescence.player.interval",0.5) or 0.5),
},
},
-- Node meta decorator registration
decorators = {},
register_decorator = function(def)
if type(def.name) ~= "string" or effervescence.decorators[def.name] then
return false, "invalid name or name already in use"
end
if type(def.apply_to) ~= "function" then
return false, "apply_to must be a function"
end
if type(def.decorate) ~= "function" then
return false, "decorate must be a function"
end
effervescence.decorators[def.name] = def
return true
end,
-- Player-based particle registration
player_particles = {},
register_player_particles = function(def)
if type(def.name) ~= "string" or def.name:find(",") or effervescence.environmental_particles[def.name] then
return false, "invalid name or name already in use"
end
if type(def.applies_to) ~= "function" then
return false, "applies_to must be a function"
end
if type(def.emit) ~= "function" then
return false, "emit must be a function"
end
def.check = (type(def.check) == "function" and def.check) or function() return true end
effervescence.player_particles[def.name] = def
return true
end,
-- Environmental particle registration
environmental_particles = {},
register_environmental_particles = function(def)
if type(def.name) ~= "string" or def.name:find(",") or effervescence.environmental_particles[def.name] then
return false, "invalid name or name already in use"
end
if type(def.applies_to) ~= "function" then
return false, "applies_to must be a function"
end
if type(def.emit) ~= "function" then
return false, "emit must be a function"
end
def.check = (type(def.check) == "function" and def.check) or function() return true end
effervescence.environmental_particles[def.name] = def
return true
end,
-- Add particle to node meta
add_particle_meta = function(pos, particle)
local meta = core.get_meta(pos)
local particles = meta:get("effervescence.particles")
if particles then
meta:set_string("effervescence.particles",particles .. "," .. particle)
else
meta:set_string("effervescence.particles",particle)
end
end,
}
-- Player effect trigger
local math_sign = function(number)
return (number > 0 and 1) or (number < 0 and -1) or 0
end
local math_round = function(number)
return math_sign(number) * math.floor(math.abs(number) + 0.5)
end
local get_look_bias = effervescence.settings.environmental.look_dir_bias > 0 and function(look_dir)
local bias = effervescence.settings.environmental.look_dir_bias
return math_round(look_dir.x * bias), math_round(look_dir.y * bias / 2), math_round(look_dir.z * bias)
end or function() return 0, 0, 0 end
-- Environmental particles
local environmental_particles
if effervescence.settings.environmental.enabled then
local etime = effervescence.settings.environmental.interval
environmental_particles = function(dtime)
etime = etime - dtime
if etime < 0 then
etime = effervescence.settings.environmental.interval
local already_emitted = {}
for _,player in ipairs(core.get_connected_players()) do
if player then
local pname = player:get_player_name()
local pos = player:get_pos()
local look_dir = player:get_look_dir()
local bx, by, bz = get_look_bias(look_dir)
for _,emitter in ipairs(
core.find_nodes_with_meta(
pos:offset(-effervescence.settings.environmental.radius_x + bx,-effervescence.settings.environmental.radius_y + by,-effervescence.settings.environmental.radius_z + bz),
pos:offset(effervescence.settings.environmental.radius_x + bx,effervescence.settings.environmental.radius_y + by,effervescence.settings.environmental.radius_z + bz)
)) do
local hash = core.hash_node_position(emitter)
if not already_emitted[hash] then
local particles = core.get_meta(emitter):get("effervescence.particles")
if particles and math.random(1,100) <= effervescence.settings.environmental.chance then
particles = particles:split(",")
local r = math.random(1,#particles)
local len = #particles
for i = r, len + r - 1, 1 do
local particle = effervescence.environmental_particles[particles[i % len + 1]]
if particle and particle:check(emitter) then
local pdef = particle:emit(emitter)
pdef.playername = pname,
core.add_particlespawner(pdef)
already_emitted[hash] = true
break
end
end
end
end
end
end
end
end
end
else
environmental_particles = function()
-- no-op; environmental particles are disabled
end
end
-- Player walk particles
local player_particles
if effervescence.settings.player.enabled then
-- Lookup map of players to particle trigger time
local ptime = {}
-- Initialize player in map on join
core.register_on_joinplayer(function(player)
ptime[player:get_player_name()] = effervescence.settings.player.interval
end)
-- Remove player time
core.register_on_leaveplayer(function(player)
ptime[player:get_player_name()] = nil
end)
-- Particle spawning function
player_particles = function(dtime)
for _,player in ipairs(core.get_connected_players()) do
if player then
local pname = player:get_player_name()
if ptime[pname] then
ptime[pname] = ptime[pname] - dtime
if ptime[pname] < 0 then
ptime[pname] = effervescence.settings.player.interval
for name,particle in pairs(effervescence.player_particles) do
if particle:check(player) then
core.add_particlespawner(particle:emit(player))
end
end
end
end
end
end
end
else
player_particles = function()
-- no-op; player particles are disabled
end
end
-- Particle trigger loop
core.register_globalstep(function(dtime)
environmental_particles(dtime)
player_particles(dtime)
end)
-- Identify target nodes and decorators
core.register_on_mods_loaded(function()
-- Node-particle map setup
local npmap = {}
for decorator,_ in pairs(effervescence.decorators) do
npmap[decorator] = {}
end
for node,ndef in pairs(core.registered_nodes) do
-- Environmental particles
for particle,pdef in pairs(effervescence.environmental_particles) do
local decorators = pdef:applies_to(node,ndef) or {}
for _,decorator in ipairs(decorators) do
npmap[decorator][node] = npmap[decorator][node] or {}
table.insert(npmap[decorator][node],particle)
end
end
-- Player particles
for particle,pdef in pairs(effervescence.player_particles) do
pdef:applies_to(node,ndef)
end
end
-- Apply particles to decorators
for name,decorator in pairs(effervescence.decorators) do
decorator:apply_to(npmap[name])
end
-- Hack for VoxelLibre/Mineclonia
local oggcm = core.get_current_modname
core.get_current_modname = function()
return "effervescence"
end
-- Trigger decorators during mapgen
core.register_on_generated(function(minp, maxp, blockseed)
for name,decorator in pairs(effervescence.decorators) do
decorator:decorate(minp, maxp, blockseed)
end
end)
-- Undo hack
core.get_current_modname = oggcm
end)

View file

@ -0,0 +1,5 @@
name = effervescence
title = Effervescence
description = Add a subtle touch of life to your worlds with effervescent particles
author = EmptyStar
optional_depends = asuna_core, caverealms, everness

View file

@ -0,0 +1,113 @@
local strategy = {
-- decoration-based gennotify node selection strategy
gennotify = {
register = function(self, name, ddef)
ddef.name = name
effervescence.register_decorator({
name = name,
apply_to = function(self, npmap)
local nlist = {}
for node,particles in pairs(npmap) do
table.insert(nlist,node)
npmap[node] = table.concat(particles,",")
end
self.nodes = npmap
ddef.place_on = nlist
core.register_decoration(ddef)
self.did = core.get_decoration_id(name)
core.set_gen_notify({ decoration = true },{ self.did })
self.did = "decoration#" .. self.did
end,
decorate = function(self, minp, maxp, blockseed)
local gennotify = core.get_mapgen_object("gennotify")
local positions = gennotify[self.did] or {}
for _,pos in ipairs(positions) do
local node = core.get_node(pos).name
local particles = self.nodes[node]
if particles then
local meta = core.get_meta(pos)
meta:set_string("effervescence.particles",particles)
end
end
end,
})
end,
},
-- random sample node selection strategy
sample = {
generation_id = 0,
nodelist = { init = false },
init_decorate = function(self, blockseed)
if self.nodelist.init == false then
local nodelist = {}
for node,_ in pairs(self.nodelist) do
table.insert(nodelist,node)
end
self.nodelist = nodelist
end
if blockseed ~= self.generation_id then
self.generation_id = blockseed
local _, emin, emax = core.get_mapgen_object("voxelmanip")
self.nodes = core.find_nodes_in_area(emin,emax,self.nodelist,true)
end
end,
register = function(self, name, per)
local sampler = self
effervescence.register_decorator({
name = name,
apply_to = function(self, npmap)
self.nodes = npmap
for node,particles in pairs(npmap) do
npmap[node] = table.concat(particles,",")
sampler.nodelist[node] = true
end
end,
decorate = function(self, minp, maxp, blockseed)
sampler:init_decorate(blockseed)
for node,particles in pairs(self.nodes) do
local positions = sampler.nodes[node]
if positions then
local plen = #positions
local pcgr = PcgRandom(blockseed)
for i = 0, math.floor(plen / per), 1 do
local pos = positions[pcgr:next(1,plen)]
local meta = core.get_meta(pos)
meta:set_string("effervescence.particles",particles)
end
end
end
end,
})
end,
},
}
-- Register gennotify decorators
strategy.gennotify:register("effervescence:floors",{
deco_type = "simple",
fill_ratio = 0.00375,
decoration = "air",
flags = "all_floors",
})
strategy.gennotify:register("effervescence:ceilings",{
deco_type = "simple",
fill_ratio = 0.00375,
decoration = "air",
flags = "all_ceilings",
})
strategy.gennotify:register("effervescence:liquid_surface",{
deco_type = "simple",
fill_ratio = 0.00425,
decoration = "air",
flags = "liquid_surface",
})
-- Register sample decorators
strategy.sample:register("effervescence:many",10)
strategy.sample:register("effervescence:few",50)
strategy.sample:register("effervescence:rare",250)

View file

@ -0,0 +1,5 @@
name = effervescence_decorators
title = Effervescence Builtin Decorators
description = Adds useful decorators for Effervescence
author = EmptyStar
depends = effervescence

View file

@ -0,0 +1,512 @@
-- Extract particle graphics from a node texture
local function extract_tile(tiles,color)
if not tiles then
return "blank.png"
end
if not color and tiles.color then
color = tiles.color
end
if type(tiles[1]) == "string" then
return tiles[1], color
elseif type(tiles[1]) == "table" then
return tiles[1].name or tiles[1].image, color
else
return "blank.png"
end
end
local function extract_particles(tiles,color,coords)
tiles, color = extract_tile(tiles,color)
return tiles .. "^[resize:16x16^[sheet:8x8:" .. (coords or (math.random(0,7) .. "," .. math.random(0,7))) .. (color and ("^[multiply:" .. color) or "")
end
-- Get texture blend value
local blend_method = core.features.particle_blend_clip and "clip" or "alpha"
-- Dusty particles (sand, dry grass, etc.)
effervescence.register_environmental_particles({
name = "effervescence:dusty",
check = function(self, pos)
return (pos.y < 0 or pos.y > 6) and core.get_node(pos:offset(0,1,0)).name == "air"
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local does_apply = (groups.stone and groups.stone > 0) or (groups.sand and groups.sand > 0) or (groups.everness_sand and groups.everness_sand > 0) or node:find("dry_") or node:find("clay") or node:find("gravel") or node:find("litter$") or node:find("podzol")
return does_apply and { "effervescence:floors" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(56,64),
time = 5,
pos = {
min = pos:add(vector.new(-6,0.575,-6)),
max = pos:add(vector.new(6,1,6)),
},
minsize = 0.2,
maxsize = 0.275,
minvel = { x = -1, y = -0.02, z = -1 },
maxvel = { x = 1, y = 0.02, z = 1 },
minexptime = 6,
maxexptime = 8,
minacc = {x = -1.5, y = 0, z = -1.5},
maxacc = {x = 1.5, y = 0.05, z = 1.5},
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Crumbly particles (falling bits of stone, dirt, gravel, etc.)
effervescence.register_environmental_particles({
name = "effervescence:crumbly",
check = function(self, pos)
return core.get_node(pos:offset(0,-1,0)).name == "air"
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local does_apply = (groups.stone and groups.stone > 0) or (groups.crumbly and groups.crumbly > 0) or (groups.soil and groups.soil > 0) or node:find("gravel") or node:find("ore") or node:find("moss") or node:find(":dirt_")
return does_apply and { "effervescence:ceilings" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = 6,
time = 0.5,
pos = {
min = pos:add(vector.new(-0.45,-0.75,-0.45)),
max = pos:add(vector.new(0.45,-0.9,0.45)),
},
minvel = {x = 0, y = -5, z = 0},
maxvel = {x = -0.1, y = -7, z = -0.1},
minacc = {x = -0.1, y = -2, z = -0.1},
maxacc = {x = 0.1, y = -3, z = 0.1},
minexptime = 0.25,
maxexptime = 1,
minsize = 0.25,
maxsize = 1.25,
glow = ndef.glow or ndef.light_source,
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Bustling particles (bit of grasses and mosses lifting off the ground)
effervescence.register_environmental_particles({
name = "effervescence:bustling",
check = function(self, pos)
return core.get_node(pos:offset(0,1,0)).name == "air"
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local does_apply = node:find("grass") or node:find("moss") or node:find("lichen") or node:find("^ethereal:.+_dirt$") or node:find("litter$") or node:find("mycelium")
return does_apply and { "effervescence:floors" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(40,48),
time = 6,
pos = {
min = pos:add(vector.new(-8,0.5,-8)),
max = pos:add(vector.new(8,0.75,8)),
},
minvel = {x = -0.5, y = 0.1, z = -0.5},
maxvel = {x = 0.5, y = 0.175, z = 0.5},
minacc = {x = -0.325, y = 0.325, z = -0.325},
maxacc = {x = 0.325, y = 0.5, z = 0.325},
minexptime = 6,
maxexptime = 12,
minsize = 0.25,
maxsize = 0.375,
glow = ndef.glow or ndef.light_source,
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Snowy particles (snowflakes gusting up from the ground)
effervescence.register_environmental_particles({
name = "effervescence:snowy",
check = function(self, pos)
return core.get_node(pos:offset(0,1,0)).name == "air"
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local is_snow = (groups.snowy and groups.snowy > 0) or node:find(":snow") or node:find("_with_snow")
if is_snow then
return { def.drawtype == "nodebox" and "effervescence:rare" or "effervescence:floors" }
else
return nil
end
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(8,40),
time = 2,
pos = {
min = pos:add(vector.new(-3,0,-3)),
max = pos:add(vector.new(3,0.4,3)),
},
minvel = {x = -4, y = 2, z = -4},
maxvel = {x = 4, y = 3, z = 4},
minacc = {x = -1.75, y = 1, z = -1.75},
maxacc = {x = 1.75, y = 1.5, z = 1.75},
minexptime = 3,
maxexptime = 5,
minsize = 0.175,
maxsize = 0.225,
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Leafy particles (falling leaves and snow on leaves)
local leaves = {
air = true, -- special exception for easier check logic
}
effervescence.register_environmental_particles({
name = "effervescence:leafy",
check = function(self, pos)
local below = core.get_node(pos:offset(0,-1,0)).name
return leaves[below]
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local is_leaves = groups.leaves and groups.leaves > 0
if is_leaves then
leaves[node] = true
return { "effervescence:few" }
elseif node:find(":snow$") then
return { "effervescence:many" }
else
return nil
end
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(1,2),
time = 3,
pos = {
min = pos:add(vector.new(-0.45,-0.3,-0.45)),
max = pos:add(vector.new(0.45,-0.475,0.45)),
},
minvel = {x = -1, y = -0.75, z = -1},
maxvel = {x = 1, y = -1.5, z = 1},
minacc = {x = -0.75, y = -1, z = -0.75},
maxacc = {x = 0.75, y = -1.5, z = 0.75},
minexptime = 4,
maxexptime = 6,
minsize = 1,
maxsize = 1.5,
glow = ndef.glow or ndef.light_source,
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Blossoming particles (flower petals on the breeze)
local colors = {
black = "#000000",
white = "#ffffff",
blue = "#1f75fe",
cyan = "#00b7eb",
orange = "#ff8c00",
yellow = "#ffd700",
purple = "#6f2da8",
violet = "#6f2da8",
magenta = "#ff33cc",
red = "#ff2400",
pink = "#ffc0cb",
green = "#7cfc00",
dark_green = "#013220",
dark_gray = "#333333",
darkgray = "#333333",
grey = "#9a9a9a",
brown = "#704214",
}
local flowers = { -- special cases listed here
["farming:sunflower_8"] = "#ffd700",
["farming:cotton_8"] = "#ffffff",
["x_farming:cotton_8"] = "#ffffff",
["dorwinion:dorwinion_glow_leaves"] = "self",
["nightshade:nightshade_glowin_leaves_1"] = "self",
["naturalbiomes:heatherflowernode"] = "self",
["naturalbiomes:heatherflower2node"] = "self",
["naturalbiomes:heatherflower3node"] = "self",
["naturalbiomes:heatherflower4node"] = "self",
}
effervescence.register_environmental_particles({
name = "effervescence:blossoming",
check = function(self, pos)
return core.get_node(pos:offset(0,1,0)).name == "air"
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local is_plant = not def.walkable and def.drawtype:find("plant") and not node:find("shroom") and ((groups.flower and groups.flower > 0) or node:find("flower"))
if is_plant then
local dye = core.get_craft_result({
method = "normal",
width = 1,
items = { node },
})
if not dye.item:is_empty() and dye.item:get_name():find("dye") then
local dgroups = core.registered_items[dye.item:get_name()].groups
for color,hex in pairs(colors) do
if (dgroups["color_" .. color] and dgroups["color_" .. color] > 0) or (dgroups["basecolor_" .. color] and dgroups["basecolor_" .. color] > 0) then
flowers[node] = hex
break
end
end
end
end
return flowers[node] and { "effervescence:many" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(2,3),
time = 1,
pos = {
min = pos:add(vector.new(-0.25,-0.125,-0.25)),
max = pos:add(vector.new(0.25,0.25,0.25)),
},
minvel = {x = -3, y = -0.05, z = -3},
maxvel = {x = 3, y = 1.5, z = 3},
minacc = {x = -0.125, y = -0.125, z = -0.125},
maxacc = {x = 0.125, y = 0.75, z = 0.125},
minexptime = 3,
maxexptime = 5,
minsize = 0.375,
maxsize = 0.5,
glow = ndef.glow or ndef.light_source,
texture = {
name = extract_particles(flowers[node.name] == "self" and ndef.tiles or {[1] = "effervescence_petals.png"},flowers[node.name] ~= "self" and flowers[node.name]),
blend = blend_method,
},
collisiondetection = true,
collision_removal = true,
vertical = false,
}
end,
})
-- Sporogenic particles (primarily mushrooms, glow worms, and certain vines)
effervescence.register_environmental_particles({
name = "effervescence:sporogenic",
check = function(self, pos)
return true
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local does_apply = not def.walkable and def.drawtype:find("plant") and (node:find("glow_worm") or node:find("shroom") or node:find("fung") or node:find("myc") or node:find("coral_grass") or node:find("coral_plant") or node:find("[:_]vine") or node:find("spore") or (groups.mushroom and groups.mushroom > 0))
return does_apply and { "effervescence:many" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(9,12),
time = 1.5,
pos = {
min = pos:add(vector.new(-0.25,-0.1,-0.25)),
max = pos:add(vector.new(0.25,0.1,0.25)),
},
minvel = {x = -0.625, y = -0.075, z = -0.625},
maxvel = {x = 0.625, y = -0.05, z = 0.625},
minacc = {x = 0, y = -0.25, z = 0},
maxacc = {x = 0, y = -0.125, z = 0},
minexptime = 5,
maxexptime = 8,
minsize = 0.25,
maxsize = 0.325,
glow = ndef.glow or ndef.light_source or 2,
texture = {
name = extract_particles(ndef.tiles,ndef.color,"4,7"),
blend = blend_method,
},
collisiondetection = true,
collision_removal = true,
vertical = false,
}
end,
})
-- Sparkly particles (glistening crystals or ice)
local neighbors = {
vector.new(0,1,0),
vector.new(1,0,0),
vector.new(0,0,1),
vector.new(-1,0,0),
vector.new(0,0,-1),
vector.new(0,-1,0),
}
effervescence.register_environmental_particles({
name = "effervescence:sparkly",
check = function(self, pos)
for _,direction in ipairs(neighbors) do
if core.get_node(pos:add(direction)).name == "air" then
return true
end
end
return false
end,
applies_to = function(self, node, def)
local does_apply = (def.drawtype:find("plant") and (node:find("[:_]crystal") or node:find("[:_]gem") or node:find("^caverealms:spike$") or node:find("icicle")) or node:find("[:_]ice$") or node:find("[:_]cave_ice$"))
return does_apply and { "effervescence:many" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(9,11),
time = 4,
pos = {
min = pos:add(vector.new(-0.75,-0.25,-0.75)),
max = pos:add(vector.new(0.75,0.575,0.75)),
},
minvel = {x = 0, y = -0.05, z = 0},
maxvel = {x = 0, y = 0, z = 0},
minacc = {x = 0, y = 0, z = 0},
maxacc = {x = 0, y = 0, z = 0},
minexptime = 3,
maxexptime = 6,
minsize = 0.1,
maxsize = 0.2,
glow = ndef.glow or ndef.light_source or 2,
texture = {
name = extract_particles(ndef.tiles,nil,"4,7") .. "^[sheet:4x4:1,1^[opacity:255^[brighten",
blend = blend_method,
},
collisiondetection = false,
vertical = false,
}
end,
})
-- Tiny bubbles at the surface of water
effervescence.register_environmental_particles({
name = "effervescence:bubbling",
check = function(self, pos)
return core.get_node(pos:offset(0,1,0)).name == "air"
end,
applies_to = function(self, node, def)
local does_apply = (def.liquidtype == "source" and node:find("[:_]water"))
return does_apply and { "effervescence:liquid_surface" }
end,
emit = function(self, pos)
local node = core.get_node(pos)
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(4,6),
time = math.random(3,4),
pos = {
min = pos:add(vector.new(-0.45,0.5,-0.45)),
max = pos:add(vector.new(0.45,0.525,0.45)),
},
minvel = {x = -0.0275, y = -0.025, z = -0.0275},
maxvel = {x = 0.0275, y = 0, z = 0.0275},
minacc = {x = 0, y = 0, z = 0},
maxacc = {x = 0, y = 0, z = 0},
minexptime = 1,
maxexptime = 2,
minsize = 1,
maxsize = 1.25,
glow = ndef.glow or ndef.light_source,
texture = extract_particles(ndef.tiles,ndef.color) .. "^[brighten^[opacity:127",
collisiondetection = false,
vertical = false,
}
end,
})
-- Player walk particles (bits of dirt, snow, etc. kicked up underfoot)
local walking_nodes = {}
effervescence.register_player_particles({
name = "effervescence:walking",
check = function(self, player)
local velocity = player:get_velocity()
if math.abs(velocity.x) > 0.025 or math.abs(velocity.z) > 0.025 then
local below = core.get_node(player:get_pos():offset(0,-0.1,0)).name
return walking_nodes[below]
end
end,
applies_to = function(self, node, def)
local groups = def.groups or {}
local does_apply = (groups.crumbly and groups.crumbly > 0) or (groups.soil and groups.soil > 0) or (groups.sand and groups.sand > 0) or (groups.leaves and groups.leaves > 0) or (groups.snowy and groups.snowy > 0) or node:find(":snow") or node:find("_with_snow")
if does_apply then
walking_nodes[node] = true
return true
else
return false
end
end,
emit = function(self, player)
local pos = player:get_pos()
local node = core.get_node(pos:offset(0,-0.1,0))
local ndef = core.registered_nodes[node.name]
return {
amount = math.random(3,4),
time = 0.25,
pos = {
min = pos:add(vector.new(-0.45,0.175,-0.45)),
max = pos:add(vector.new(0.45,0.275,0.45)),
},
minvel = {x = -1, y = 0.75, z = -1},
maxvel = {x = 1, y = 1, z = 1},
minacc = {x = 0, y = -9.81, z = 0},
maxacc = {x = 0, y = -9.81, z = 0},
minexptime = 1,
maxexptime = 1,
minsize = 0.75,
maxsize = 1,
glow = ndef.glow or ndef.light_source,
texture = {
name = extract_particles(ndef.tiles,ndef.color),
blend = blend_method,
},
collisiondetection = true,
collision_removal = true,
vertical = false,
}
end,
})

View file

@ -0,0 +1,5 @@
name = effervescence_particles
title = Effervescence Builtin Particle Effects
description = Adds builtin effervescent particles that will work with many Luanti games
author = EmptyStar
depends = effervescence, effervescence_decorators

View file

@ -0,0 +1,25 @@
MIT License
Copyright (c) 2022 Skandarella
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.
Textures and Models by Liil/Wilhelmine/ under (MIT) License (c) 2022
Thanks to ShadMOrdre (https://github.com/ShadMOrdre) for fixing the schematics/leaf decay.

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

View file

@ -0,0 +1,4 @@
name = effervescence
title = Effervescence
description = Adds a subtle touch of life to Luanti worlds with ambient particle effects
author = EmptyStar

View file

View file

@ -0,0 +1,30 @@
[Environmental Particles]
# Enable environmental particles? If enabled, newly generated terrain will be decorated with particles that players will see as they explore the world.
effervescence.environmental.enabled (Enable environmental particles?) bool true
# The interval at which environmental particles will generate, every X seconds. A smaller interval will trigger particles more often but will use more system resources and vice-versa.
effervescence.environmental.interval (Particle trigger interval) float 5.25 1.0 60.0
# The percentage chance of environmental particles to generate per node every interval. Lower numbers generate fewer particles and vice-versa.
effervescence.environmental.chance (Chance of particles) int 29 1 100
# The x radius around each player in which effervescent nodes are checked for at each interval.
effervescence.environmental.radius_x (Search radius x) int 16 8 32
# The y radius around each player in which effervescent nodes are checked for at each interval.
effervescence.environmental.radius_y (Search radius y) int 8 4 32
# The z radius around each player in which effervescent nodes are checked for at each interval.
effervescence.environmental.radius_z (Search radius z) int 16 8 32
# The number of nodes to shift a player's search box for effervescent nodes in the environment based on the direction they're looking. This causes more particles to spawn in the direction a player is looking which creates more particles in view at the expense of fewer particles further away in the opposite direction. This number is halved for the y axis.
effervescence.environmental.look_dir_bias (Look direction bias) int 4 0 32
[Player Particles]
# Show particles at player locations? If true, player-based particles will appear near players.
effervescence.player.enabled (Use player particles?) bool true
# How often to show player walk particles, in seconds.
effervescence.player.interval (Player particles interval) float 0.5 0.1 5.0