add oil, dynamic fluid, and waterworks
@ -512,6 +512,12 @@ end)
|
||||
local function car_step(self, dtime)
|
||||
if dtime > .2 then dtime = .2 end
|
||||
local def = cars_registered_cars[self.object:get_entity_name()]
|
||||
if self.ignition and oil and def.gas_usage then
|
||||
if self.gas <= 0 then
|
||||
self.ignition = false
|
||||
self.gas = 0
|
||||
end
|
||||
end
|
||||
local velocity = self.object:getvelocity()
|
||||
local yaw = self.object:getyaw()
|
||||
if not yaw then return end
|
||||
@ -602,8 +608,12 @@ local function car_step(self, dtime)
|
||||
end
|
||||
end
|
||||
local driver = self.passengers[1].player
|
||||
if driver and self.ignition then
|
||||
driver:hud_change(self.hud, "text", tostring(math.abs(rnd(self.v*2.23694, 10)).." MPH"))
|
||||
if driver then
|
||||
local text = tostring(math.abs(rnd(self.v*2.23694, 10)).." MPH")
|
||||
if oil and def.gas_usage then
|
||||
text = text.." "..tostring(math.floor(self.gas*10)/10).."L Gas"
|
||||
end
|
||||
driver:hud_change(self.hud, "text", text)
|
||||
local ctrl = driver:get_player_control()
|
||||
local sign
|
||||
local lights
|
||||
@ -636,6 +646,7 @@ local function car_step(self, dtime)
|
||||
end
|
||||
end
|
||||
--VELOCITY MOVEMENT
|
||||
if self.ignition then
|
||||
local newv = self.v
|
||||
if ctrl.up then
|
||||
if sign >= 0 then
|
||||
@ -676,6 +687,16 @@ local function car_step(self, dtime)
|
||||
cars.setlight(lights, "brakelights", false)
|
||||
end
|
||||
|
||||
else
|
||||
local sign
|
||||
if self.v == 0 then sign = 0 else sign = get_sign(self.v) end
|
||||
if sign ~= 0 then
|
||||
self.v = self.v - def.coasting*dtime*sign
|
||||
if get_sign(self.v) ~= sign then
|
||||
self.v = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
--ACCELERATION MOVEMENT
|
||||
--[[
|
||||
if ctrl.up then
|
||||
@ -742,7 +763,7 @@ local function car_step(self, dtime)
|
||||
if attachTimer >= 5 then
|
||||
if self.wheel.backright then self.wheel.backright:set_attach(self.object, "", {z=-11.75,y=2.5,x=-8.875}, {x=0,y=0,z=0}) end
|
||||
if self.wheel.backleft then self.wheel.backleft:set_attach(self.object, "", {z=-11.75,y=2.5,x=8.875}, {x=0,y=0,z=0}) end
|
||||
if self.lights then self.lights:set_attach(self.object, "", {x=0,y=0,x=0}, {x=0,y=0,z=0}) end
|
||||
if self.lights then self.lights:set_attach(self.object, "", {x=0,y=0,z=0}, {x=0,y=0,z=0}) end
|
||||
end
|
||||
self.lastctrl = ctrl
|
||||
else
|
||||
@ -833,6 +854,13 @@ local function car_step(self, dtime)
|
||||
local abs_v = math.abs(self.v)
|
||||
--if abs_v > 0 and driver ~= nil then
|
||||
if self.ignition then
|
||||
if oil and def.gas_usage and not slowing then
|
||||
if self.v == 0 or self.cruise then--idle gas usage
|
||||
self.gas = self.gas - (def.gas_usage*dtime*.25)/60
|
||||
else--you are accelerating
|
||||
self.gas = self.gas - (def.gas_usage*dtime)/60
|
||||
end
|
||||
end
|
||||
self.timer1 = self.timer1 + dtime
|
||||
local rpm = 1
|
||||
for i, tab in pairs(def.rpmvalues) do
|
||||
@ -995,6 +1023,7 @@ function cars_register_car(def)
|
||||
if not self.timer1 then self.timer1 = 0 end
|
||||
if not self.timer2 then self.timer2 = 0 end
|
||||
if not self.hp then self.hp = 20 end
|
||||
if not self.gas then self.gas = 0 end
|
||||
if not self.platenumber then
|
||||
self.platenumber = {}
|
||||
end
|
||||
@ -1008,6 +1037,7 @@ function cars_register_car(def)
|
||||
self.trunkinv = deserializeContents(deserialized.trunk)
|
||||
self.key = deserialized.key
|
||||
self.hp = deserialized.hp or 20
|
||||
self.gas = deserialized.gas or 0
|
||||
if deserialized.plate then
|
||||
self.platenumber.text = deserialized.plate.text
|
||||
end
|
||||
@ -1057,7 +1087,7 @@ function cars_register_car(def)
|
||||
self.object:set_hp(self.hp)
|
||||
end,
|
||||
get_staticdata = function(self)
|
||||
return minetest.serialize({owner = self.owner, trunk = serializeContents(self.trunkinv), secret = self.secret, locked = self.locked, key = self.key, plate = self.platenumber, hp = self.hp})
|
||||
return minetest.serialize({owner = self.owner, trunk = serializeContents(self.trunkinv), secret = self.secret, locked = self.locked, key = self.key, plate = self.platenumber, hp = self.hp, gas = self.gas})
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
car_step(self, dtime)
|
||||
@ -1074,7 +1104,21 @@ function cars_register_car(def)
|
||||
return true
|
||||
end
|
||||
local punchitem = puncher:get_wielded_item():get_name()
|
||||
if (punchitem == "") and (time_from_last_punch >= tool_capabilities.full_punch_interval) and math.random(1,2) == 1 then
|
||||
if oil and name and oil.fueling[name] then
|
||||
local data = oil.fueling[name]
|
||||
local obj = oil.fueling[name].obj
|
||||
local pos = oil.fueling[name].pos
|
||||
minetest.get_node_timer(pos):start(1)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("name", name)
|
||||
if obj then
|
||||
local ent = obj:get_luaentity()
|
||||
ent.finishobj = self.object
|
||||
if def.gas_offset then
|
||||
ent.finishoffset = def.gas_offset
|
||||
end
|
||||
end
|
||||
elseif (punchitem == "") and (time_from_last_punch >= tool_capabilities.full_punch_interval) and math.random(1,2) == 1 then
|
||||
local closeid = getClosest(puncher, self)
|
||||
if DEBUG_TEXT then
|
||||
minetest.chat_send_all(tostring(closeid))
|
||||
|
@ -21,6 +21,8 @@ local sedandef = {
|
||||
acceleration = 3,
|
||||
braking = 10,
|
||||
coasting = 2,
|
||||
gas_usage = 1,
|
||||
gas_offset = {x=-1,y=.8,z=-1.1},
|
||||
max_speed = 24.5872,
|
||||
trunksize = {x=6,y=2},
|
||||
trunkloc = {x = 0, y = 4, z = -8},
|
||||
|
17
mods/dynamic_liquid/.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
47
mods/dynamic_liquid/.gitignore
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# OSX
|
||||
# =========================
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
21
mods/dynamic_liquid/LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
dynamic_liquid mod for Minetest
|
||||
|
||||
Copyright (C) 2017 FaceDeer
|
||||
|
||||
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.
|
39
mods/dynamic_liquid/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
This mod implements a simple approach to making the various liquid node types (water, lava) behave in a more realistic manner. In a nutshell, it sets an Active Block Modifier running for liquid nodes that implements the following behavior:
|
||||
|
||||
* If there is a "flowing" or air node below a "source" liquid block, swap the liquid block with the node below it (ie, the liquid drops)
|
||||
* Else swap with a random flowing or air node beside it.
|
||||
|
||||
This causes "source" blocks for liquids to randomly shuffle around when they don't completely fill a horizontal layer, and causes them to drain rapidly down holes or flow rapidly down hillsides. Normal "flowing" behaviour is unchanged so even though this mod is just moving cubes around the surface of the water looks reasonably smooth. The ABM only runs for blocks that are adjacent to flowing nodes and does very few calculations so it's also reasonably lightweight - I'm essentially borrowing the engine's built-in liquid flow detection to determine when my own code needs to run.
|
||||
|
||||
Each type of liquid can have this behaviour enabled independently of each other. By default lava is configured to flow more slowly than water, but this can be changed in the mod's settings.
|
||||
|
||||
## Damp Clay Counters Ocean Loss
|
||||
|
||||
If basic water is set as dynamic, it loses its "renewability" - water nodes don't replicate like they normally do. If you are concerned about the global water level dropping over time as water pours down into undersea caverns, or just generally don't like the idea of water being a finite resource (even with an ocean's worth to start with), this mod includes an optional feature that turns natural clay deposits into "springs". Clay deposits get an Active Block Modifier that checks if there's air or shallow water above them and refills them with newly spawned water source blocks.
|
||||
|
||||
If this clay is dug out by players and reconstituted into clay blocks elsewhere, the "spring" behaviour won't occur for these - it only happens for clay spawned by mapgen while this feature is enabled. (In technical terms, when clay springs are enabled the map generator replaces generated clay with an otherwise identical "damp clay" node type that drops regular clay when dug. The ABM only affects this "damp clay" node type.)
|
||||
|
||||
There's also a "spring" block that's available only via the creative menu or /give command. This block has an ABM that ensures there's always a single block of water generated directly above it, regardless of where the block is placed.
|
||||
|
||||
Note that other than this clay spring feature and the override of water's "renewability" this mod doesn't affect any default node definitions, so any other mods dealing with liquids should still function as they did before. If you disable any of the liquid types they will just "freeze" in whatever position they were in at the time.
|
||||
|
||||
## "Flow-through" Blocks
|
||||
|
||||
There are many blocks in the game that one would think should not serve as an impenetrable barrier to water flow, but which do. For example: metal bars, grass, leaves. The "flow-through" feature of this mod makes them permeable, allowing water_source and lava_source blocks to "teleport" from one side of a flow-through block to the other. Liquid source blocks will only teleport through a single layer of flow-through node and only in the cardinal directions.
|
||||
|
||||
Other mods can mark blocks as flow-through by adding them to the "flow_through" group.
|
||||
|
||||
## Lava/Water Interaction
|
||||
|
||||
Due to the way default lava/water interaction works a single block of lava_source would be able to wander endlessly around on the surface of the ocean turning the uppermost layer of water into stone. Similarly, a single block of water_source could wander around on the surface of a lava pool turning the uppermost layer into obsidian. This is an undesirable outcome so the dynamic_liquid mod includes a more sophisticated replacement to the default lava-cooling ABM.
|
||||
|
||||
lava_flowing nodes are now eliminated entirely by nodes marked cools_lava, they do not leave behind default:stone. This means that infinite stone generation is no longer possible using arrangements of lava and water.
|
||||
|
||||
lava_source nodes are turned into obsidian only by water_source nodes; water_flowing nodes are eliminated by lava_source nodes without affecting them. The water_source node is destroyed in the process. This means that if you want to cool a large amount of lava you're going to need a correspondingly large amount of water.
|
||||
|
||||
For modders, you can now fine-tune the effects of blocks on lava (and vice versa) using the following groups:
|
||||
|
||||
* "group:dynamic_cools_lava_flowing" - causes lava_flowing nodes to vanish. The old "group:cools_lava" has this effect.
|
||||
* "group:dynamic_lava_flowing_destroys" - causes lava_flowing nodes to destroy nodes belonging to this group.
|
||||
* "group:dynamic_cools_lava_source" - causes lava_source nodes to turn into obsidian. The old "group:cools_lava" does *not* have this effect.
|
||||
* "group:dynamic_lava_source_destroys" - causes lava_source nodes to destroy nodes belonging to this group.
|
224
mods/dynamic_liquid/cooling_lava.lua
Normal file
@ -0,0 +1,224 @@
|
||||
if not minetest.get_modpath("default") then return end
|
||||
|
||||
local new_lava_cooling = minetest.settings:get_bool("dynamic_liquid_new_lava_cooling", true)
|
||||
if not new_lava_cooling then return end
|
||||
|
||||
local falling_obsidian = minetest.settings:get_bool("dynamic_liquid_falling_obsidian", false)
|
||||
|
||||
-- The existing cool_lava ABM is hard-coded to respond to water nodes
|
||||
-- and overriding node groups doesn't appear to work:
|
||||
-- https://github.com/minetest/minetest/issues/5518
|
||||
-- So modifying the lava-cooling system requires some unfortunate
|
||||
-- workarounds. Most notable is the "dynamic_cools_lava" group;
|
||||
-- the "cools_lava" group can't be removed from flowing water for ABM
|
||||
-- purposes so this redundancy is required.
|
||||
|
||||
-- Mods can hook into this system by adding the group
|
||||
-- "dynamic_cools_lava_flowing" and/or "dynamic_cools_lava_source"
|
||||
-- to nodes that should cool lava and
|
||||
-- "dynamic_lava_flowing_destroys" and/or "dynamic_lava_source_destroys"
|
||||
-- to nodes that should be destroyed by proximity to lava.
|
||||
|
||||
local particles = minetest.settings:get_bool("enable_particles", true)
|
||||
|
||||
local steam = function(pos)
|
||||
if particles then
|
||||
minetest.add_particlespawner({
|
||||
amount = 6,
|
||||
time = 1,
|
||||
minpos = pos,
|
||||
maxpos = pos,
|
||||
minvel = {x=-2, y=0, z=-2},
|
||||
maxvel = {x=2, y=1, z=2},
|
||||
minacc = {x=0, y=2, z=0},
|
||||
maxacc = {x=0, y=2, z=0},
|
||||
minexptime = 1,
|
||||
maxexptime = 4,
|
||||
minsize = 16,
|
||||
maxsize = 16,
|
||||
collisiondetection = false,
|
||||
vertical = false,
|
||||
texture = "smoke_puff.png",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
default.cool_lava = function(pos, node)
|
||||
-- no-op disables default cooling ABM
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
local dynamic_cools_lava_flowing = {"group:dynamic_cools_lava_flowing", "group:cools_lava"}
|
||||
|
||||
-- Flowing lava will turn these blocks into steam.
|
||||
local dynamic_lava_flowing_destroys = {
|
||||
"group:dynamic_lava_flowing_destroys",
|
||||
"default:water_flowing",
|
||||
"default:river_water_flowing",
|
||||
"default:snow",
|
||||
"default:snowblock"
|
||||
}
|
||||
|
||||
local all_flowing_nodes = {unpack(dynamic_cools_lava_flowing)}
|
||||
for i = 1, #dynamic_lava_flowing_destroys do
|
||||
all_flowing_nodes[#dynamic_cools_lava_flowing + i] = dynamic_lava_flowing_destroys[i]
|
||||
end
|
||||
|
||||
local cool_lava_flowing = function(pos, node)
|
||||
local cooler_adjacent = minetest.find_node_near(pos, 1, dynamic_cools_lava_flowing)
|
||||
|
||||
if cooler_adjacent ~= nil then
|
||||
-- pulling nearby sources into position is necessary to break certain classes of
|
||||
-- flow "deadlock". Weird, but what're you gonna do.
|
||||
local nearby_source = minetest.find_node_near(pos, 1, "default:lava_source")
|
||||
if nearby_source then
|
||||
minetest.set_node(pos, {name="default:lava_source"})
|
||||
minetest.set_node(nearby_source, {name="air"})
|
||||
steam(nearby_source)
|
||||
else
|
||||
minetest.set_node(pos, {name = "air"})
|
||||
steam(pos)
|
||||
end
|
||||
end
|
||||
|
||||
local evaporate_list = minetest.find_nodes_in_area(
|
||||
vector.add(pos,{x=-1, y=-1, z=-1}),
|
||||
vector.add(pos,{x=1, y=1, z=1}),
|
||||
dynamic_lava_flowing_destroys
|
||||
)
|
||||
for _, loc in pairs(evaporate_list) do
|
||||
minetest.set_node(loc, {name="air"})
|
||||
steam(loc)
|
||||
end
|
||||
|
||||
minetest.sound_play("default_cool_lava",
|
||||
{pos = pos, max_hear_distance = 16, gain = 0.25})
|
||||
end
|
||||
|
||||
minetest.register_abm({
|
||||
label = "Lava flowing cooling",
|
||||
nodenames = {"default:lava_flowing"},
|
||||
neighbors = all_flowing_nodes,
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(...)
|
||||
cool_lava_flowing(...)
|
||||
end,
|
||||
})
|
||||
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
local dynamic_cools_lava_source = {"group:dynamic_cools_lava_source"}
|
||||
for name, node_def in pairs(minetest.registered_nodes) do
|
||||
-- We don't want "flowing" nodes to cool lava source blocks, otherwise when water falls onto a large pool of lava there's
|
||||
-- way too many blocks turned to obsidian.
|
||||
if minetest.get_item_group(name, "cools_lava") > 0 and name ~= "default:water_flowing" and name ~= "default:river_water_flowing" then
|
||||
table.insert(dynamic_cools_lava_source, name)
|
||||
end
|
||||
end
|
||||
|
||||
-- lava source blocks will turn these blocks into steam.
|
||||
local dynamic_lava_source_destroys = {
|
||||
"group:dynamic_lava_source_destroys",
|
||||
"default:water_source",
|
||||
"default:river_water_source",
|
||||
"default:water_flowing",
|
||||
"default:river_water_flowing",
|
||||
"default:ice",
|
||||
"default:snow",
|
||||
"default:snowblock"
|
||||
}
|
||||
|
||||
local all_source_nodes = {unpack(dynamic_cools_lava_source)}
|
||||
for i = 1, #dynamic_lava_source_destroys do
|
||||
all_source_nodes[#dynamic_cools_lava_source + i] = dynamic_lava_source_destroys[i]
|
||||
end
|
||||
|
||||
local is_pos_in_list = function(pos, list)
|
||||
for _, loc in pairs(list) do
|
||||
if vector.equals(pos, loc) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function shuffle_array(a)
|
||||
local rand = math.random
|
||||
local iterations = #a
|
||||
local j
|
||||
|
||||
for i = iterations, 2, -1 do
|
||||
j = rand(i)
|
||||
a[i], a[j] = a[j], a[i]
|
||||
end
|
||||
end
|
||||
|
||||
local cool_lava_source = function(pos, node)
|
||||
local cooler_list = minetest.find_nodes_in_area(
|
||||
vector.add(pos,{x=-1, y=-1, z=-1}),
|
||||
vector.add(pos,{x=1, y=1, z=1}),
|
||||
dynamic_cools_lava_source)
|
||||
local evaporate_list = minetest.find_nodes_in_area(
|
||||
vector.add(pos,{x=-1, y=-1, z=-1}),
|
||||
vector.add(pos,{x=1, y=1, z=1}),
|
||||
dynamic_lava_source_destroys
|
||||
)
|
||||
|
||||
shuffle_array(cooler_list)
|
||||
|
||||
local obsidian_location = nil
|
||||
for _, loc in pairs(cooler_list) do
|
||||
if is_pos_in_list(loc, evaporate_list) then
|
||||
if loc.y < pos.y then
|
||||
if loc.z == pos.z and loc.x == pos.x then
|
||||
obsidian_location = loc -- best outcome, directly below us. End loop immediately.
|
||||
break
|
||||
else
|
||||
obsidian_location = loc -- next-best outcome, in the tier below us.
|
||||
end
|
||||
elseif loc.y == pos.y and obsidian_location == nil then
|
||||
obsidian_location = loc -- On the same level as us, acceptable if nothing else comes along.
|
||||
end
|
||||
end
|
||||
end
|
||||
if obsidian_location == nil and #cooler_list > 0 then
|
||||
obsidian_location = pos -- there's an adjacent cooler node, but it's above us. Turn into obsidian in place.
|
||||
end
|
||||
|
||||
for _, loc in pairs(evaporate_list) do
|
||||
minetest.set_node(loc, {name="air"})
|
||||
steam(loc)
|
||||
end
|
||||
|
||||
if obsidian_location ~= nil then
|
||||
minetest.set_node(pos, {name = "air"})
|
||||
minetest.set_node(obsidian_location, {name = "default:obsidian"})
|
||||
if minetest.spawn_falling_node and falling_obsidian then -- TODO cutting-edge dev function, so check if it exists for the time being. Remove check when 0.4.16 is released.
|
||||
minetest.spawn_falling_node(obsidian_location)
|
||||
end
|
||||
elseif #evaporate_list > 0 then
|
||||
-- Again, this weird bit is necessary for breaking certain types of flow deadlock
|
||||
local loc = evaporate_list[math.random(1,#evaporate_list)]
|
||||
if loc.y <= pos.y then
|
||||
minetest.set_node(pos, {name = "air"})
|
||||
minetest.set_node(loc, {name = "default:lava_source"})
|
||||
end
|
||||
end
|
||||
|
||||
minetest.sound_play("default_cool_lava",
|
||||
{pos = pos, max_hear_distance = 16, gain = 0.25})
|
||||
end
|
||||
|
||||
|
||||
minetest.register_abm({
|
||||
label = "Lava source cooling",
|
||||
nodenames = {"default:lava_source"},
|
||||
neighbors = all_source_nodes,
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(...)
|
||||
cool_lava_source(...)
|
||||
end,
|
||||
})
|
5
mods/dynamic_liquid/depends.txt
Normal file
@ -0,0 +1,5 @@
|
||||
default?
|
||||
intllib?
|
||||
doc?
|
||||
xpanes?
|
||||
carts?
|
4
mods/dynamic_liquid/description.txt
Normal file
@ -0,0 +1,4 @@
|
||||
Flowing dynamic liquids and ocean-maintenance springs. v0.5
|
||||
|
||||
Github: https://github.com/FaceDeer/dynamic_liquid/
|
||||
Forum: https://forum.minetest.net/viewtopic.php?t=16485
|
421
mods/dynamic_liquid/i18n.py
Normal file
@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Script to generate the template file and update the translation files.
|
||||
# Copy the script into the mod or modpack root folder and run it there.
|
||||
#
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
#
|
||||
# See https://github.com/minetest-tools/update_translations for
|
||||
# potential future updates to this script.
|
||||
|
||||
from __future__ import print_function
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
|
||||
# Running params
|
||||
params = {"recursive": False,
|
||||
"help": False,
|
||||
"mods": False,
|
||||
"verbose": False,
|
||||
"folders": []
|
||||
}
|
||||
# Available CLI options
|
||||
options = {"recursive": ['--recursive', '-r'],
|
||||
"help": ['--help', '-h'],
|
||||
"mods": ['--installed-mods'],
|
||||
"verbose": ['--verbose', '-v']
|
||||
}
|
||||
|
||||
# Strings longer than this will have extra space added between
|
||||
# them in the translation files to make it easier to distinguish their
|
||||
# beginnings and endings at a glance
|
||||
doublespace_threshold = 60
|
||||
|
||||
def set_params_folders(tab: list):
|
||||
'''Initialize params["folders"] from CLI arguments.'''
|
||||
# Discarding argument 0 (tool name)
|
||||
for param in tab[1:]:
|
||||
stop_param = False
|
||||
for option in options:
|
||||
if param in options[option]:
|
||||
stop_param = True
|
||||
break
|
||||
if not stop_param:
|
||||
params["folders"].append(os.path.abspath(param))
|
||||
|
||||
def set_params(tab: list):
|
||||
'''Initialize params from CLI arguments.'''
|
||||
for option in options:
|
||||
for option_name in options[option]:
|
||||
if option_name in tab:
|
||||
params[option] = True
|
||||
break
|
||||
|
||||
def print_help(name):
|
||||
'''Prints some help message.'''
|
||||
print(f'''SYNOPSIS
|
||||
{name} [OPTIONS] [PATHS...]
|
||||
DESCRIPTION
|
||||
{', '.join(options["help"])}
|
||||
prints this help message
|
||||
{', '.join(options["recursive"])}
|
||||
run on all subfolders of paths given
|
||||
{', '.join(options["mods"])}
|
||||
run on locally installed modules
|
||||
{', '.join(options["verbose"])}
|
||||
add output information
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
'''Main function'''
|
||||
set_params(_argv)
|
||||
set_params_folders(_argv)
|
||||
if params["help"]:
|
||||
print_help(_argv[0])
|
||||
elif params["recursive"] and params["mods"]:
|
||||
print("Option --installed-mods is incompatible with --recursive")
|
||||
else:
|
||||
# Add recursivity message
|
||||
print("Running ", end='')
|
||||
if params["recursive"]:
|
||||
print("recursively ", end='')
|
||||
# Running
|
||||
if params["mods"]:
|
||||
print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}")
|
||||
run_all_subfolders("~/.minetest/mods")
|
||||
elif len(params["folders"]) >= 2:
|
||||
print("on folder list:", params["folders"])
|
||||
for f in params["folders"]:
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(f)
|
||||
else:
|
||||
update_folder(f)
|
||||
elif len(params["folders"]) == 1:
|
||||
print("on folder", params["folders"][0])
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(params["folders"][0])
|
||||
else:
|
||||
update_folder(params["folders"][0])
|
||||
else:
|
||||
print("on folder", os.path.abspath("./"))
|
||||
if params["recursive"]:
|
||||
run_all_subfolders(os.path.abspath("./"))
|
||||
else:
|
||||
update_folder(os.path.abspath("./"))
|
||||
|
||||
#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
|
||||
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
|
||||
pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
|
||||
pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
|
||||
|
||||
# Handles "concatenation" .. " of strings"
|
||||
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)
|
||||
|
||||
pattern_tr = re.compile(r'(.+?[^@])=(.*)')
|
||||
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
|
||||
pattern_tr_filename = re.compile(r'\.tr$')
|
||||
pattern_po_language_code = re.compile(r'(.*)\.po$')
|
||||
|
||||
#attempt to read the mod's name from the mod.conf file. Returns None on failure
|
||||
def get_modname(folder):
|
||||
try:
|
||||
with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
|
||||
for line in mod_conf:
|
||||
match = pattern_name.match(line)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
#If there are already .tr files in /locale, returns a list of their names
|
||||
def get_existing_tr_files(folder):
|
||||
out = []
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
if pattern_tr_filename.search(name):
|
||||
out.append(name)
|
||||
return out
|
||||
|
||||
# A series of search and replaces that massage a .po file's contents into
|
||||
# a .tr file's equivalent
|
||||
def process_po_file(text):
|
||||
# The first three items are for unused matches
|
||||
text = re.sub(r'#~ msgid "', "", text)
|
||||
text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\n#~ msgstr "', "=", text)
|
||||
# comment lines
|
||||
text = re.sub(r'#.*\n', "", text)
|
||||
# converting msg pairs into "=" pairs
|
||||
text = re.sub(r'msgid "', "", text)
|
||||
text = re.sub(r'"\nmsgstr ""\n"', "=", text)
|
||||
text = re.sub(r'"\nmsgstr "', "=", text)
|
||||
# various line breaks and escape codes
|
||||
text = re.sub(r'"\n"', "", text)
|
||||
text = re.sub(r'"\n', "\n", text)
|
||||
text = re.sub(r'\\"', '"', text)
|
||||
text = re.sub(r'\\n', '@n', text)
|
||||
# remove header text
|
||||
text = re.sub(r'=Project-Id-Version:.*\n', "", text)
|
||||
# remove double-spaced lines
|
||||
text = re.sub(r'\n\n', '\n', text)
|
||||
return text
|
||||
|
||||
# Go through existing .po files and, if a .tr file for that language
|
||||
# *doesn't* exist, convert it and create it.
|
||||
# The .tr file that results will subsequently be reprocessed so
|
||||
# any "no longer used" strings will be preserved.
|
||||
# Note that "fuzzy" tags will be lost in this process.
|
||||
def process_po_files(folder, modname):
|
||||
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
|
||||
for name in files:
|
||||
code_match = pattern_po_language_code.match(name)
|
||||
if code_match == None:
|
||||
continue
|
||||
language_code = code_match.group(1)
|
||||
tr_name = modname + "." + language_code + ".tr"
|
||||
tr_file = os.path.join(root, tr_name)
|
||||
if os.path.exists(tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"{tr_name} already exists, ignoring {name}")
|
||||
continue
|
||||
fname = os.path.join(root, name)
|
||||
with open(fname, "r", encoding='utf-8') as po_file:
|
||||
if params["verbose"]:
|
||||
print(f"Importing translations from {name}")
|
||||
text = process_po_file(po_file.read())
|
||||
with open(tr_file, "wt", encoding='utf-8') as tr_out:
|
||||
tr_out.write(text)
|
||||
|
||||
# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
|
||||
# Creates a directory if it doesn't exist, silently does
|
||||
# nothing if it already exists
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc: # Python >2.5
|
||||
if exc.errno == errno.EEXIST and os.path.isdir(path):
|
||||
pass
|
||||
else: raise
|
||||
|
||||
# Converts the template dictionary to a text to be written as a file
|
||||
# dKeyStrings is a dictionary of localized string to source file sets
|
||||
# dOld is a dictionary of existing translations and comments from
|
||||
# the previous version of this text
|
||||
def strings_to_text(dkeyStrings, dOld, mod_name):
|
||||
lOut = [f"# textdomain: {mod_name}\n"]
|
||||
|
||||
dGroupedBySource = {}
|
||||
|
||||
for key in dkeyStrings:
|
||||
sourceList = list(dkeyStrings[key])
|
||||
sourceList.sort()
|
||||
sourceString = "\n".join(sourceList)
|
||||
listForSource = dGroupedBySource.get(sourceString, [])
|
||||
listForSource.append(key)
|
||||
dGroupedBySource[sourceString] = listForSource
|
||||
|
||||
lSourceKeys = list(dGroupedBySource.keys())
|
||||
lSourceKeys.sort()
|
||||
for source in lSourceKeys:
|
||||
localizedStrings = dGroupedBySource[source]
|
||||
localizedStrings.sort()
|
||||
lOut.append("")
|
||||
lOut.append(source)
|
||||
lOut.append("")
|
||||
for localizedString in localizedStrings:
|
||||
val = dOld.get(localizedString, {})
|
||||
translation = val.get("translation", "")
|
||||
comment = val.get("comment")
|
||||
if len(localizedString) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{localizedString}={translation}")
|
||||
if len(localizedString) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
|
||||
|
||||
unusedExist = False
|
||||
for key in dOld:
|
||||
if key not in dkeyStrings:
|
||||
val = dOld[key]
|
||||
translation = val.get("translation")
|
||||
comment = val.get("comment")
|
||||
# only keep an unused translation if there was translated
|
||||
# text or a comment associated with it
|
||||
if translation != None and (translation != "" or comment):
|
||||
if not unusedExist:
|
||||
unusedExist = True
|
||||
lOut.append("\n\n##### not used anymore #####\n")
|
||||
if len(key) > doublespace_threshold and not lOut[-1] == "":
|
||||
lOut.append("")
|
||||
if comment != None:
|
||||
lOut.append(comment)
|
||||
lOut.append(f"{key}={translation}")
|
||||
if len(key) > doublespace_threshold:
|
||||
lOut.append("")
|
||||
return "\n".join(lOut) + '\n'
|
||||
|
||||
# Writes a template.txt file
|
||||
# dkeyStrings is the dictionary returned by generate_template
|
||||
def write_template(templ_file, dkeyStrings, mod_name):
|
||||
# read existing template file to preserve comments
|
||||
existing_template = import_tr_file(templ_file)
|
||||
|
||||
text = strings_to_text(dkeyStrings, existing_template[0], mod_name)
|
||||
mkdir_p(os.path.dirname(templ_file))
|
||||
with open(templ_file, "wt", encoding='utf-8') as template_file:
|
||||
template_file.write(text)
|
||||
|
||||
|
||||
# Gets all translatable strings from a lua file
|
||||
def read_lua_file_strings(lua_file):
|
||||
lOut = []
|
||||
with open(lua_file, encoding='utf-8') as text_file:
|
||||
text = text_file.read()
|
||||
#TODO remove comments here
|
||||
|
||||
text = re.sub(pattern_concat, "", text)
|
||||
|
||||
strings = []
|
||||
for s in pattern_lua.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed.findall(text):
|
||||
strings.append(s)
|
||||
|
||||
for s in strings:
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=0-9]", "@@", s)
|
||||
s = s.replace('\\"', '"')
|
||||
s = s.replace("\\'", "'")
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
return lOut
|
||||
|
||||
# Gets strings from an existing translation file
|
||||
# returns both a dictionary of translations
|
||||
# and the full original source text so that the new text
|
||||
# can be compared to it for changes.
|
||||
def import_tr_file(tr_file):
|
||||
dOut = {}
|
||||
text = None
|
||||
if os.path.exists(tr_file):
|
||||
with open(tr_file, "r", encoding='utf-8') as existing_file :
|
||||
# save the full text to allow for comparison
|
||||
# of the old version with the new output
|
||||
text = existing_file.read()
|
||||
existing_file.seek(0)
|
||||
# a running record of the current comment block
|
||||
# we're inside, to allow preceeding multi-line comments
|
||||
# to be retained for a translation line
|
||||
latest_comment_block = None
|
||||
for line in existing_file.readlines():
|
||||
line = line.rstrip('\n')
|
||||
if line[:3] == "###":
|
||||
# Reset comment block if we hit a header
|
||||
latest_comment_block = None
|
||||
continue
|
||||
if line[:1] == "#":
|
||||
# Save the comment we're inside
|
||||
if not latest_comment_block:
|
||||
latest_comment_block = line
|
||||
else:
|
||||
latest_comment_block = latest_comment_block + "\n" + line
|
||||
continue
|
||||
match = pattern_tr.match(line)
|
||||
if match:
|
||||
# this line is a translated line
|
||||
outval = {}
|
||||
outval["translation"] = match.group(2)
|
||||
if latest_comment_block:
|
||||
# if there was a comment, record that.
|
||||
outval["comment"] = latest_comment_block
|
||||
latest_comment_block = None
|
||||
dOut[match.group(1)] = outval
|
||||
return (dOut, text)
|
||||
|
||||
# Walks all lua files in the mod folder, collects translatable strings,
|
||||
# and writes it to a template.txt file
|
||||
# Returns a dictionary of localized strings to source file sets
|
||||
# that can be used with the strings_to_text function.
|
||||
def generate_template(folder, mod_name):
|
||||
dOut = {}
|
||||
for root, dirs, files in os.walk(folder):
|
||||
for name in files:
|
||||
if fnmatch.fnmatch(name, "*.lua"):
|
||||
fname = os.path.join(root, name)
|
||||
found = read_lua_file_strings(fname)
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
for s in found:
|
||||
sources = dOut.get(s, set())
|
||||
sources.add(f"### {os.path.basename(fname)} ###")
|
||||
dOut[s] = sources
|
||||
|
||||
if len(dOut) == 0:
|
||||
return None
|
||||
templ_file = os.path.join(folder, "locale/template.txt")
|
||||
write_template(templ_file, dOut, mod_name)
|
||||
return dOut
|
||||
|
||||
# Updates an existing .tr file, copying the old one to a ".old" file
|
||||
# if any changes have happened
|
||||
# dNew is the data used to generate the template, it has all the
|
||||
# currently-existing localized strings
|
||||
def update_tr_file(dNew, mod_name, tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"updating {tr_file}")
|
||||
|
||||
tr_import = import_tr_file(tr_file)
|
||||
dOld = tr_import[0]
|
||||
textOld = tr_import[1]
|
||||
|
||||
textNew = strings_to_text(dNew, dOld, mod_name)
|
||||
|
||||
if textOld and textOld != textNew:
|
||||
print(f"{tr_file} has changed.")
|
||||
shutil.copyfile(tr_file, f"{tr_file}.old")
|
||||
|
||||
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
|
||||
new_tr_file.write(textNew)
|
||||
|
||||
# Updates translation files for the mod in the given folder
|
||||
def update_mod(folder):
|
||||
modname = get_modname(folder)
|
||||
if modname is not None:
|
||||
process_po_files(folder, modname)
|
||||
print(f"Updating translations for {modname}")
|
||||
data = generate_template(folder, modname)
|
||||
if data == None:
|
||||
print(f"No translatable strings found in {modname}")
|
||||
else:
|
||||
for tr_file in get_existing_tr_files(folder):
|
||||
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
|
||||
else:
|
||||
print("Unable to find modname in folder " + folder)
|
||||
|
||||
# Determines if the folder being pointed to is a mod or a mod pack
|
||||
# and then runs update_mod accordingly
|
||||
def update_folder(folder):
|
||||
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
|
||||
if is_modpack:
|
||||
subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
|
||||
for subfolder in subfolders:
|
||||
update_mod(subfolder + "/")
|
||||
else:
|
||||
update_mod(folder)
|
||||
print("Done.")
|
||||
|
||||
def run_all_subfolders(folder):
|
||||
for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]:
|
||||
update_folder(modfolder + "/")
|
||||
|
||||
|
||||
main()
|
567
mods/dynamic_liquid/init.lua
Normal file
@ -0,0 +1,567 @@
|
||||
dynamic_liquid = {} -- global table to expose liquid_abm for other mods' usage
|
||||
|
||||
dynamic_liquid.registered_liquids = {} -- used by the flow-through node abm
|
||||
dynamic_liquid.registered_liquid_neighbors = {}
|
||||
|
||||
local water_level = tonumber(minetest.get_mapgen_setting("water_level"))
|
||||
|
||||
local modname = minetest.get_current_modname()
|
||||
local modpath = minetest.get_modpath(modname)
|
||||
local S = minetest.get_translator(modname)
|
||||
|
||||
dofile(modpath.."/cooling_lava.lua")
|
||||
|
||||
-- By making this giant table of all possible permutations of horizontal direction we can avoid
|
||||
-- lots of redundant calculations.
|
||||
|
||||
local all_direction_permutations = {
|
||||
{{x=0,z=1},{x=0,z=-1},{x=1,z=0},{x=-1,z=0}},
|
||||
{{x=0,z=1},{x=0,z=-1},{x=-1,z=0},{x=1,z=0}},
|
||||
{{x=0,z=1},{x=1,z=0},{x=0,z=-1},{x=-1,z=0}},
|
||||
{{x=0,z=1},{x=1,z=0},{x=-1,z=0},{x=0,z=-1}},
|
||||
{{x=0,z=1},{x=-1,z=0},{x=0,z=-1},{x=1,z=0}},
|
||||
{{x=0,z=1},{x=-1,z=0},{x=1,z=0},{x=0,z=-1}},
|
||||
{{x=0,z=-1},{x=0,z=1},{x=-1,z=0},{x=1,z=0}},
|
||||
{{x=0,z=-1},{x=0,z=1},{x=1,z=0},{x=-1,z=0}},
|
||||
{{x=0,z=-1},{x=1,z=0},{x=-1,z=0},{x=0,z=1}},
|
||||
{{x=0,z=-1},{x=1,z=0},{x=0,z=1},{x=-1,z=0}},
|
||||
{{x=0,z=-1},{x=-1,z=0},{x=1,z=0},{x=0,z=1}},
|
||||
{{x=0,z=-1},{x=-1,z=0},{x=0,z=1},{x=1,z=0}},
|
||||
{{x=1,z=0},{x=0,z=1},{x=0,z=-1},{x=-1,z=0}},
|
||||
{{x=1,z=0},{x=0,z=1},{x=-1,z=0},{x=0,z=-1}},
|
||||
{{x=1,z=0},{x=0,z=-1},{x=0,z=1},{x=-1,z=0}},
|
||||
{{x=1,z=0},{x=0,z=-1},{x=-1,z=0},{x=0,z=1}},
|
||||
{{x=1,z=0},{x=-1,z=0},{x=0,z=1},{x=0,z=-1}},
|
||||
{{x=1,z=0},{x=-1,z=0},{x=0,z=-1},{x=0,z=1}},
|
||||
{{x=-1,z=0},{x=0,z=1},{x=1,z=0},{x=0,z=-1}},
|
||||
{{x=-1,z=0},{x=0,z=1},{x=0,z=-1},{x=1,z=0}},
|
||||
{{x=-1,z=0},{x=0,z=-1},{x=1,z=0},{x=0,z=1}},
|
||||
{{x=-1,z=0},{x=0,z=-1},{x=0,z=1},{x=1,z=0}},
|
||||
{{x=-1,z=0},{x=1,z=0},{x=0,z=-1},{x=0,z=1}},
|
||||
{{x=-1,z=0},{x=1,z=0},{x=0,z=1},{x=0,z=-1}},
|
||||
}
|
||||
|
||||
local get_node = minetest.get_node
|
||||
local set_node = minetest.swap_node
|
||||
|
||||
-- Dynamic liquids
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
local disable_flow_above = tonumber(minetest.settings:get("dynamic_liquid_disable_flow_above"))
|
||||
|
||||
if disable_flow_above == nil or disable_flow_above >= 31000 then
|
||||
|
||||
-- version without altitude check
|
||||
dynamic_liquid.liquid_abm = function(liquid, flowing_liquid, chance)
|
||||
minetest.register_abm({
|
||||
label = "dynamic_liquid " .. liquid .. " and " .. flowing_liquid,
|
||||
nodenames = {liquid},
|
||||
neighbors = {flowing_liquid},
|
||||
interval = 1,
|
||||
chance = chance or 1,
|
||||
catch_up = false,
|
||||
action = function(pos,node) -- Do everything possible to optimize this method
|
||||
local check_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
||||
local check_node = get_node(check_pos)
|
||||
local check_node_name = check_node.name
|
||||
if check_node_name == flowing_liquid or check_node_name == "air" then
|
||||
set_node(pos, check_node)
|
||||
set_node(check_pos, node)
|
||||
return
|
||||
end
|
||||
local perm = all_direction_permutations[math.random(24)]
|
||||
local dirs -- declare outside of loop so it won't keep entering/exiting scope
|
||||
for i=1,4 do
|
||||
dirs = perm[i]
|
||||
-- reuse check_pos to avoid allocating a new table
|
||||
check_pos.x = pos.x + dirs.x
|
||||
check_pos.y = pos.y
|
||||
check_pos.z = pos.z + dirs.z
|
||||
check_node = get_node(check_pos)
|
||||
check_node_name = check_node.name
|
||||
if check_node_name == flowing_liquid or check_node_name == "air" then
|
||||
set_node(pos, check_node)
|
||||
set_node(check_pos, node)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
dynamic_liquid.registered_liquids[liquid] = flowing_liquid
|
||||
table.insert(dynamic_liquid.registered_liquid_neighbors, liquid)
|
||||
end
|
||||
|
||||
else
|
||||
-- version with altitude check
|
||||
dynamic_liquid.liquid_abm = function(liquid, flowing_liquid, chance)
|
||||
minetest.register_abm({
|
||||
label = "dynamic_liquid " .. liquid .. " and " .. flowing_liquid .. " with altitude check",
|
||||
nodenames = {liquid},
|
||||
neighbors = {flowing_liquid},
|
||||
interval = 1,
|
||||
chance = chance or 1,
|
||||
catch_up = false,
|
||||
action = function(pos,node) -- Do everything possible to optimize this method
|
||||
-- This altitude check is the only difference from the version above.
|
||||
-- If the altitude check is disabled we don't ever need to make the comparison,
|
||||
-- hence the two different versions.
|
||||
if pos.y > disable_flow_above then
|
||||
return
|
||||
end
|
||||
local check_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
||||
local check_node = get_node(check_pos)
|
||||
local check_node_name = check_node.name
|
||||
if check_node_name == flowing_liquid or check_node_name == "air" then
|
||||
set_node(pos, check_node)
|
||||
set_node(check_pos, node)
|
||||
return
|
||||
end
|
||||
local perm = all_direction_permutations[math.random(24)]
|
||||
local dirs -- declare outside of loop so it won't keep entering/exiting scope
|
||||
for i=1,4 do
|
||||
dirs = perm[i]
|
||||
-- reuse check_pos to avoid allocating a new table
|
||||
check_pos.x = pos.x + dirs.x
|
||||
check_pos.y = pos.y
|
||||
check_pos.z = pos.z + dirs.z
|
||||
check_node = get_node(check_pos)
|
||||
check_node_name = check_node.name
|
||||
if check_node_name == flowing_liquid or check_node_name == "air" then
|
||||
set_node(pos, check_node)
|
||||
set_node(check_pos, node)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
dynamic_liquid.registered_liquids[liquid] = flowing_liquid
|
||||
table.insert(dynamic_liquid.registered_liquid_neighbors, liquid)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not minetest.get_modpath("default") then
|
||||
return
|
||||
end
|
||||
|
||||
local water = minetest.settings:get_bool("dynamic_liquid_water", true)
|
||||
local river_water = minetest.settings:get_bool("dynamic_liquid_river_water", false)
|
||||
local lava = minetest.settings:get_bool("dynamic_liquid_lava", true)
|
||||
|
||||
local water_probability = tonumber(minetest.settings:get("dynamic_liquid_water_flow_propability"))
|
||||
if water_probability == nil then
|
||||
water_probability = 1
|
||||
end
|
||||
|
||||
local river_water_probability = tonumber(minetest.settings:get("dynamic_liquid_river_water_flow_propability"))
|
||||
if river_water_probability == nil then
|
||||
river_water_probability = 1
|
||||
end
|
||||
|
||||
local lava_probability = tonumber(minetest.settings:get("dynamic_liquid_lava_flow_propability"))
|
||||
if lava_probability == nil then
|
||||
lava_probability = 5
|
||||
end
|
||||
|
||||
local springs = minetest.settings:get_bool("dynamic_liquid_springs", true)
|
||||
|
||||
if water then
|
||||
-- override water_source and water_flowing with liquid_renewable set to false
|
||||
local override_def = {liquid_renewable = false}
|
||||
minetest.override_item("default:water_source", override_def)
|
||||
minetest.override_item("default:water_flowing", override_def)
|
||||
end
|
||||
|
||||
if lava then
|
||||
dynamic_liquid.liquid_abm("default:lava_source", "default:lava_flowing", lava_probability)
|
||||
end
|
||||
if water then
|
||||
dynamic_liquid.liquid_abm("default:water_source", "default:water_flowing", water_probability)
|
||||
end
|
||||
if river_water then
|
||||
dynamic_liquid.liquid_abm("default:river_water_source", "default:river_water_flowing", river_water_probability)
|
||||
end
|
||||
|
||||
-- Flow-through nodes
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
local flow_through = minetest.settings:get_bool("dynamic_liquid_flow_through", true)
|
||||
|
||||
if flow_through then
|
||||
|
||||
local flow_through_directions = {
|
||||
{{x=1,z=0},{x=0,z=1}},
|
||||
{{x=0,z=1},{x=1,z=0}},
|
||||
}
|
||||
|
||||
minetest.register_abm({
|
||||
label = "dynamic_liquid flow-through",
|
||||
nodenames = {"group:flow_through", "group:leaves", "group:sapling", "group:grass", "group:dry_grass", "group:flora", "groups:rail", "groups:flower"},
|
||||
neighbors = dynamic_liquid.registered_liquid_neighbors,
|
||||
interval = 1,
|
||||
chance = 2, -- since liquid is teleported two nodes by this abm, halve the chance
|
||||
catch_up = false,
|
||||
action = function(pos)
|
||||
local source_pos = {x=pos.x, y=pos.y+1, z=pos.z}
|
||||
local dest_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
||||
local source_node = get_node(source_pos)
|
||||
local dest_node
|
||||
local source_flowing_node = dynamic_liquid.registered_liquids[source_node.name]
|
||||
local dest_flowing_node
|
||||
if source_flowing_node ~= nil then
|
||||
dest_node = minetest.get_node(dest_pos)
|
||||
if dest_node.name == source_flowing_node or dest_node.name == "air" then
|
||||
set_node(dest_pos, source_node)
|
||||
set_node(source_pos, dest_node)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local perm = flow_through_directions[math.random(2)]
|
||||
local dirs -- declare outside of loop so it won't keep entering/exiting scope
|
||||
for i=1,2 do
|
||||
dirs = perm[i]
|
||||
-- reuse to avoid allocating a new table
|
||||
source_pos.x = pos.x + dirs.x
|
||||
source_pos.y = pos.y
|
||||
source_pos.z = pos.z + dirs.z
|
||||
|
||||
dest_pos.x = pos.x - dirs.x
|
||||
dest_pos.y = pos.y
|
||||
dest_pos.z = pos.z - dirs.z
|
||||
|
||||
source_node = get_node(source_pos)
|
||||
dest_node = get_node(dest_pos)
|
||||
source_flowing_node = dynamic_liquid.registered_liquids[source_node.name]
|
||||
dest_flowing_node = dynamic_liquid.registered_liquids[dest_node.name]
|
||||
|
||||
if (source_flowing_node ~= nil and (dest_node.name == source_flowing_node or dest_node.name == "air")) or
|
||||
(dest_flowing_node ~= nil and (source_node.name == dest_flowing_node or source_node.name == "air"))
|
||||
then
|
||||
set_node(source_pos, dest_node)
|
||||
set_node(dest_pos, source_node)
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local add_flow_through = function(node_name)
|
||||
local node_def = minetest.registered_nodes[node_name]
|
||||
if node_def == nil then
|
||||
minetest.log("warning", "dynamic_liquid attempted to call add_flow_through on the node name "
|
||||
.. node_name .. ", which was not found in minetest.registered_nodes.")
|
||||
return
|
||||
end
|
||||
local new_groups = node_def.groups
|
||||
new_groups.flow_through = 1
|
||||
minetest.override_item(node_name,{groups = new_groups})
|
||||
end
|
||||
|
||||
if minetest.get_modpath("default") then
|
||||
for _, name in pairs({
|
||||
"default:apple",
|
||||
"default:papyrus",
|
||||
"default:dry_shrub",
|
||||
"default:bush_stem",
|
||||
"default:acacia_bush_stem",
|
||||
"default:sign_wall_wood",
|
||||
"default:sign_wall_steel",
|
||||
"default:ladder_wood",
|
||||
"default:ladder_steel",
|
||||
"default:fence_wood",
|
||||
"default:fence_acacia_wood",
|
||||
"default:fence_junglewood",
|
||||
"default:fence_pine_wood",
|
||||
"default:fence_aspen_wood",
|
||||
}) do
|
||||
add_flow_through(name)
|
||||
end
|
||||
end
|
||||
|
||||
if minetest.get_modpath("xpanes") then
|
||||
add_flow_through("xpanes:bar")
|
||||
add_flow_through("xpanes:bar_flat")
|
||||
end
|
||||
|
||||
if minetest.get_modpath("carts") then
|
||||
add_flow_through("carts:rail")
|
||||
add_flow_through("carts:powerrail")
|
||||
add_flow_through("carts:brakerail")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Springs
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
local function deep_copy(table_in)
|
||||
local table_out = {}
|
||||
for index, value in pairs(table_in) do
|
||||
if type(value) == "table" then
|
||||
table_out[index] = deep_copy(value)
|
||||
else
|
||||
table_out[index] = value
|
||||
end
|
||||
end
|
||||
return table_out
|
||||
end
|
||||
|
||||
local duplicate_def = function (name)
|
||||
local old_def = minetest.registered_nodes[name]
|
||||
return deep_copy(old_def)
|
||||
end
|
||||
|
||||
-- register damp clay whether we're going to set the ABM or not, if the user disables this feature we don't want existing
|
||||
-- spring clay to turn into unknown nodes.
|
||||
local clay_def = duplicate_def("default:clay")
|
||||
clay_def.description = S("Damp Clay")
|
||||
if not springs then
|
||||
clay_def.groups.not_in_creative_inventory = 1 -- take it out of creative inventory though
|
||||
end
|
||||
minetest.register_node("dynamic_liquid:clay", clay_def)
|
||||
|
||||
local data = {}
|
||||
|
||||
if springs then
|
||||
local c_clay = minetest.get_content_id("default:clay")
|
||||
local c_spring_clay = minetest.get_content_id("dynamic_liquid:clay")
|
||||
|
||||
-- Turn mapgen clay into spring clay
|
||||
minetest.register_on_generated(function(minp, maxp, seed)
|
||||
if minp.y >= water_level or maxp.y <= -15 then
|
||||
return
|
||||
end
|
||||
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
||||
vm:get_data(data)
|
||||
|
||||
for voxelpos, voxeldata in pairs(data) do
|
||||
if voxeldata == c_clay then
|
||||
data[voxelpos] = c_spring_clay
|
||||
end
|
||||
end
|
||||
vm:set_data(data)
|
||||
vm:write_to_map()
|
||||
end)
|
||||
|
||||
minetest.register_abm({
|
||||
label = "dynamic_liquid damp clay spring",
|
||||
nodenames = {"dynamic_liquid:clay"},
|
||||
neighbors = {"air", "default:water_source", "default:water_flowing"},
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(pos,node)
|
||||
local check_node
|
||||
local check_node_name
|
||||
while pos.y < water_level do
|
||||
pos.y = pos.y + 1
|
||||
check_node = get_node(pos)
|
||||
check_node_name = check_node.name
|
||||
if check_node_name == "air" or check_node_name == "default:water_flowing" then
|
||||
set_node(pos, {name="default:water_source"})
|
||||
elseif check_node_name ~= "default:water_source" then
|
||||
--Something's been put on top of this clay, don't send water through it
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
local spring_sounds = nil
|
||||
if default.node_sound_gravel_defaults ~= nil then
|
||||
spring_sounds = default.node_sound_gravel_defaults()
|
||||
elseif default.node_sound_sand_defaults ~= nil then
|
||||
spring_sounds = default.node_sound_dirt_defaults()
|
||||
end
|
||||
|
||||
-- This is a creative-mode only node that produces a modest amount of water continuously no matter where it is.
|
||||
-- Allow this one to turn into "unknown node" when this feature is disabled, since players had to explicitly place it.
|
||||
minetest.register_node("dynamic_liquid:spring", {
|
||||
description = S("Spring"),
|
||||
_doc_items_longdesc = S("A natural spring that generates an endless stream of water source blocks"),
|
||||
_doc_items_usagehelp = S("Generates one source block of water directly on top of itself once per second, provided the space is clear. If this natural spring is dug out the flow stops and it is turned into ordinary cobble."),
|
||||
drops = "default:gravel",
|
||||
tiles = {"default_cobble.png^[combine:16x80:0,-48=crack_anylength.png",
|
||||
"default_cobble.png","default_cobble.png","default_cobble.png","default_cobble.png","default_cobble.png",
|
||||
},
|
||||
is_ground_content = false,
|
||||
groups = {cracky = 3, stone = 2},
|
||||
sounds = spring_sounds,
|
||||
})
|
||||
|
||||
minetest.register_abm({
|
||||
label = "dynamic_liquid creative spring",
|
||||
nodenames = {"dynamic_liquid:spring"},
|
||||
neighbors = {"air", "default:water_flowing"},
|
||||
interval = 1,
|
||||
chance = 1,
|
||||
catch_up = false,
|
||||
action = function(pos,node)
|
||||
pos.y = pos.y + 1
|
||||
local check_node = get_node(pos)
|
||||
local check_node_name = check_node.name
|
||||
if check_node_name == "air" or check_node_name == "default:water_flowing" then
|
||||
set_node(pos, {name="default:water_source"})
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local mapgen_prefill = minetest.settings:get_bool("dynamic_liquid_mapgen_prefill", true)
|
||||
|
||||
local waternodes
|
||||
|
||||
if mapgen_prefill then
|
||||
local c_water = minetest.get_content_id("default:water_source")
|
||||
local c_air = minetest.get_content_id("air")
|
||||
waternodes = {}
|
||||
|
||||
local fill_to = function (vi, data, area)
|
||||
if area:containsi(vi) and area:position(vi).y <= water_level then
|
||||
if data[vi] == c_air then
|
||||
data[vi] = c_water
|
||||
table.insert(waternodes, vi)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- local count = 0
|
||||
local drop_liquid = function(vi, data, area, min_y)
|
||||
if data[vi] ~= c_water then
|
||||
-- we only care about water.
|
||||
return
|
||||
end
|
||||
local start = vi -- remember the water node we started from
|
||||
local ystride = area.ystride
|
||||
vi = vi - ystride
|
||||
if data[vi] ~= c_air then
|
||||
-- if there's no air below this water node, give up immediately.
|
||||
return
|
||||
end
|
||||
vi = vi - ystride -- There's air below the water, so move down one.
|
||||
while data[vi] == c_air and area:position(vi).y > min_y do
|
||||
-- the min_y check is here to ensure that we don't put water into the mapgen
|
||||
-- border zone below our current map chunk where it might get erased by future mapgen activity.
|
||||
-- if there's more air, keep going.
|
||||
vi = vi - ystride
|
||||
end
|
||||
vi = vi + ystride -- Move back up one. vi is now pointing at the last air node above the first non-air node.
|
||||
data[vi] = c_water
|
||||
data[start] = c_air
|
||||
-- count = count + 1
|
||||
-- if count % 100 == 0 then
|
||||
-- minetest.chat_send_all("dropped water " .. (start-vi)/ystride .. " at " .. minetest.pos_to_string(area:position(vi)))
|
||||
-- end
|
||||
end
|
||||
|
||||
minetest.register_on_generated(function(minp, maxp, seed)
|
||||
if minp.y > water_level then
|
||||
-- we're in the sky.
|
||||
return
|
||||
end
|
||||
|
||||
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
|
||||
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
|
||||
vm:get_data(data)
|
||||
local maxp_y = maxp.y
|
||||
local minp_y = minp.y
|
||||
|
||||
if maxp_y > -70 then
|
||||
local top = vector.new(maxp.x, math.min(maxp_y, water_level), maxp.z) -- prevents flood fill from affecting any water above sea level
|
||||
for vi in area:iterp(minp, top) do
|
||||
if data[vi] == c_water then
|
||||
table.insert(waternodes, vi)
|
||||
end
|
||||
end
|
||||
|
||||
while table.getn(waternodes) > 0 do
|
||||
local vi = table.remove(waternodes)
|
||||
local below = vi - area.ystride
|
||||
local left = vi - area.zstride
|
||||
local right = vi + area.zstride
|
||||
local front = vi - 1
|
||||
local back = vi + 1
|
||||
|
||||
fill_to(below, data, area)
|
||||
fill_to(left, data, area)
|
||||
fill_to(right, data, area)
|
||||
fill_to(front, data, area)
|
||||
fill_to(back, data, area)
|
||||
end
|
||||
else
|
||||
-- Caves sometimes generate with liquid nodes hovering in mid air.
|
||||
-- This immediately drops them straight down as far as they can go, reducing the ABM thrashing.
|
||||
-- We only iterate down to minp.y+1 because anything at minp.y will never be dropped farther anyway.
|
||||
for vi in area:iter(minp.x, minp_y+1, minp.z, maxp.x, maxp_y, maxp.z) do
|
||||
-- fortunately, area:iter iterates through y columns going upward. Just what we need!
|
||||
-- We could possibly be a bit more efficient by remembering how far we dropped then
|
||||
-- last liquid node in a column and moving stuff down that far,
|
||||
-- but for now let's keep it simple.
|
||||
drop_liquid(vi, data, area, minp_y)
|
||||
end
|
||||
end
|
||||
|
||||
vm:set_data(data)
|
||||
vm:write_to_map()
|
||||
vm:update_liquids()
|
||||
end)
|
||||
end
|
||||
|
||||
local displace_liquid = minetest.settings:get_bool("dynamic_liquid_displace_liquid", true)
|
||||
if displace_liquid then
|
||||
|
||||
local cardinal_dirs = {
|
||||
{x= 0, y=0, z= 1},
|
||||
{x= 1, y=0, z= 0},
|
||||
{x= 0, y=0, z=-1},
|
||||
{x=-1, y=0, z= 0},
|
||||
{x= 0, y=-1, z= 0},
|
||||
{x= 0, y=1, z= 0},
|
||||
}
|
||||
-- breadth-first search passing through liquid searching for air or flowing liquid.
|
||||
local flood_search_outlet = function(start_pos, source, flowing)
|
||||
local start_node = minetest.get_node(start_pos)
|
||||
local start_node_name = start_node.name
|
||||
if start_node_name == "air" or start_node_name == flowing then
|
||||
return start_pos
|
||||
end
|
||||
|
||||
local visited = {}
|
||||
visited[minetest.hash_node_position(start_pos)] = true
|
||||
local queue = {start_pos}
|
||||
local queue_pointer = 1
|
||||
|
||||
while #queue >= queue_pointer do
|
||||
local current_pos = queue[queue_pointer]
|
||||
queue_pointer = queue_pointer + 1
|
||||
for _, cardinal_dir in ipairs(cardinal_dirs) do
|
||||
local new_pos = vector.add(current_pos, cardinal_dir)
|
||||
local new_hash = minetest.hash_node_position(new_pos)
|
||||
if visited[new_hash] == nil then
|
||||
local new_node = minetest.get_node(new_pos)
|
||||
local new_node_name = new_node.name
|
||||
if new_node_name == "air" or new_node_name == flowing then
|
||||
return new_pos
|
||||
end
|
||||
visited[new_hash] = true
|
||||
if new_node_name == source then
|
||||
table.insert(queue, new_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Conserve liquids, when placing nodes in liquids try to find a place to displace the liquid to.
|
||||
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
|
||||
local flowing = dynamic_liquid.registered_liquids[oldnode.name]
|
||||
if flowing ~= nil then
|
||||
local dest = flood_search_outlet(pos, oldnode.name, flowing)
|
||||
if dest ~= nil then
|
||||
minetest.swap_node(dest, oldnode)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
12
mods/dynamic_liquid/locale/dynamic_liquid.de.tr
Normal file
@ -0,0 +1,12 @@
|
||||
# textdomain: dynamic_liquid
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
A natural spring that generates an endless stream of water source blocks=
|
||||
|
||||
Damp Clay=Feuchten Lehm
|
||||
|
||||
Generates one source block of water directly on top of itself once per second, provided the space is clear. If this natural spring is dug out the flow stops and it is turned into ordinary cobble.=
|
||||
|
||||
Spring=Quelle
|
12
mods/dynamic_liquid/locale/dynamic_liquid.es.tr
Normal file
@ -0,0 +1,12 @@
|
||||
# textdomain: dynamic_liquid
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
A natural spring that generates an endless stream of water source blocks=Un manantial natural que genera un flujo sin fín de bloques fuente de agua
|
||||
|
||||
Damp Clay=Arcilla húmeda
|
||||
|
||||
Generates one source block of water directly on top of itself once per second, provided the space is clear. If this natural spring is dug out the flow stops and it is turned into ordinary cobble.=Genera un bloque fuente de agua directamente encima de sí mismo una vez por segundo, en tanto el espacio esté libre. Si éste manantial natural es excavado el flujo se detiene y se convierte en adoquines comúnes.
|
||||
|
||||
Spring=Manantial
|
12
mods/dynamic_liquid/locale/dynamic_liquid.fr.tr
Normal file
@ -0,0 +1,12 @@
|
||||
# textdomain: dynamic_liquid
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
A natural spring that generates an endless stream of water source blocks=
|
||||
|
||||
Damp Clay=Argile humide
|
||||
|
||||
Generates one source block of water directly on top of itself once per second, provided the space is clear. If this natural spring is dug out the flow stops and it is turned into ordinary cobble.=
|
||||
|
||||
Spring=Source
|
12
mods/dynamic_liquid/locale/template.txt
Normal file
@ -0,0 +1,12 @@
|
||||
# textdomain: dynamic_liquid
|
||||
|
||||
|
||||
### init.lua ###
|
||||
|
||||
A natural spring that generates an endless stream of water source blocks=
|
||||
|
||||
Damp Clay=
|
||||
|
||||
Generates one source block of water directly on top of itself once per second, provided the space is clear. If this natural spring is dug out the flow stops and it is turned into ordinary cobble.=
|
||||
|
||||
Spring=
|
3
mods/dynamic_liquid/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = dynamic_liquid
|
||||
optional_depends = default, doc, xpanes, carts
|
||||
description = Flowing dynamic liquids and ocean-maintenance springs.
|
BIN
mods/dynamic_liquid/screenshot.png
Normal file
After Width: | Height: | Size: 144 KiB |
76
mods/dynamic_liquid/settingtypes.txt
Normal file
@ -0,0 +1,76 @@
|
||||
#Makes water limited (it doesn't spawn new source blocks any more) and causes
|
||||
#water blocks to move dynamically
|
||||
dynamic_liquid_water (Dynamic water) bool true
|
||||
|
||||
#Causes river water blocks to move dynamically. Use caution when using this
|
||||
#with the "valleys" mapgen, the streams generated by that mapgen have elevation
|
||||
#changes and can make a mess if their water moves downhill uncontrolled.
|
||||
dynamic_liquid_river_water (Dynamic river water) bool false
|
||||
|
||||
#Causes lava blocks to move dynamically
|
||||
dynamic_liquid_lava (Dynamic lava) bool true
|
||||
|
||||
#Floatlands are "leaky", so if you wish to prevent their lakes from draining
|
||||
#set this value to somewhere below the elevation the floatlands are generated
|
||||
#at.
|
||||
dynamic_liquid_disable_flow_above (Disable flow above this altitude) int 31000
|
||||
|
||||
#Causes natural clay deposits to act as water sources, seeping new water blocks
|
||||
#into the space above them.
|
||||
#Also adds a "spring" block to the creative inventory to serve as an infinite water
|
||||
#source in creative mode.
|
||||
dynamic_liquid_springs (Springs) bool true
|
||||
|
||||
#Allows dynamic liquids to "teleport" through nodes that should allow liquids to
|
||||
#pass through. This includes the groups:
|
||||
#leaves, sapling, grass, dry_grass, flora, rail, flower
|
||||
#As well as select other nodes from the default game's definitions. Other mods
|
||||
#can set nodes as flow-through by adding them to these groups or to the group
|
||||
#"flow_through"
|
||||
dynamic_liquid_flow_through (Flow-through) bool true
|
||||
|
||||
#During map generation, this mod can attempt to "pre-fill" underwater cave
|
||||
#entrances by replacing air directly below water with additional water blocks.
|
||||
#This process is only attempted below "sea level" and is not perfect - it will
|
||||
#only fill down to the bottom of the map block being generated and it won't
|
||||
#fill out openings to the sides. But it should hopefully reduce the magnitude
|
||||
#of the whirlpools that form over cave entrances and the load on the active
|
||||
#block modifier in these situations.
|
||||
dynamic_liquid_mapgen_prefill (Mapgen water prefill) bool true
|
||||
|
||||
#Dynamic liquid movement causes trouble with the default lava cooling system.
|
||||
#When set to true, this replaces the default lava cooling system with one
|
||||
#that will not result in oceans being paved over by a single wandering
|
||||
#lava block or lava seas being paved by a single wandering water block.
|
||||
dynamic_liquid_new_lava_cooling (Revised lava cooling) bool true
|
||||
|
||||
#When this is enabled, obsidian blocks spawned by lava cooling will fall from
|
||||
#their origin point like sand or gravel does. Otherwise, obsidian will still
|
||||
#behave the same - this occurs only when it's first created.
|
||||
dynamic_liquid_falling_obsidian (Falling obsidian) bool false
|
||||
|
||||
#When true, placing a block where a registered liquid source currently is
|
||||
#will make a simple attempt to displace the liquid source somewhere else
|
||||
#rather than destroying it.
|
||||
dynamic_liquid_displace_liquid (Displace liquid) bool true
|
||||
|
||||
[Flow Rates]
|
||||
|
||||
#Sets the probability of water flow per block per second.
|
||||
#That is, if this is set to 5 then there's a 1/5 chance per second that a water block
|
||||
#will check if it should move. Increase this value to make water flow slower.
|
||||
#This value only has an effect if dynamic water is set to true
|
||||
dynamic_liquid_water_flow_propability (Water flow probability) int 1 1 100
|
||||
|
||||
#Sets the probability of river water flow per block per second.
|
||||
#That is, if this is set to 5 then there's a 1/5 chance per second that a river water
|
||||
#block will check if it should move. Increase this value to make river water flow
|
||||
#slower.
|
||||
#This value only has an effect if dynamic river water is set to true
|
||||
dynamic_liquid_river_water_flow_propability (River water flow probability) int 1 1 100
|
||||
|
||||
#Sets the probability of lava flow per block per second.
|
||||
#That is, if this is set to 5 then there's a 1/5 chance per second that a lava block
|
||||
#will check if it should move. Increase this value to make lava flow slower.
|
||||
#This value only has an effect if dynamic lava is set to true
|
||||
dynamic_liquid_lava_flow_propability (Lava flow probability) int 5 1 100
|
2
mods/gas_lib/depends.txt
Normal file
@ -0,0 +1,2 @@
|
||||
default
|
||||
fire
|
71
mods/gas_lib/gasses.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local function add_group(name, group, val)
|
||||
local defgroup=table.copy(minetest.registered_nodes[name].groups)
|
||||
defgroup[group] = val
|
||||
minetest.override_item(name, { groups=defgroup })
|
||||
end
|
||||
|
||||
gas_lib.register_gas("gas_lib:smoke", {
|
||||
description = 'Smoke',
|
||||
tiles = {{
|
||||
name = "smoke.png^gui_hb_bg.png",
|
||||
--backface_culling=false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 6,
|
||||
},
|
||||
}},
|
||||
inventory_image = "smoke.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
wield_image = "smoke.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
post_effect_color = {a = 60, r = 100, g = 100, b = 100},
|
||||
damage_per_second = 1,
|
||||
drowning = 1,
|
||||
interval = 3,
|
||||
weight = -8,
|
||||
deathchance = 5,
|
||||
})
|
||||
|
||||
gas_lib.register_gas("gas_lib:steam", {
|
||||
description = 'Steam',
|
||||
tiles = {{
|
||||
name = "steam.png^gui_hb_bg.png",
|
||||
--backface_culling=false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 6,
|
||||
},
|
||||
}},
|
||||
inventory_image = "steam.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
wield_image = "steam.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
post_effect_color = {a = 60, r = 100, g = 100, b = 100},
|
||||
damage_per_second = 3,
|
||||
interval = 3,
|
||||
weight = -7,
|
||||
liquid = "default:water_source",
|
||||
deathchance = 5,
|
||||
})
|
||||
|
||||
minetest.register_abm{
|
||||
label="Smoke Gen",
|
||||
nodenames= {"group:smokey"},
|
||||
neighbors={"air"},
|
||||
interval=4,
|
||||
chance=1,
|
||||
action=function(pos)
|
||||
local newpos = minetest.find_node_near(pos, 1, "air")
|
||||
if newpos and math.random(1, minetest.get_item_group(minetest.get_node(pos).name, "smokey"))==1 then
|
||||
minetest.add_node(newpos, {name = "gas_lib:smoke"})
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local water_def = minetest.registered_nodes["default:water_source"]
|
||||
water_def.groups.vaporizable = 1
|
||||
minetest.override_item("default:water_source", { gas="gas_lib:steam", groups = water_def.groups, gas_byproduct = "default:dirt"})
|
||||
|
||||
add_group("default:furnace_active", "smokey", 1)
|
||||
add_group("fire:basic_flame", "smokey", 2)
|
||||
add_group("default:lava_source", "smokey", 4)
|
137
mods/gas_lib/init.lua
Normal file
@ -0,0 +1,137 @@
|
||||
gas_lib = {}
|
||||
|
||||
local function shuffle(tbl)
|
||||
for i = #tbl, 2, -1 do
|
||||
local j = math.random(i)
|
||||
tbl[i], tbl[j] = tbl[j], tbl[i]
|
||||
end
|
||||
end
|
||||
|
||||
function gas_lib.find_air_neighbor(pos)
|
||||
local gn=minetest.get_node
|
||||
if math.random(3) == 1 and gn(pos).name == "air" then return pos end
|
||||
local pos_table = {{"z", 0}, {"z", 1}, {"z", -1}, {"x", 1}, {"x", -1}}
|
||||
shuffle(pos_table)
|
||||
for k, v in pairs(pos_table) do
|
||||
local newpos = vector.new(pos)
|
||||
newpos[v[1]] = newpos[v[1]] + v[2]
|
||||
if gn(newpos).name == "air" then return newpos end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function gas_lib.tick(itemstring, pos, elapsed)
|
||||
local def = minetest.registered_nodes[itemstring]
|
||||
local node = minetest.get_node(pos)
|
||||
if node.param2 == 0 then node.param2 = def.lifetime+1 end
|
||||
--node deletion
|
||||
if math.random(100) <= def.deathchance then
|
||||
minetest.remove_node(pos)
|
||||
return
|
||||
end
|
||||
local pos1 = vector.new({x = pos.x + 1, y = pos.y + 1, z = pos.z + 1})
|
||||
local pos2 = vector.new({x = pos.x - 1, y = pos.y - 1, z = pos.z - 1})
|
||||
local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
|
||||
if #nodes == 0 then minetest.get_node_timer(pos):start(def.interval+math.random(10)/10) return end -- gas has no space to move, may as well not do anything
|
||||
if node.param2 ~= 0 and #nodes > 13 then
|
||||
node.param2 = node.param2 - 1
|
||||
if node.param2 <=1 then
|
||||
minetest.remove_node(pos)
|
||||
return
|
||||
else
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
end
|
||||
|
||||
local rand = math.random(10)
|
||||
local weight = def.weight
|
||||
if weight == 0 then --if has no weight, have a small chance to either go up or down
|
||||
weight = math.random(2)
|
||||
if weight == 2 then weight = -1 end
|
||||
end
|
||||
if rand <= math.abs(weight) then -- move up/down
|
||||
local sign = weight/math.abs(weight)
|
||||
local newpos = vector.new(pos)
|
||||
newpos.y = newpos.y - sign
|
||||
newpos = gas_lib.find_air_neighbor(newpos)
|
||||
if newpos then
|
||||
minetest.remove_node(pos)
|
||||
minetest.add_node(newpos, node)
|
||||
return
|
||||
end
|
||||
end
|
||||
--hasn't moved up or down, so move horizontally.
|
||||
local newpos = gas_lib.find_air_neighbor(vector.new(pos))
|
||||
if newpos then
|
||||
minetest.remove_node(pos)
|
||||
minetest.add_node(newpos, node)
|
||||
return
|
||||
end
|
||||
minetest.get_node_timer(pos):start(def.interval+math.random(10)/10)
|
||||
end
|
||||
|
||||
local function table_combine(table1, table2)
|
||||
local newtable = table.copy(table1) -- table 1 gets written over
|
||||
for index,data in pairs(table2) do newtable[index] = data end
|
||||
return newtable
|
||||
end
|
||||
|
||||
local defaultdef = {
|
||||
drawtype = "glasslike",
|
||||
paramtype = "light",
|
||||
paramtype2 = "none",
|
||||
drop="",
|
||||
use_texture_alpha=true,
|
||||
sunlight_propagates = true,
|
||||
walkable = false,
|
||||
pointable = false,
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
floodable= true,
|
||||
interval = 2.5,
|
||||
lifetime = 10, --how many intervals it can last in open air
|
||||
weight = 0, --chance out of 10 to go up, use negative to go down
|
||||
deathchance = 0, --chance out of 100 to randomly die
|
||||
on_flood = function(pos, oldnode, newnode)
|
||||
local newpos = gas_lib.find_air_neighbor(pos)
|
||||
if newpos then
|
||||
minetest.add_node(newpos, oldnode)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
function gas_lib.register_gas(itemstring, def)
|
||||
def = table_combine(defaultdef, def)
|
||||
def.on_construct = function(pos) minetest.get_node_timer(pos):start(def.interval+math.random(10)/10) end
|
||||
def.on_timer = function(pos, elapsed) gas_lib.tick(itemstring, pos, elapsed) end
|
||||
if not def.groups then def.groups = {} end
|
||||
if not def.groups.gas then def.groups.gas = 1 end
|
||||
minetest.register_node(itemstring, def)
|
||||
end
|
||||
|
||||
minetest.register_abm{
|
||||
label="Remove gas",
|
||||
nodenames= {"group:gas"},
|
||||
interval=60,
|
||||
chance=20,
|
||||
action=function(pos)
|
||||
minetest.remove_node(pos)
|
||||
end
|
||||
}
|
||||
minetest.register_lbm{
|
||||
name="gas_lib:ensuretimer",
|
||||
nodenames= {"group:gas"},
|
||||
run_at_every_load = true,
|
||||
action=function(pos)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
if not timer:is_started() then
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
minetest.get_node_timer(pos):start(def.interval+math.random(10)/10)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local path = minetest.get_modpath(minetest.get_current_modname())
|
||||
dofile(path .. "/gasses.lua")
|
||||
dofile(path .. "/tools.lua")
|
2
mods/gas_lib/textures/smoke license.txt
Normal file
@ -0,0 +1,2 @@
|
||||
-- Animated smoke texture courtesy of Texmex.
|
||||
-- License for textures: CC--BY-SA 4.0
|
BIN
mods/gas_lib/textures/smoke.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
mods/gas_lib/textures/steam.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
333
mods/gas_lib/tools.lua
Normal file
@ -0,0 +1,333 @@
|
||||
local S = default.get_translator
|
||||
|
||||
local function furnace_active(fuel_percent)
|
||||
return "size[8,8.5]"..
|
||||
"list[context;fuel;2.75,2.5;1,1;]"..
|
||||
"image[2.75,1.5;1,1;default_furnace_fire_bg.png^[lowpart:"..
|
||||
(fuel_percent)..":default_furnace_fire_fg.png]"..
|
||||
"list[current_player;main;0,4.25;8,1;]"..
|
||||
"list[current_player;main;0,5.5;8,3;8]"..
|
||||
"listring[context;fuel]"..
|
||||
"listring[current_player;main]"..
|
||||
default.get_hotbar_bg(0, 4.25)
|
||||
end
|
||||
|
||||
local function furnace_inactive()
|
||||
return "size[8,8.5]"..
|
||||
"list[context;fuel;2.75,2.5;1,1;]"..
|
||||
"image[2.75,1.5;1,1;default_furnace_fire_bg.png]"..
|
||||
"list[current_player;main;0,4.25;8,1;]"..
|
||||
"list[current_player;main;0,5.5;8,3;8]"..
|
||||
"listring[context;fuel]"..
|
||||
"listring[current_player;main]"..
|
||||
default.get_hotbar_bg(0, 4.25)
|
||||
end
|
||||
|
||||
--
|
||||
-- Node callback functions that are the same for active and inactive furnace
|
||||
--
|
||||
|
||||
local function can_dig(pos, player)
|
||||
local meta = minetest.get_meta(pos);
|
||||
local inv = meta:get_inventory()
|
||||
return inv:is_empty("fuel")
|
||||
end
|
||||
|
||||
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return 0
|
||||
end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
if listname == "fuel" then
|
||||
if minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
|
||||
if inv:is_empty("src") then
|
||||
meta:set_string("infotext", S("Furnace is empty"))
|
||||
end
|
||||
return stack:get_count()
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
local stack = inv:get_stack(from_list, from_index)
|
||||
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
|
||||
end
|
||||
|
||||
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return 0
|
||||
end
|
||||
return stack:get_count()
|
||||
end
|
||||
|
||||
local function swap_node(pos, name)
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name == name then
|
||||
return
|
||||
end
|
||||
node.name = name
|
||||
minetest.swap_node(pos, node)
|
||||
end
|
||||
|
||||
local function vaporize(p, airabove)
|
||||
if not p then return end
|
||||
local def = minetest.registered_nodes[minetest.get_node(p).name]
|
||||
if def.gas then
|
||||
if airabove then
|
||||
minetest.add_node({x=p.x,y=p.y+1,z=p.z}, {name = def.gas})
|
||||
else
|
||||
minetest.add_node(p, {name = def.gas})
|
||||
end
|
||||
end
|
||||
if def.gas_byproduct and math.random(def.gas_byproduct_chance or 4) == 1 then
|
||||
minetest.swap_node(p, {name=def.gas_byproduct})
|
||||
elseif airabove then
|
||||
minetest.remove_node(p)
|
||||
end
|
||||
end
|
||||
|
||||
local function furnace_node_timer(pos, elapsed)
|
||||
--
|
||||
-- Initialize metadata
|
||||
--
|
||||
local meta = minetest.get_meta(pos)
|
||||
local fuel_time = meta:get_float("fuel_time") or 0
|
||||
local fuel_totaltime = meta:get_float("fuel_totaltime") or 0
|
||||
|
||||
local inv = meta:get_inventory()
|
||||
local fuellist
|
||||
|
||||
local fuel
|
||||
|
||||
local update = true
|
||||
while elapsed > 0 and update do
|
||||
update = false
|
||||
fuellist = inv:get_list("fuel")
|
||||
local el = math.min(elapsed, fuel_totaltime - fuel_time)
|
||||
if fuel_time < fuel_totaltime then
|
||||
-- The furnace is currently active and has enough fuel
|
||||
fuel_time = fuel_time + el
|
||||
if elapsed >= 1 and math.random(10) == 1 then
|
||||
local airabove = true
|
||||
local p = minetest.find_nodes_in_area_under_air({x=pos.x+1,y=pos.y+1,z=pos.z+1}, {x=pos.x-1,y=pos.y-1,z=pos.z-1}, "group:vaporizable")
|
||||
p = p[math.random(#p)]
|
||||
if not p then
|
||||
airabove = false
|
||||
p = minetest.find_nodes_in_area({x=pos.x+1,y=pos.y+1,z=pos.z+1}, {x=pos.x-1,y=pos.y-1,z=pos.z-1}, "group:vaporizable")
|
||||
p = p[math.random(#p)]
|
||||
end
|
||||
vaporize(p,airabove)
|
||||
end
|
||||
else
|
||||
local afterfuel
|
||||
fuel, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})
|
||||
fuel.time = fuel.time*2
|
||||
if fuel.time == 0 then
|
||||
-- No valid fuel in fuel list
|
||||
fuel_totaltime = 0
|
||||
fuel_time = 0
|
||||
else
|
||||
-- Take fuel from fuel list
|
||||
inv:set_stack("fuel", 1, afterfuel.items[1])
|
||||
-- Put replacements in dst list or drop them on the furnace.
|
||||
local replacements = fuel.replacements
|
||||
if replacements[1] then
|
||||
local above = vector.new(pos.x, pos.y + 1, pos.z)
|
||||
local drop_pos = minetest.find_node_near(above, 1, {"air"}) or above
|
||||
minetest.item_drop(replacements[1], nil, drop_pos)
|
||||
end
|
||||
update = true
|
||||
fuel_totaltime = fuel.time + (fuel_totaltime - fuel_time)
|
||||
fuel_time = 0
|
||||
end
|
||||
end
|
||||
elapsed = elapsed - el
|
||||
end
|
||||
|
||||
if fuel and fuel_totaltime > fuel.time then
|
||||
fuel_totaltime = fuel.time
|
||||
end
|
||||
|
||||
--
|
||||
-- Update formspec, infotext and node
|
||||
--
|
||||
local formspec
|
||||
local item_state
|
||||
|
||||
local fuel_state = S("Empty")
|
||||
local active = false
|
||||
local result = false
|
||||
|
||||
if fuel_totaltime ~= 0 then
|
||||
active = true
|
||||
local fuel_percent = 100 - math.floor(fuel_time / fuel_totaltime * 100)
|
||||
fuel_state = S("@1%", fuel_percent)
|
||||
formspec = furnace_active(fuel_percent, 0)
|
||||
swap_node(pos, "gas_lib:furnace_active")
|
||||
-- make sure timer restarts automatically
|
||||
result = true
|
||||
else
|
||||
if fuellist and not fuellist[1]:is_empty() then
|
||||
fuel_state = S("@1%", 0)
|
||||
end
|
||||
formspec = furnace_inactive()
|
||||
swap_node(pos, "gas_lib:furnace")
|
||||
-- stop timer on the inactive furnace
|
||||
minetest.get_node_timer(pos):stop()
|
||||
end
|
||||
|
||||
|
||||
local infotext
|
||||
if active then
|
||||
infotext = S("Furnace active")
|
||||
else
|
||||
infotext = S("Furnace inactive")
|
||||
end
|
||||
infotext = infotext .. "\n" .. S("(Fuel: @1)", fuel_state)
|
||||
|
||||
--
|
||||
-- Set meta values
|
||||
--
|
||||
meta:set_float("fuel_totaltime", fuel_totaltime)
|
||||
meta:set_float("fuel_time", fuel_time)
|
||||
meta:set_string("formspec", formspec)
|
||||
meta:set_string("infotext", infotext)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--
|
||||
-- Node definitions
|
||||
--
|
||||
|
||||
minetest.register_node("gas_lib:furnace", {
|
||||
description = S("Furnace"),
|
||||
tiles = {
|
||||
"default_furnace_top.png", "default_furnace_bottom.png",
|
||||
"default_furnace_side.png", "default_furnace_side.png",
|
||||
"default_furnace_side.png", "default_furnace_front.png"
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2},
|
||||
legacy_facedir_simple = true,
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
|
||||
can_dig = can_dig,
|
||||
|
||||
on_timer = furnace_node_timer,
|
||||
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size('fuel', 1)
|
||||
furnace_node_timer(pos, 0)
|
||||
end,
|
||||
|
||||
on_metadata_inventory_move = function(pos)
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
end,
|
||||
on_metadata_inventory_put = function(pos)
|
||||
-- start timer function, it will sort out whether furnace can burn or not.
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
end,
|
||||
on_blast = function(pos)
|
||||
local drops = {}
|
||||
default.get_inventory_drops(pos, "src", drops)
|
||||
default.get_inventory_drops(pos, "fuel", drops)
|
||||
default.get_inventory_drops(pos, "dst", drops)
|
||||
drops[#drops+1] = "gas_lib:furnace"
|
||||
minetest.remove_node(pos)
|
||||
return drops
|
||||
end,
|
||||
|
||||
allow_metadata_inventory_put = allow_metadata_inventory_put,
|
||||
allow_metadata_inventory_move = allow_metadata_inventory_move,
|
||||
allow_metadata_inventory_take = allow_metadata_inventory_take,
|
||||
})
|
||||
|
||||
minetest.register_node("gas_lib:furnace_active", {
|
||||
description = S("Furnace"),
|
||||
tiles = {
|
||||
"default_furnace_top.png", "default_furnace_bottom.png",
|
||||
"default_furnace_side.png", "default_furnace_side.png",
|
||||
"default_furnace_side.png",
|
||||
{
|
||||
image = "default_furnace_front_active.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 1.5
|
||||
},
|
||||
}
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
light_source = 8,
|
||||
drop = "gas_lib:furnace",
|
||||
groups = {cracky=2, not_in_creative_inventory=1, smokey = 2},
|
||||
legacy_facedir_simple = true,
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
on_timer = furnace_node_timer,
|
||||
|
||||
can_dig = can_dig,
|
||||
|
||||
allow_metadata_inventory_put = allow_metadata_inventory_put,
|
||||
allow_metadata_inventory_move = allow_metadata_inventory_move,
|
||||
allow_metadata_inventory_take = allow_metadata_inventory_take,
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "gas_lib:furnace",
|
||||
recipe = {
|
||||
{"group:stone", "group:stone", "group:stone"},
|
||||
{"group:stone", "default:wood", "group:stone"},
|
||||
{"group:stone", "group:stone", "group:stone"},
|
||||
}
|
||||
})
|
||||
|
||||
local exchangertick = 10
|
||||
|
||||
minetest.register_node("gas_lib:heat_exchanger", {
|
||||
description = "Heat Exchanger",
|
||||
tiles = {"default_stone.png"},
|
||||
groups = {cracky = 1},
|
||||
on_construct = function(pos)
|
||||
minetest.get_node_timer(pos):start(exchangertick)
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
local rn = minetest.registered_nodes
|
||||
local above = rn[minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z}).name]
|
||||
local below = rn[minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name]
|
||||
if not above or not below then return true end
|
||||
if not above.gas then return true end
|
||||
if not below.liquid then return true end
|
||||
if math.random(3) == 3 then--heat liquid
|
||||
vaporize({x=pos.x,y=pos.y+1,z=pos.z}, minetest.get_node({x=pos.x,y=pos.y+2,z=pos.z}).name == "air")
|
||||
else --cool gas
|
||||
minetest.swap_node({x=pos.x,y=pos.y-1,z=pos.z}, {name=below.liquid})
|
||||
end
|
||||
return true
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_lbm{
|
||||
name="gas_lib:heattimer",
|
||||
nodenames= {"gas_lib:heat_exchanger", "gas_lib:furnace_active"},
|
||||
run_at_every_load = true,
|
||||
action=function(pos)
|
||||
local timer = minetest.get_node_timer(pos)
|
||||
if not timer:is_started() then
|
||||
local node = minetest.get_node(pos)
|
||||
local def = minetest.registered_nodes[node.name]
|
||||
minetest.get_node_timer(pos):start(exchangertick)
|
||||
end
|
||||
end
|
||||
}
|
6
mods/oil/depends.txt
Normal file
@ -0,0 +1,6 @@
|
||||
default
|
||||
gas_lib
|
||||
bucket
|
||||
dynamic_liquid
|
||||
currency
|
||||
waterworks?
|
507
mods/oil/init.lua
Normal file
@ -0,0 +1,507 @@
|
||||
-- textures LGLv2.1" = "ShadMOrdre. Tenplus1, Gail de Sailly, VannessaE, runs, and numerous others."
|
||||
oil = {}
|
||||
oil.fueling = {}
|
||||
|
||||
minetest.register_node("oil:gasoline_source", {
|
||||
description = "Gasoline Source",
|
||||
drawtype = "liquid",
|
||||
waving = 3,
|
||||
tiles = {
|
||||
{
|
||||
name = "cars_gasoline_source_animated.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name = "cars_gasoline_source_animated.png",
|
||||
backface_culling = true,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
alpha = 240,
|
||||
paramtype = "light",
|
||||
walkable = false,
|
||||
pointable = false,
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
is_ground_content = false,
|
||||
drop = "",
|
||||
drowning = 1,
|
||||
liquid_renewable = false,
|
||||
liquidtype = "source",
|
||||
liquid_alternative_flowing = "oil:gasoline_flowing",
|
||||
liquid_alternative_source = "oil:gasoline_source",
|
||||
liquid_viscosity = 4,
|
||||
post_effect_color = {a = 103, r = 30, g = 60, b = 90},
|
||||
groups = {water = 3, liquid = 3, vaporizable = 1, flammable = 1},
|
||||
sounds = default.node_sound_water_defaults(),
|
||||
gas = "oil:gasoline_vapor"
|
||||
})
|
||||
|
||||
minetest.register_node("oil:gasoline_flowing", {
|
||||
description = "Gasoline Flowing",
|
||||
drawtype = "flowingliquid",
|
||||
waving = 3,
|
||||
tiles = {"cars_gasoline_source.png"},
|
||||
special_tiles = {
|
||||
{
|
||||
name = "cars_gasoline_flowing_animated.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name = "cars_gasoline_flowing_animated.png",
|
||||
backface_culling = true,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
alpha = 240,
|
||||
paramtype = "light",
|
||||
paramtype2 = "flowingliquid",
|
||||
walkable = false,
|
||||
pointable = false,
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
is_ground_content = false,
|
||||
drop = "",
|
||||
drowning = 1,
|
||||
liquid_renewable = false,
|
||||
liquidtype = "flowing",
|
||||
liquid_alternative_flowing = "oil:gasoline_flowing",
|
||||
liquid_alternative_source = "oil:gasoline_source",
|
||||
liquid_viscosity = 4,
|
||||
post_effect_color = {a = 103, r = 30, g = 60, b = 90},
|
||||
groups = {water = 3, liquid = 3, not_in_creative_inventory = 1, flammable = 1},
|
||||
sounds = default.node_sound_water_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_node("oil:oil_source", {
|
||||
description = "Crude Oil Source",
|
||||
drawtype = "liquid",
|
||||
waving = 3,
|
||||
tiles = {
|
||||
{
|
||||
name = "cars_oil_source_animated.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name = "cars_oil_source_animated.png",
|
||||
backface_culling = true,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
alpha = 240,
|
||||
paramtype = "light",
|
||||
walkable = false,
|
||||
pointable = false,
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
is_ground_content = false,
|
||||
drop = "",
|
||||
drowning = 1,
|
||||
liquid_renewable = false,
|
||||
liquidtype = "source",
|
||||
liquid_alternative_flowing = "oil:oil_flowing",
|
||||
liquid_alternative_source = "oil:oil_source",
|
||||
liquid_viscosity = 4,
|
||||
post_effect_color = {a = 103, r = 30, g = 60, b = 90},
|
||||
groups = {water = 3, liquid = 3, vaporizable = 1, flammable = 1},
|
||||
sounds = default.node_sound_water_defaults(),
|
||||
gas = "oil:gasoline_vapor",
|
||||
gas_byproduct = "oil:tar",
|
||||
gas_byproduct_chance = 6
|
||||
})
|
||||
|
||||
minetest.register_node("oil:oil_flowing", {
|
||||
description = "Crude Oil Flowing",
|
||||
drawtype = "flowingliquid",
|
||||
waving = 3,
|
||||
tiles = {"cars_oil_source.png"},
|
||||
special_tiles = {
|
||||
{
|
||||
name = "cars_oil_flowing_animated.png",
|
||||
backface_culling = false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name = "cars_oil_flowing_animated.png",
|
||||
backface_culling = true,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
alpha = 240,
|
||||
paramtype = "light",
|
||||
paramtype2 = "flowingliquid",
|
||||
walkable = false,
|
||||
pointable = false,
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
is_ground_content = false,
|
||||
drop = "",
|
||||
drowning = 1,
|
||||
liquid_renewable = false,
|
||||
liquidtype = "flowing",
|
||||
liquid_alternative_flowing = "oil:oil_flowing",
|
||||
liquid_alternative_source = "oil:oil_source",
|
||||
liquid_viscosity = 4,
|
||||
post_effect_color = {a = 103, r = 30, g = 60, b = 90},
|
||||
groups = {water = 3, liquid = 3, not_in_creative_inventory = 1, flammable = 1},
|
||||
sounds = default.node_sound_water_defaults(),
|
||||
})
|
||||
|
||||
dynamic_liquid.liquid_abm("oil:oil_source", "oil:oil_flowing", 1)
|
||||
dynamic_liquid.liquid_abm("oil:gasoline_source", "oil:gasoline_flowing", 1)
|
||||
if waterworks then
|
||||
waterworks.register_liquid("oil:oil_source", {flowing = "oil:oil_flowing"})
|
||||
waterworks.register_liquid("oil:gasoline_source", {flowing = "oil:gasoline_flowing"})
|
||||
end
|
||||
|
||||
minetest.register_node("oil:tar", {
|
||||
description = "Tar",
|
||||
tiles = {"cars_oil_source.png"},
|
||||
})
|
||||
|
||||
minetest.register_ore({
|
||||
ore_type = "blob",
|
||||
ore = "oil:oil_source",
|
||||
wherein = "default:stone",
|
||||
clust_scarcity = 32 * 32 * 32,
|
||||
clust_num_ores = 16,
|
||||
clust_size = 6,
|
||||
y_min = -31000,
|
||||
y_max = -64,
|
||||
})
|
||||
|
||||
bucket.register_liquid(
|
||||
"oil:gasoline_source",
|
||||
"oil:gasoline_flowing",
|
||||
"oil:bucket_gasoline",
|
||||
"bucket_water.png",
|
||||
"Gasoline Bucket",
|
||||
{tool = 1, gasoline_bucket = 1}
|
||||
)
|
||||
bucket.register_liquid(
|
||||
"oil:oil_source",
|
||||
"oil:oil_flowing",
|
||||
"oil:bucket_oil",
|
||||
"bucket_water.png",
|
||||
"Oil Bucket",
|
||||
{tool = 1, oil_bucket = 1}
|
||||
)
|
||||
|
||||
gas_lib.register_gas("oil:gasoline_vapor", {
|
||||
description = 'Gasoline Vapor',
|
||||
tiles = {{
|
||||
name = "smoke.png^gui_hb_bg.png",
|
||||
--backface_culling=false,
|
||||
animation = {
|
||||
type = "vertical_frames",
|
||||
aspect_w = 16,
|
||||
aspect_h = 16,
|
||||
length = 6,
|
||||
},
|
||||
}},
|
||||
inventory_image = "smoke.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
wield_image = "smoke.png^[verticalframe:16:1^gui_hb_bg.png",
|
||||
post_effect_color = {a = 60, r = 100, g = 100, b = 100},
|
||||
damage_per_second = 1,
|
||||
drowning = 1,
|
||||
interval = 3,
|
||||
weight = -8,
|
||||
deathchance = 3,
|
||||
liquid = "oil:gasoline_source"
|
||||
})
|
||||
|
||||
local bucket_liters = 1000
|
||||
local pump_capacity = 2*bucket_liters
|
||||
|
||||
local reclick_stopper = {}
|
||||
|
||||
local function form_pump(pos, owner)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local spos = pos.x .. "," .. pos.y .. "," .. pos.z
|
||||
local price = meta:get_string("price")
|
||||
if price == "" then price = "0" end
|
||||
local form = "size[8,7]" ..
|
||||
"list[current_player;main;0,3;8,4;0]" ..
|
||||
"list[nodemeta:"..spos..";input;6,0.9;1,1.1;0]" ..
|
||||
"label[5.7,0.1;Gas: "..meta:get_int("gas").."L]" ..
|
||||
"label[5.7,0.4;Bought: "..meta:get_int("gasbought").."L]" ..
|
||||
"button_exit[6,1.8;1,1;buy;Buy]"..
|
||||
"label[5.7,-.2;Price: "..price.." minegeld/L]"
|
||||
if owner then
|
||||
form = form..
|
||||
"label[0.5,0;Customers gave:]" ..
|
||||
"list[nodemeta:" .. spos .. ";output;0.5,0.5;2.5,2;0]"..
|
||||
"field[3,0.6;2.5,1;price;Price (minegeld/L);"..price.."]"
|
||||
else
|
||||
|
||||
end
|
||||
return form
|
||||
end
|
||||
|
||||
local form_table = {}
|
||||
|
||||
local function rotateVector(x, y, a)
|
||||
local c = math.cos(a)
|
||||
local s = math.sin(a)
|
||||
return c*x - s*y, s*x + c*y
|
||||
end
|
||||
|
||||
oil.stopfuel = function(name)
|
||||
local data = oil.fueling[name]
|
||||
if not data then return end
|
||||
if data.obj then
|
||||
data.obj:remove()
|
||||
end
|
||||
local meta = minetest.get_meta(data.pos)
|
||||
meta:set_string("name", "")
|
||||
minetest.get_node_timer(data.pos):stop()
|
||||
oil.fueling[name] = nil
|
||||
end
|
||||
|
||||
minetest.register_entity("oil:line", {
|
||||
hp_max = 1,
|
||||
physical = false,
|
||||
pointable = false,
|
||||
weight = 5,
|
||||
collisionbox = {-0.1,-0.1,-0.1, 0.1,0.1,0.1},
|
||||
visual = "cube",
|
||||
visual_size = {x=.05, y=.1},
|
||||
textures = {"blackline.png", "blackline.png", "blackline.png", "blackline.png", "blackline.png", "blackline.png"}, -- number of required textures depends on visual
|
||||
colors = {}, -- number of required colors depends on visual
|
||||
spritediv = {x=1, y=1},
|
||||
initial_sprite_basepos = {x=0, y=0},
|
||||
is_visible = true,
|
||||
makes_footstep_sound = false,
|
||||
automatic_rotate = 0,
|
||||
on_step = function(self, dtime)
|
||||
if self.startobj then
|
||||
self.start = self.startobj:get_pos()
|
||||
end
|
||||
if self.finishobj then
|
||||
self.finish = self.finishobj:get_pos()
|
||||
if self.finishoffset then
|
||||
local offset = table.copy(self.finishoffset)
|
||||
local yaw
|
||||
if self.finishobj:is_player() then
|
||||
yaw = self.finishobj:get_look_horizontal() or 0
|
||||
else
|
||||
yaw = self.finishobj:get_yaw() or 0
|
||||
end
|
||||
offset.x, offset.z = rotateVector(offset.x, offset.z, yaw)
|
||||
self.finish = vector.add(self.finish,offset)
|
||||
end
|
||||
end
|
||||
local sp = self.start
|
||||
local fp = self.finish
|
||||
if not sp or not fp then self.object:remove() return end
|
||||
if self.laststart and self.lastfinish and vector.equals(self.laststart, sp) and vector.equals(self.lastfinish, fp) then return end
|
||||
|
||||
local dist = vector.distance(sp, fp)
|
||||
if dist > 4 then
|
||||
for name, data in pairs(oil.fueling) do
|
||||
if data.obj == self.object then
|
||||
oil.stopfuel(name)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
local delta = vector.subtract(sp, fp)
|
||||
local yaw = math.atan2(delta.z, delta.x) - math.pi / 2
|
||||
local pitch = math.atan2(delta.y, math.sqrt(delta.z*delta.z + delta.x*delta.x))
|
||||
pitch = pitch + math.pi/2
|
||||
|
||||
self.object:move_to({x=(sp.x+fp.x)/2, y=(sp.y+fp.y)/2, z=(sp.z+fp.z)/2, })
|
||||
self.object:set_rotation({x=pitch, y=yaw, z=0})
|
||||
self.object:set_properties({visual_size = {x=.05, y=dist}})
|
||||
self.laststart = sp
|
||||
self.lastfinish = fp
|
||||
end,
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
if not staticdata or staticdata == "" then self.object:remove() return end
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname ~= "oil:gas_pump" then return end
|
||||
if not player then return end
|
||||
local name = player:get_player_name()
|
||||
local pos = form_table[name]
|
||||
if not pos then return end
|
||||
local meta = minetest.get_meta(pos)
|
||||
if fields.buy then
|
||||
local inv = meta:get_inventory()
|
||||
local price = tonumber(meta:get_string("price"))
|
||||
local input = inv:get_stack("input", 1)
|
||||
local moneys = {minegeld = 1, minegeld_5 = 5, minegeld_10 = 10}
|
||||
local amount = (moneys[string.gsub(input:get_name(), "currency:", "")] or 0) * input:get_count()
|
||||
if price > 0 and (amount >= price or meta:get_int("gasbought") > 0) then
|
||||
local gasbought = math.floor(amount/price)
|
||||
local gas = meta:get_int("gas")
|
||||
if gasbought > gas then gasbought = gas end
|
||||
local change = math.floor(amount-gasbought*price)
|
||||
local output = ItemStack({name = "currency:minegeld", count = amount-change})
|
||||
local changestack = ItemStack({name = "currency:minegeld", count = change})
|
||||
if inv:room_for_item("output", output) then
|
||||
if change > 0 then
|
||||
inv:set_stack("input", 1, changestack)
|
||||
else
|
||||
inv:set_stack("input", 1, ItemStack())
|
||||
end
|
||||
meta:set_int("gas", gas-gasbought)
|
||||
inv:add_item("output", output)
|
||||
gasbought = gasbought+meta:get_int("gasbought")
|
||||
meta:set_int("gasbought", gasbought)
|
||||
if gasbought > 0 then
|
||||
minetest.chat_send_player(name, "Punch the car you wish to gas up.")
|
||||
local obj = minetest.add_entity(pos, "oil:line", "sup")
|
||||
local ent = obj:get_luaentity()
|
||||
local offset = {x=.4,y=.1,z=-.4}
|
||||
local yaw = minetest.dir_to_yaw(minetest.facedir_to_dir(minetest.get_node(pos).param2))
|
||||
offset.x, offset.z = rotateVector(offset.x, offset.z, yaw)
|
||||
ent.start = vector.add(pos, offset)
|
||||
ent.finishobj = player
|
||||
ent.finishoffset = {x=0,y=1.2,z=.3}
|
||||
oil.stopfuel(name)
|
||||
oil.fueling[name] = {pos = pos, obj = obj}
|
||||
end
|
||||
else
|
||||
minetest.chat_send_player(name, "Pump is full and cannot fit payment, contact pump owner.")
|
||||
end
|
||||
end
|
||||
end
|
||||
if fields.key_enter_field == "price" then
|
||||
local owner = meta:get_string("owner")
|
||||
if owner == "" or owner == name then
|
||||
if tonumber(fields.price) then
|
||||
meta:set_string("price", fields.price)
|
||||
end
|
||||
end
|
||||
end
|
||||
if fields.quit then
|
||||
form_table[name] = nil
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_node("oil:pump", {
|
||||
description = "Gas Pump",
|
||||
drawtype = "mesh",
|
||||
mesh = "gaspump.b3d",
|
||||
paramtype2 = "facedir",
|
||||
tiles = {"gaspumpUV.png"},
|
||||
groups = {cracky = 3, stone = 1},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if not clicker then return end
|
||||
local name = clicker:get_player_name()
|
||||
if not name then return end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local owner = meta:get_string("owner")
|
||||
minetest.show_formspec(name,"oil:gas_pump", form_pump(pos, owner == "" or owner == name))
|
||||
form_table[name] = pos
|
||||
end,
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = minetest.get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
inv:set_size("input", 1)
|
||||
inv:set_size("output", 4)
|
||||
meta:set_string("owner", placer:get_player_name() or "")
|
||||
meta:set_string("infotext", "Gas Pump (owned by "..meta:get_string("owner")..")")
|
||||
end,
|
||||
on_punch = function(pos, node, puncher, pointed_thing)
|
||||
if not puncher then return end
|
||||
local meta = minetest.get_meta(pos)
|
||||
local name = puncher:get_player_name()
|
||||
if not name then return end
|
||||
if meta:get_string("owner") ~= "" and meta:get_string("owner") ~= name then return end
|
||||
if reclick_stopper[name] then reclick_stopper[name] = nil return end
|
||||
local wield = puncher:get_wielded_item():get_name()
|
||||
local gas = meta:get_int("gas") or 0
|
||||
if wield == "bucket:bucket_empty" then
|
||||
if gas >= bucket_liters then
|
||||
meta:set_string("gas", gas-bucket_liters)
|
||||
puncher:set_wielded_item("oil:bucket_gasoline")
|
||||
reclick_stopper[name] = true
|
||||
minetest.after(.5, function() reclick_stopper[name] = nil end)
|
||||
end
|
||||
elseif wield == "oil:bucket_gasoline" then
|
||||
if gas + bucket_liters <= pump_capacity then
|
||||
puncher:set_wielded_item("bucket:bucket_empty")
|
||||
meta:set_string("gas", gas+bucket_liters)
|
||||
reclick_stopper[name] = true
|
||||
minetest.after(.5, function() reclick_stopper[name] = nil end)
|
||||
end
|
||||
end
|
||||
end,
|
||||
on_timer = function(pos, elapsed)
|
||||
local meta = minetest.get_meta(pos)
|
||||
|
||||
local name = meta:get_string("name")
|
||||
if name == "" then return end
|
||||
local data = oil.fueling[name]
|
||||
if not data then return end
|
||||
|
||||
if not data.obj or data.obj:is_player() then return end
|
||||
|
||||
local ent = data.obj:get_luaentity()
|
||||
if not ent or not ent.finishobj then return end
|
||||
local def = cars_registered_cars[ent.finishobj:get_entity_name()]
|
||||
if not def then return end
|
||||
local carent = ent.finishobj:get_luaentity()
|
||||
|
||||
local maxgas = def.gas_cap or 50
|
||||
if not carent.gas then carent.gas = 0 end
|
||||
local gas = meta:get_int("gasbought") or 0
|
||||
if gas == 0 then oil.stopfuel(name) return end
|
||||
|
||||
meta:set_int("gasbought", gas - 1)
|
||||
carent.gas = carent.gas + 1
|
||||
--minetest.chat_send_all(carent.gas)
|
||||
if carent.gas >= maxgas then carent.gas = maxgas oil.stopfuel(name) return end
|
||||
|
||||
return true
|
||||
end
|
||||
})
|
BIN
mods/oil/models/gaspump.b3d
Normal file
BIN
mods/oil/textures/blackline.png
Normal file
After Width: | Height: | Size: 546 B |
BIN
mods/oil/textures/cars_gasoline_flowing_animated.png
Normal file
After Width: | Height: | Size: 332 B |
BIN
mods/oil/textures/cars_gasoline_source.png
Normal file
After Width: | Height: | Size: 246 B |
BIN
mods/oil/textures/cars_gasoline_source_animated.png
Normal file
After Width: | Height: | Size: 665 B |
BIN
mods/oil/textures/cars_oil_flowing_animated.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
mods/oil/textures/cars_oil_source.png
Normal file
After Width: | Height: | Size: 382 B |
BIN
mods/oil/textures/cars_oil_source_animated.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
mods/oil/textures/gaspumpUV.png
Normal file
After Width: | Height: | Size: 12 KiB |
4
mods/static_ocean/depends.txt
Normal file
@ -0,0 +1,4 @@
|
||||
default
|
||||
dynamic_liquid
|
||||
bucket
|
||||
waterworks?
|
42
mods/static_ocean/init.lua
Normal file
@ -0,0 +1,42 @@
|
||||
local def = table.copy(minetest.registered_nodes["default:water_source"])
|
||||
def.liquid_alternative_source = "static_ocean:water_source"
|
||||
def.liquid_alternative_flowing = "static_ocean:water_flowing"
|
||||
def.liquid_renewable = true
|
||||
minetest.register_node("static_ocean:water_source", def)
|
||||
|
||||
local def = table.copy(minetest.registered_nodes["default:water_flowing"])
|
||||
def.liquid_alternative_source = "static_ocean:water_source"
|
||||
def.liquid_alternative_flowing = "static_ocean:water_flowing"
|
||||
def.liquid_renewable = true
|
||||
minetest.register_node("static_ocean:water_flowing", def)
|
||||
local source = "static_ocean:water_source"
|
||||
local flowing = "static_ocean:water_flowing"
|
||||
bucket.liquids[source] = {
|
||||
source = source,
|
||||
flowing = flowing,
|
||||
itemname = "bucket:bucket_water",
|
||||
force_renew = false,
|
||||
}
|
||||
bucket.liquids[flowing] = bucket.liquids[source]
|
||||
|
||||
minetest.register_alias_force("mapgen_water_source", "static_ocean:water_source")--replace mapgen water with static water (not effected by dynamic_liquid)
|
||||
|
||||
if waterworks then
|
||||
waterworks.register_liquid("static_ocean:water_source", {flowing = "static_ocean:water_flowing", replace = "default:water_source"})
|
||||
end
|
||||
|
||||
minetest.register_abm({
|
||||
label = "delete normal water near ocean",
|
||||
nodenames = {"static_ocean:water_source"},
|
||||
neighbors = {"default:water_source"},
|
||||
interval = 4,
|
||||
chance = 1,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
local pos1 = vector.subtract(pos, 1)
|
||||
local pos2 = vector.add(pos, 1)
|
||||
local nodes = minetest.find_nodes_in_area(pos1, pos2, "default:water_source")
|
||||
for index, nodepos in pairs(nodes) do
|
||||
minetest.remove_node(nodepos)
|
||||
end
|
||||
end,
|
||||
})
|
2
mods/waterworks/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
41
mods/waterworks/.gitignore
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# Compiled Lua sources
|
||||
luac.out
|
||||
|
||||
# luarocks build files
|
||||
*.src.rock
|
||||
*.zip
|
||||
*.tar.gz
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.os
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
*.def
|
||||
*.exp
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
21
mods/waterworks/LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 FaceDeer
|
||||
|
||||
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.
|
56
mods/waterworks/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
This mod provides a set of nodes that allow large quantities of water to be pumped from one place to another, potentially over very long distances.
|
||||
|
||||
**Important Note:** The default behaviour of water in Minetest does not actually lend itself well to this kind of activity. In particular, ``default:water_source`` has ``liquid_renewable = true`` set in its node definition, which often causes new water nodes to be created where old ones are removed.
|
||||
|
||||
This mod includes an optional setting that disables ``liquid_renewable`` for default water, but for best effect it is highly recommended that this mod be used in conjunction with the [dynamic_liquid](https://github.com/minetest-mods/dynamic_liquid) mod.
|
||||
|
||||
The [airtanks](https://github.com/minetest-mods/airtanks) mod can also be helpful for players who are interested in doing large-scale underwater construction projects.
|
||||
|
||||
A pipe network is only active when a player is near one of the terminal nodes attached to it. The map blocks containing all terminal nodes for the network will be forceloaded as long as the pipe network is active, allowing water to be added or removed from remote locations, but note that water will be unable to flow into or out from those remote map blocks into adjoining blocks so it may behoove a player to visit these places from time to time to ensure continued flow.
|
||||
|
||||
## Pipes
|
||||
|
||||
The core node type introduced by this mod is the pipe. When pipes are laid adjacent to each other they connect up to form a pipe network, to which inlets, outlets, and pumps can be connected. All contiguous pipes are part of the same network, and all terminal nodes connected to that network will be able to interact with each other through the pipes.
|
||||
|
||||
Pipes automatically connect to other pipes through any of their six faces.
|
||||
|
||||
## Terminals
|
||||
|
||||
Terminals can only be connected to a pipe network via one face, the side that by default is facing away from the player when they place the node in world. They interact with water only on the opposite face - the one facing toward the player when they place the node in world. Terminals require at least one pipe segment to connect to, they don't interact directly with each other.
|
||||
|
||||
A screwdriver can be used to reorient terminals if you want one facing upward or downward.
|
||||
|
||||
The types of terminals in this mod are:
|
||||
|
||||
* Inlets let water enter the pipe but not leave
|
||||
* Outlets let water out but not in
|
||||
* Grates let water flow either way depending on pressure
|
||||
* Pumps are inlets that force water into the network at a higher pressure than their elevation would normally give it.
|
||||
|
||||
## Valves
|
||||
|
||||
A valve can be used to connect or disconnect sections of a pipe network with a simple right-click. When a valve is "open" it acts like a pipe section, and when it's "closed" it does not act like a pipe.
|
||||
|
||||
## Elevation and pressure
|
||||
|
||||
The flow of water through the network is determined by two main factors; the directionality of each type of terminal, and the pressure of the water at that terminal.
|
||||
|
||||
Water flows from high pressure terminals to low pressure terminals. The rise and fall of the pipe in between those two terminals doesn't really matter, just the pressure at the terminals themselves.
|
||||
|
||||
The following figure illustrates the basics of how this works with a very simple three-terminal pipe network:
|
||||
|
||||

|
||||
|
||||
The two terminals on the left side are "inlets", only permitting water to enter the network, and the terminal on the right is a grate that allows water in or out.
|
||||
|
||||
If terminal 1 were to be immersed in water, water nodes would be transferred from terminal 1 to terminal 2 because terminal 1's higher elevation gives it higher pressure than terminal 2. Water would *not* be transferred to terminal 3 as terminal 3 is an inlet only.
|
||||
|
||||
If terminal 2 were to be immersed, likewise no water would be transferred because although terminal 2 can allow water to enter the pipe (it's a grate) there are no valid outlet terminals it could go to.
|
||||
|
||||
If terminal 3 were immersed in water, no water would be transferred because terminal 2 is higher elevation and therefore there isn't enough pressure at terminal 3 to reach it.
|
||||
|
||||

|
||||
|
||||
In this example terminal 3 is a pump, which acts as if it were an inlet located at an elevation 100 meters higher than it actually is. There are two potential outlets for water entering the system. Water is preferentially emitted from the *lowest* pressure outlet, so if terminal 3 was immersed in water it would be sent to terminal 2. However, if terminal 2 was contained in an enclosed space that had run out of room for additional water, the water would then be sent to the next-lowest outlet and come out of terminal 1.
|
||||
|
||||
If terminal 1 was immersed, then water would transfer from it to terminal 2. Terminal 3 is an inlet, so water wouldn't come out of it.
|
92
mods/waterworks/crafting.lua
Normal file
@ -0,0 +1,92 @@
|
||||
--"waterworks:pipe"
|
||||
if minetest.get_modpath("pipeworks") then
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:pipe 12",
|
||||
recipe = {
|
||||
{ "default:steel_ingot", "", "default:steel_ingot" },
|
||||
{ "default:steel_ingot", "", "default:steel_ingot" },
|
||||
{ "default:steel_ingot", "", "default:steel_ingot" }
|
||||
},
|
||||
})
|
||||
else
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:pipe 12",
|
||||
recipe = {
|
||||
{ "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" },
|
||||
{ "", "", "" },
|
||||
{ "default:steel_ingot", "default:steel_ingot", "default:steel_ingot" }
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
--"waterworks:valve_on"
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:valve_on",
|
||||
recipe = {
|
||||
{ "default:steel_ingot", "", "default:steel_ingot" },
|
||||
{ "", "waterworks:pipe", "" },
|
||||
{ "default:steel_ingot", "", "default:steel_ingot" }
|
||||
},
|
||||
})
|
||||
|
||||
--"waterworks:inlet"
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:inlet",
|
||||
recipe = {
|
||||
{ "default:steel_ingot", "", "" },
|
||||
{ "", "waterworks:pipe", "" },
|
||||
{ "default:steel_ingot", "", "" }
|
||||
},
|
||||
})
|
||||
--"waterworks:pumped_inlet"
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:pumped_inlet",
|
||||
recipe = {
|
||||
{ "default:steel_ingot", "", "" },
|
||||
{ "default:mese_crystal_fragment", "waterworks:pipe", "" },
|
||||
{ "default:steel_ingot", "", "" }
|
||||
},
|
||||
})
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:pumped_inlet",
|
||||
recipe = {
|
||||
{ "default:mese_crystal_fragment", "waterworks:inlet"},
|
||||
},
|
||||
})
|
||||
|
||||
--"waterworks:outlet"
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:outlet",
|
||||
recipe = {
|
||||
{ "", "", "default:steel_ingot" },
|
||||
{ "", "waterworks:pipe", "" },
|
||||
{ "", "", "default:steel_ingot" }
|
||||
},
|
||||
})
|
||||
|
||||
--"waterworks:grate"
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:grate",
|
||||
recipe = {
|
||||
{ "", "default:steel_ingot", "" },
|
||||
{ "", "waterworks:pipe", "" },
|
||||
{ "", "default:steel_ingot", "" }
|
||||
},
|
||||
})
|
||||
|
||||
-- Allow the basic connectors to be cycled through
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:inlet",
|
||||
type = "shapeless",
|
||||
recipe = { "waterworks:outlet"},
|
||||
})
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:outlet",
|
||||
type = "shapeless",
|
||||
recipe = {"waterworks:grate"},
|
||||
})
|
||||
minetest.register_craft( {
|
||||
output = "waterworks:grate",
|
||||
type = "shapeless",
|
||||
recipe = {"waterworks:inlet"},
|
||||
})
|
1
mods/waterworks/depends.txt
Normal file
@ -0,0 +1 @@
|
||||
default
|
209
mods/waterworks/execute.lua
Normal file
@ -0,0 +1,209 @@
|
||||
local pressure_margin = 20
|
||||
|
||||
local pipe_cache = {}
|
||||
|
||||
local cardinal_dirs = {
|
||||
{x= 0, y=0, z= 1},
|
||||
{x= 1, y=0, z= 0},
|
||||
{x= 0, y=0, z=-1},
|
||||
{x=-1, y=0, z= 0},
|
||||
{x= 0, y=-1, z= 0},
|
||||
{x= 0, y=1, z= 0},
|
||||
}
|
||||
|
||||
local sort_by_pressure = function(first, second)
|
||||
local first_pressure = first.pressure
|
||||
local second_pressure = second.pressure
|
||||
if first_pressure == nil or second_pressure == nil then
|
||||
minetest.log("error", "[waterworks] attempted to sort something by pressure that had no pressure value: " .. dump(first) .. "\n" .. dump(second))
|
||||
return
|
||||
end
|
||||
|
||||
return first_pressure > second_pressure
|
||||
end
|
||||
|
||||
local valid_sink = function(node_name)
|
||||
return node_name == "air" or node_name == "default:water_flowing" or node_name == "static_ocean:water_flowing"
|
||||
end
|
||||
local valid_source = function(node_name)
|
||||
return waterworks.registered_liquids[node_name] ~= nil
|
||||
end
|
||||
|
||||
-- breadth-first search passing through water searching for air or flowing water, limited to y <= pressure.
|
||||
-- I could try to be fancy about water flowing downward preferentially, let's leave that as a TODO for now.
|
||||
local flood_search_outlet = function(start_pos, pressure)
|
||||
local start_node = minetest.get_node(start_pos)
|
||||
local start_node_name = start_node.name
|
||||
if valid_sink(start_node_name) then
|
||||
return start_pos
|
||||
end
|
||||
|
||||
local visited = {}
|
||||
visited[minetest.hash_node_position(start_pos)] = true
|
||||
local queue = {start_pos}
|
||||
local queue_pointer = 1
|
||||
|
||||
while #queue >= queue_pointer do
|
||||
local current_pos = queue[queue_pointer]
|
||||
queue_pointer = queue_pointer + 1
|
||||
for _, cardinal_dir in ipairs(cardinal_dirs) do
|
||||
local new_pos = vector.add(current_pos, cardinal_dir)
|
||||
local new_hash = minetest.hash_node_position(new_pos)
|
||||
if visited[new_hash] == nil and new_pos.y <= pressure then
|
||||
local new_node = minetest.get_node(new_pos)
|
||||
local new_node_name = new_node.name
|
||||
if valid_sink(new_node_name) then
|
||||
return new_pos
|
||||
end
|
||||
visited[new_hash] = true
|
||||
if valid_source(new_node_name) then
|
||||
table.insert(queue, new_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local upward_dirs = {
|
||||
{x= 0, y=0, z= 1},
|
||||
{x= 1, y=0, z= 0},
|
||||
{x= 0, y=0, z=-1},
|
||||
{x=-1, y=0, z= 0},
|
||||
{x= 0, y=1, z= 0},
|
||||
}
|
||||
|
||||
local shuffle = function(tbl)
|
||||
for i = #tbl, 2, -1 do
|
||||
local rand = math.random(i)
|
||||
tbl[i], tbl[rand] = tbl[rand], tbl[i]
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
-- depth-first random-walk search trending in an upward direction, returns when it gets cornered
|
||||
local find_source = function(start_pos)
|
||||
local current_node = minetest.get_node(start_pos)
|
||||
local current_node_name = current_node.name
|
||||
if not valid_source(current_node_name) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local visited = {[minetest.hash_node_position(start_pos)] = true}
|
||||
local current_pos = start_pos
|
||||
|
||||
local continue = true
|
||||
while continue do
|
||||
continue = false
|
||||
shuffle(upward_dirs)
|
||||
for _, dir in ipairs(upward_dirs) do
|
||||
local next_pos = vector.add(current_pos, dir)
|
||||
local next_hash = minetest.hash_node_position(next_pos)
|
||||
if visited[next_hash] == nil then
|
||||
visited[next_hash] = true
|
||||
local next_node = minetest.get_node(next_pos)
|
||||
local next_node_name = next_node.name
|
||||
if valid_source(next_node_name) then
|
||||
current_pos = next_pos
|
||||
continue = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return current_pos
|
||||
end
|
||||
|
||||
|
||||
waterworks.execute_pipes = function(net_index, net_capacity)
|
||||
local net = waterworks.pipe_networks[net_index]
|
||||
if net == nil then
|
||||
minetest.log("error", "[waterworks] Invalid net index given to execute: " .. tostring(net_index))
|
||||
return
|
||||
end
|
||||
|
||||
local inlets
|
||||
local outlets
|
||||
|
||||
if net.cache_valid then
|
||||
-- We don't need to recalculate, nothing about the pipe network has changed since last time
|
||||
inlets = pipe_cache[net_index].inlets
|
||||
outlets = pipe_cache[net_index].outlets
|
||||
else
|
||||
-- Find all the inlets and outlets and sort them by pressure
|
||||
inlets = {}
|
||||
if net.connected.inlet ~= nil then
|
||||
for _, inlet_set in pairs(net.connected.inlet) do
|
||||
for _, inlet in pairs(inlet_set) do
|
||||
table.insert(inlets, inlet)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(inlets, sort_by_pressure)
|
||||
|
||||
outlets = {}
|
||||
if net.connected.outlet ~= nil then
|
||||
for _, outlet_set in pairs(net.connected.outlet) do
|
||||
for _, outlet in pairs(outlet_set) do
|
||||
table.insert(outlets, outlet)
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(outlets, sort_by_pressure)
|
||||
|
||||
-- Cache the results
|
||||
pipe_cache[net_index] = {}
|
||||
pipe_cache[net_index].inlets = inlets
|
||||
pipe_cache[net_index].outlets = outlets
|
||||
|
||||
net.cache_valid = true
|
||||
end
|
||||
|
||||
local inlet_index = 1
|
||||
local outlet_index = #outlets
|
||||
local inlet_count = #inlets
|
||||
|
||||
local count = 0
|
||||
|
||||
-- Starting with the highest-pressure inlet and the lowest-pressure outlet, attempt to move water.
|
||||
-- We then proceed to steadily lower-pressure inlets and higher-pressure outlets until we meet in the middle, at which point
|
||||
-- the system is in equilibrium.
|
||||
while inlet_index <= inlet_count and outlet_index > 0 and count < net_capacity do
|
||||
local source = inlets[inlet_index]
|
||||
local sink = outlets[outlet_index]
|
||||
|
||||
--minetest.debug("source: " .. dump(source))
|
||||
--minetest.debug("sink: " .. dump(sink))
|
||||
|
||||
-- pressure_margin allows us to check sources that are a little bit below sinks,
|
||||
-- in case the extra pressure from their water depth is sufficient to force water through
|
||||
if source.pressure + pressure_margin >= sink.pressure then
|
||||
local source_pos = find_source(source.target)
|
||||
local sink_pos
|
||||
if source_pos ~= nil then
|
||||
sink_pos = flood_search_outlet(sink.target, math.max(source.pressure, source_pos.y))
|
||||
if sink_pos ~= nil then
|
||||
local source_node = minetest.get_node(source_pos).name
|
||||
local source_def = waterworks.registered_liquids[source_node]
|
||||
if source_def and source_def.replace then
|
||||
source_node = source_def.replace
|
||||
end
|
||||
minetest.swap_node(sink_pos, {name=source_node})
|
||||
minetest.swap_node(source_pos, {name="air"})
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
if source_pos == nil then
|
||||
-- the outlet had available space but the inlet didn't provide
|
||||
inlet_index = inlet_index + 1
|
||||
elseif sink_pos == nil then
|
||||
-- the inlet provided but the outlet didn't have space
|
||||
outlet_index = outlet_index - 1
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
112
mods/waterworks/globalstep.lua
Normal file
@ -0,0 +1,112 @@
|
||||
local worldpath = minetest.get_worldpath()
|
||||
local network_filename = worldpath.."/waterworks_network.json"
|
||||
|
||||
-- Json storage
|
||||
|
||||
local save_data = function()
|
||||
if waterworks.dirty_data ~= true then
|
||||
return
|
||||
end
|
||||
local file = io.open(network_filename, "w")
|
||||
if file then
|
||||
file:write(minetest.serialize(waterworks.pipe_networks))
|
||||
file:close()
|
||||
waterworks.dirty_data = false
|
||||
end
|
||||
end
|
||||
|
||||
local read_data = function()
|
||||
local file = io.open(network_filename, "r")
|
||||
if file then
|
||||
waterworks.pipe_networks = minetest.deserialize(file:read("*all")) -- note: any cached references to pipe_networks is invalidated here, so do this once at the beginning of the run and never again thereafter.
|
||||
file:close()
|
||||
else
|
||||
waterworks.pipe_networks = {}
|
||||
end
|
||||
waterworks.dirty_data = false
|
||||
for _, net in ipairs(waterworks.pipe_networks) do
|
||||
net.cache_valid = false
|
||||
end
|
||||
end
|
||||
|
||||
read_data()
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
local nets_near_players = {}
|
||||
|
||||
minetest.register_abm ({
|
||||
label = "Active connected node tracking",
|
||||
nodenames = {"group:waterworks_connected"},
|
||||
interval = 1.0,
|
||||
chance = 1,
|
||||
action = function(pos, node, active_object_count, active_object_count_wider)
|
||||
local player_close = false
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local player_pos = player:get_pos()
|
||||
if math.abs(player_pos.x - pos.x) < 161 and math.abs(player_pos.z - pos.z) < 161 and math.abs(player_pos.y - pos.y) < 161 then
|
||||
player_close = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not player_close then return end
|
||||
|
||||
local hash = minetest.hash_node_position(pos) + waterworks.facedir_to_hash(node.param2)
|
||||
local net_index = waterworks.find_network_for_pipe_hash(hash)
|
||||
if net_index < 0 then return end
|
||||
--minetest.chat_send_all("net near player " .. tostring(net_index))
|
||||
nets_near_players[net_index] = 5.0
|
||||
end,
|
||||
})
|
||||
|
||||
local forceloads = {}
|
||||
local timer = 0
|
||||
minetest.register_globalstep(function(dtime)
|
||||
timer = timer + dtime
|
||||
if timer > 1.0 then
|
||||
|
||||
if waterworks.dirty_data then
|
||||
-- it's possible that a pipe network was split or merged, invalidating the nets_near_players values here.
|
||||
-- Best to clear them and do nothing for one globalstep, they'll be repopulated shortly.
|
||||
nets_near_players = {}
|
||||
end
|
||||
|
||||
-- find connected node positions for all networks with connected nodes near players
|
||||
local ensure_forceload = {}
|
||||
for index, live_time in pairs(nets_near_players) do
|
||||
local new_time = live_time - timer
|
||||
--minetest.chat_send_all("new time " .. tostring(new_time))
|
||||
if new_time < 0 then
|
||||
nets_near_players[index] = nil
|
||||
else
|
||||
nets_near_players[index] = new_time
|
||||
for connection_type, connections in pairs(waterworks.pipe_networks[index].connected) do
|
||||
for hash, _ in pairs(connections) do
|
||||
ensure_forceload[hash] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- clear forceloads that are no longer needed
|
||||
for hash, _ in pairs(forceloads) do
|
||||
if not ensure_forceload[hash] then
|
||||
minetest.forceload_free_block(minetest.get_position_from_hash(hash), true)
|
||||
end
|
||||
end
|
||||
forceloads = ensure_forceload
|
||||
-- enable forceloads that are needed
|
||||
for hash, _ in pairs(forceloads) do
|
||||
minetest.forceload_block(minetest.get_position_from_hash(hash), true)
|
||||
end
|
||||
|
||||
timer = timer - 1.0
|
||||
save_data()
|
||||
for index, _ in pairs(nets_near_players) do
|
||||
--minetest.chat_send_all("executing index " .. tostring(index))
|
||||
waterworks.execute_pipes(index, 8)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
78
mods/waterworks/init.lua
Normal file
@ -0,0 +1,78 @@
|
||||
waterworks = {}
|
||||
|
||||
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
waterworks.registered_liquids = {}
|
||||
waterworks.register_liquid = function(name, def)
|
||||
if not def then def = {} else
|
||||
if not def.replace then
|
||||
minetest.override_item(name, {liquid_renewable = false})
|
||||
if def.flowing then
|
||||
minetest.override_item(def.flowing, {liquid_renewable = false})
|
||||
end
|
||||
end
|
||||
end
|
||||
waterworks.registered_liquids[name] = def
|
||||
end
|
||||
|
||||
waterworks.register_liquid("default:water_source", {flowing = "default:water_flowing"})
|
||||
|
||||
dofile(modpath .. "/globalstep.lua")
|
||||
dofile(modpath .. "/network.lua")
|
||||
dofile(modpath .. "/execute.lua")
|
||||
dofile(modpath .. "/nodes.lua")
|
||||
dofile(modpath .. "/crafting.lua")
|
||||
|
||||
-- This rebuilds pipe networks whenever the map block is loaded.
|
||||
-- May be useful for fixing broken stuff.
|
||||
-- Note that this doesn't *remove* pipes that are inappropriately listed in the network,
|
||||
-- that may be tricky.
|
||||
--minetest.register_lbm({
|
||||
-- label = "Validate waterworks pipe networks",
|
||||
-- name = "waterworks:validate_pipe_networks",
|
||||
-- nodenames = {"group:waterworks_pipe"},
|
||||
-- run_at_every_load = true,
|
||||
-- action = function(pos, node)
|
||||
-- local hash_pos = minetest.hash_node_position(pos)
|
||||
-- local found = false
|
||||
-- for i, net in ipairs(waterworks.pipe_networks) do
|
||||
-- if net.pipes[hash_pos] then
|
||||
-- found = true
|
||||
-- break
|
||||
-- end
|
||||
-- end
|
||||
-- if not found then
|
||||
-- local new_net_index = waterworks.place_pipe(pos)
|
||||
-- minetest.log("warning", "[waterworks] detected an unregistered pipe at " ..
|
||||
-- minetest.pos_to_string(pos) .. ", added it to pipe network " ..
|
||||
-- tostring(new_net_index))
|
||||
-- end
|
||||
-- end,
|
||||
--})
|
||||
|
||||
--minetest.register_chatcommand("dump_pipes", {
|
||||
---- params = "pos", -- Short parameter description
|
||||
-- description = "dump pipe network to debug log",
|
||||
-- func = function(name, param)
|
||||
-- --minetest.debug(dump(pipe_networks))
|
||||
--
|
||||
-- for i, net in ipairs(waterworks.pipe_networks) do
|
||||
-- minetest.debug("net #" .. tostring(i) .. " cache valid: " .. dump(net.cache_valid))
|
||||
-- minetest.debug("\tpipes:")
|
||||
-- local count = 0
|
||||
-- for hash_pos, _ in pairs(net.pipes) do
|
||||
-- minetest.debug("\t\t"..minetest.pos_to_string(minetest.get_position_from_hash(hash_pos)))
|
||||
-- count = count + 1
|
||||
-- end
|
||||
-- minetest.debug("\tpipe count: " .. tostring(count))
|
||||
-- minetest.debug("\tconnections:")
|
||||
-- for connection_type, connection_list in pairs(net.connected) do
|
||||
-- minetest.debug("\t\t"..connection_type..":")
|
||||
-- for connection_hash, data in pairs(connection_list) do
|
||||
-- minetest.debug("\t\t\t"..minetest.pos_to_string(minetest.get_position_from_hash(connection_hash))..": "..string.gsub(string.gsub(dump(data), "\t", ""), "\n", " "))
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- end,
|
||||
--})
|
3
mods/waterworks/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = waterworks
|
||||
description = Provides pipes, pumps, and valves for moving water over long distances
|
||||
depends = default
|
386
mods/waterworks/network.lua
Normal file
@ -0,0 +1,386 @@
|
||||
local pipe_networks = waterworks.pipe_networks
|
||||
|
||||
local invalidate_cache = function(pipe_network)
|
||||
pipe_network.cache_valid = false
|
||||
waterworks.dirty_data = true
|
||||
end
|
||||
|
||||
local cardinal_dirs = {
|
||||
{x= 0, y=0, z= 1},
|
||||
{x= 1, y=0, z= 0},
|
||||
{x= 0, y=0, z=-1},
|
||||
{x=-1, y=0, z= 0},
|
||||
{x= 0, y=-1, z= 0},
|
||||
{x= 0, y=1, z= 0},
|
||||
}
|
||||
-- Mapping from facedir value to index in cardinal_dirs.
|
||||
local facedir_to_dir_map = {
|
||||
[0]=1, 2, 3, 4,
|
||||
5, 2, 6, 4,
|
||||
6, 2, 5, 4,
|
||||
1, 5, 3, 6,
|
||||
1, 6, 3, 5,
|
||||
1, 4, 3, 2,
|
||||
}
|
||||
|
||||
-- Turn the cardinal directions into a set of integers you can add to a hash to step in that direction.
|
||||
local cardinal_dirs_hash = {}
|
||||
for i, dir in ipairs(cardinal_dirs) do
|
||||
cardinal_dirs_hash[i] = minetest.hash_node_position(dir) - minetest.hash_node_position({x=0, y=0, z=0})
|
||||
end
|
||||
|
||||
local facedir_to_dir_index = function(param2)
|
||||
return facedir_to_dir_map[param2 % 32]
|
||||
end
|
||||
|
||||
local facedir_to_cardinal_hash = function(dir_index)
|
||||
return cardinal_dirs_hash[dir_index]
|
||||
end
|
||||
|
||||
waterworks.facedir_to_hash = function(param2)
|
||||
return facedir_to_cardinal_hash(facedir_to_dir_index(param2))
|
||||
end
|
||||
|
||||
local init_new_network = function(hash_pos)
|
||||
waterworks.dirty_data = true
|
||||
return {pipes = {[hash_pos] = true}, connected = {}, cache_valid = false}
|
||||
end
|
||||
|
||||
local get_neighbor_pipes = function(pos)
|
||||
local neighbor_pipes = {}
|
||||
local neighbor_connected = {}
|
||||
for _, dir in ipairs(cardinal_dirs) do
|
||||
local potential_pipe_pos = vector.add(pos, dir)
|
||||
local neighbor = minetest.get_node(potential_pipe_pos)
|
||||
if minetest.get_item_group(neighbor.name, "waterworks_pipe") > 0 then
|
||||
table.insert(neighbor_pipes, potential_pipe_pos)
|
||||
elseif minetest.get_item_group(neighbor.name, "waterworks_connected") > 0 then
|
||||
table.insert(neighbor_connected, potential_pipe_pos)
|
||||
end
|
||||
end
|
||||
return neighbor_pipes, neighbor_connected
|
||||
end
|
||||
|
||||
local merge_networks = function(index_list)
|
||||
table.sort(index_list)
|
||||
local first_index = table.remove(index_list, 1)
|
||||
local merged_network = pipe_networks[first_index]
|
||||
-- remove in reverse order so that indices of earlier tables to remove don't get disrupted
|
||||
for i = #index_list, 1, -1 do
|
||||
local index = index_list[i]
|
||||
local net_to_merge = pipe_networks[index]
|
||||
for pipe_hash, _ in pairs(net_to_merge.pipes) do
|
||||
merged_network.pipes[pipe_hash] = true
|
||||
end
|
||||
for item_type, item_list in pairs(net_to_merge.connected) do
|
||||
merged_network.connected[item_type] = merged_network.connected[item_type] or {}
|
||||
for connection_hash, connection_data in pairs(item_list) do
|
||||
merged_network.connected[item_type][connection_hash] = connection_data
|
||||
end
|
||||
end
|
||||
table.remove(pipe_networks, index)
|
||||
end
|
||||
invalidate_cache(merged_network)
|
||||
return first_index
|
||||
end
|
||||
|
||||
local handle_connected = function(connected_positions)
|
||||
for _, pos in ipairs(connected_positions) do
|
||||
local node = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node.name]
|
||||
if node_def._waterworks_update_connected then
|
||||
node_def._waterworks_update_connected(pos)
|
||||
else
|
||||
minetest.log("error", "[waterworks] Node def for " .. node.name .. " had no _waterworks_update_connected defined")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- When placing a pipe at pos, identifies what pipe network to add it to and updates the network map.
|
||||
-- Note that this can result in fusing multiple networks together into one network.
|
||||
waterworks.place_pipe = function(pos)
|
||||
local hash_pos = minetest.hash_node_position(pos)
|
||||
local neighbor_pipes, neighbor_connected = get_neighbor_pipes(pos)
|
||||
local neighbor_count = #neighbor_pipes
|
||||
|
||||
if neighbor_count == 0 then
|
||||
-- this newly-placed pipe has no other pipes next to it, so make a new network for it.
|
||||
local new_net = init_new_network(hash_pos)
|
||||
table.insert(pipe_networks, new_net)
|
||||
handle_connected(neighbor_connected)
|
||||
return #pipe_networks
|
||||
elseif neighbor_count == 1 then
|
||||
-- there's only one pipe neighbor. Look up what network it belongs to and add this pipe to it too.
|
||||
local neighbor_pos_hash = minetest.hash_node_position(neighbor_pipes[1])
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
local pipes = net.pipes
|
||||
if pipes[neighbor_pos_hash] then
|
||||
pipes[hash_pos] = true
|
||||
invalidate_cache(net)
|
||||
handle_connected(neighbor_connected)
|
||||
return i
|
||||
end
|
||||
end
|
||||
else
|
||||
local neighbor_index_set = {} -- set of indices for networks that neighbors belong to
|
||||
local neighbor_index_list = {} -- list version of above
|
||||
for _, neighbor_pos in ipairs(neighbor_pipes) do
|
||||
local neighbor_hash = minetest.hash_node_position(neighbor_pos)
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
if net.pipes[neighbor_hash] then
|
||||
if not neighbor_index_set[i] then
|
||||
table.insert(neighbor_index_list, i)
|
||||
neighbor_index_set[i] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #neighbor_index_list == 1 then -- all neighbors belong to one network. Add this node to that network.
|
||||
local target_network_index = neighbor_index_list[1]
|
||||
pipe_networks[target_network_index]["pipes"][hash_pos] = true
|
||||
invalidate_cache(pipe_networks[target_network_index])
|
||||
handle_connected(neighbor_connected)
|
||||
return target_network_index
|
||||
end
|
||||
|
||||
-- The most complicated case, this new pipe segment bridges multiple networks.
|
||||
if #neighbor_index_list > 1 then
|
||||
local new_index = merge_networks(neighbor_index_list)
|
||||
pipe_networks[new_index]["pipes"][hash_pos] = true
|
||||
handle_connected(neighbor_connected)
|
||||
return new_index
|
||||
end
|
||||
end
|
||||
|
||||
-- if we get here we're in a strange state - there are neighbor pipe nodes but none are registered in a network.
|
||||
-- We could be trying to recover from corruption, so pretend the neighbors don't exist and start a new network.
|
||||
-- The unregistered neighbors may join it soon.
|
||||
local new_net = init_new_network(hash_pos)
|
||||
table.insert(pipe_networks, new_net)
|
||||
handle_connected(neighbor_connected)
|
||||
return #pipe_networks
|
||||
|
||||
end
|
||||
|
||||
waterworks.remove_pipe = function(pos)
|
||||
local hash_pos = minetest.hash_node_position(pos)
|
||||
local neighbor_pipes = get_neighbor_pipes(pos)
|
||||
local neighbor_count = #neighbor_pipes
|
||||
|
||||
if neighbor_count == 0 then
|
||||
-- no neighbors, so this is the last of its network.
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
if net.pipes[hash_pos] then
|
||||
table.remove(pipe_networks, i)
|
||||
waterworks.dirty_data = true
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
minetest.log("error", "[waterworks] pipe removed from pos " .. minetest.pos_to_string(pos) ..
|
||||
" didn't belong to any networks. Something went wrong to get to this state.")
|
||||
return -1
|
||||
elseif neighbor_count == 1 then
|
||||
-- there's only one pipe neighbor. This pipe is at the end of a line, so just remove it.
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
local pipes = net.pipes
|
||||
if pipes[hash_pos] then
|
||||
pipes[hash_pos] = nil
|
||||
invalidate_cache(net)
|
||||
-- If there's anything connected to the pipe here, remove it from the network too
|
||||
for _, connected_items in pairs(net.connected) do
|
||||
connected_items[hash_pos] = nil
|
||||
end
|
||||
return i
|
||||
end
|
||||
end
|
||||
minetest.log("error", "[waterworks] pipe removed from pos " .. minetest.pos_to_string(pos) ..
|
||||
" didn't belong to any networks, despite being neighbor to one at " ..
|
||||
minetest.pos_to_string(neighbor_pipes[1]) ..
|
||||
". Something went wrong to get to this state.")
|
||||
return -1
|
||||
else
|
||||
-- we may be splitting networks. This is complicated.
|
||||
-- find the network we currently belong to. Remove ourselves from it.
|
||||
local old_net
|
||||
local old_pipes
|
||||
local old_connected
|
||||
local old_index
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
local pipes = net.pipes
|
||||
if pipes[hash_pos] then
|
||||
old_connected = net.connected
|
||||
old_net = net
|
||||
old_pipes = pipes
|
||||
old_index = i
|
||||
old_pipes[hash_pos] = nil
|
||||
-- if there's anything connected to the pipe here, remove it
|
||||
for _, connected_items in pairs(old_connected) do
|
||||
connected_items[hash_pos] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if old_index == nil then
|
||||
minetest.log("error", "[waterworks] pipe removed from pos " .. minetest.pos_to_string(pos) ..
|
||||
" didn't belong to any networks, despite being neighbor to several. Something went wrong to get to this state.")
|
||||
return -1
|
||||
end
|
||||
|
||||
-- get the hashes of the neighbor positions.
|
||||
-- We're maintaining a set as well as a list because they're
|
||||
-- efficient for different purposes. The list is easy to count,
|
||||
-- the set is easy to test membership of.
|
||||
local neighbor_hashes_list = {}
|
||||
local neighbor_hashes_set = {}
|
||||
for i, neighbor_pos in ipairs(neighbor_pipes) do
|
||||
local neighbor_hash = minetest.hash_node_position(neighbor_pos)
|
||||
neighbor_hashes_list[i] = neighbor_hash
|
||||
neighbor_hashes_set[neighbor_hash] = true
|
||||
end
|
||||
|
||||
-- We're going to need to traverse through the old network, starting from each of our neighbors,
|
||||
-- to establish what's still connected.
|
||||
local to_visit = {}
|
||||
local visited = {[hash_pos] = true} -- set of hashes we've visited already. We know the starting point is not valid.
|
||||
local new_nets = {} -- this will be where we put new sets of connected nodes.
|
||||
while #neighbor_hashes_list > 0 do
|
||||
local current_neighbor = table.remove(neighbor_hashes_list) -- pop neighbor hash and push it into the to_visit list.
|
||||
neighbor_hashes_set[current_neighbor] = nil
|
||||
table.insert(to_visit, current_neighbor) -- file that neighbor hash as our starting point.
|
||||
local new_net = init_new_network(current_neighbor) -- we know that hash is in old_net, so initialize the new_net with it.
|
||||
local new_pipes = new_net.pipes
|
||||
while #to_visit > 0 do
|
||||
local current_hash = table.remove(to_visit)
|
||||
for _, cardinal_hash in ipairs(cardinal_dirs_hash) do
|
||||
local test_hash = cardinal_hash + current_hash
|
||||
if not visited[test_hash] then
|
||||
if old_pipes[test_hash] then
|
||||
-- we've traversed to a node that was in the old network
|
||||
old_pipes[test_hash] = nil -- remove from old network
|
||||
new_pipes[test_hash] = true -- add to one we're building
|
||||
table.insert(to_visit, test_hash) -- flag it as next one to traverse from
|
||||
if neighbor_hashes_set[test_hash] then
|
||||
--we've encountered another neighbor while traversing
|
||||
--eliminate it from future consideration as a starting point.
|
||||
neighbor_hashes_set[test_hash] = nil
|
||||
for i, neighbor_hash_in_list in ipairs(neighbor_hashes_list) do
|
||||
if neighbor_hash_in_list == test_hash then
|
||||
table.remove(neighbor_hashes_list, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
if #neighbor_hashes_list == 0 then
|
||||
--Huzzah! We encountered all neighbors. The rest of the nodes in old_net should belong to new_net.
|
||||
--We can skip all remaining pathfinding flood-fill and connected testing
|
||||
for remaining_hash, _ in pairs(old_pipes) do
|
||||
new_pipes[remaining_hash] = true
|
||||
to_visit = {}
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
visited[current_hash] = true
|
||||
end
|
||||
table.insert(new_nets, new_net)
|
||||
end
|
||||
|
||||
-- distribute connected items to the new nets
|
||||
if #new_nets == 1 then
|
||||
-- net didn't split, just keep the old stuff
|
||||
new_nets[1].connected = old_connected
|
||||
else
|
||||
for _, new_net in ipairs(new_nets) do
|
||||
local new_pipes = new_net.pipes
|
||||
for item_type, item_list in pairs(old_connected) do
|
||||
new_net.connected[item_type] = new_net.connected[item_type] or {}
|
||||
for connection_hash, connection_data in pairs(item_list) do
|
||||
if new_pipes[connection_hash] then
|
||||
new_net.connected[item_type][connection_hash] = connection_data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- replace the old net with one of the new nets
|
||||
pipe_networks[old_index] = table.remove(new_nets)
|
||||
-- if there are any additional nets left, add those as brand new ones.
|
||||
for _, new_net in ipairs(new_nets) do
|
||||
table.insert(pipe_networks, new_net)
|
||||
end
|
||||
return old_index
|
||||
end
|
||||
end
|
||||
|
||||
waterworks.place_connected = function(pos, item_type, data)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir_index = facedir_to_dir_index(node.param2)
|
||||
local dir_hash = facedir_to_cardinal_hash(dir_index)
|
||||
local pos_hash = minetest.hash_node_position(pos)
|
||||
local connection_hash = pos_hash + dir_hash
|
||||
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
if net.pipes[connection_hash] then
|
||||
net.connected[item_type] = net.connected[item_type] or {}
|
||||
net.connected[item_type][connection_hash] = net.connected[item_type][connection_hash] or {}
|
||||
net.connected[item_type][connection_hash][dir_index] = data
|
||||
invalidate_cache(net)
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
waterworks.remove_connected = function(pos, item_type)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir_index = facedir_to_dir_index(node.param2)
|
||||
local dir_hash = facedir_to_cardinal_hash(dir_index)
|
||||
local pos_hash = minetest.hash_node_position(pos)
|
||||
local connection_hash = pos_hash + dir_hash
|
||||
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
if net.pipes[connection_hash] then
|
||||
local item_list = net.connected[item_type]
|
||||
if item_list then
|
||||
if item_list[connection_hash] ~= nil then
|
||||
local connected_items = item_list[connection_hash]
|
||||
connected_items[dir_index] = nil
|
||||
local count = 0
|
||||
for _, data in pairs(connected_items) do
|
||||
count = count + 1
|
||||
end
|
||||
if count == 0 then
|
||||
item_list[connection_hash] = nil
|
||||
end
|
||||
count = 0
|
||||
for _, item in pairs(item_list) do
|
||||
count = count + 1
|
||||
end
|
||||
if count == 0 then
|
||||
net.connected[item_type] = nil
|
||||
end
|
||||
invalidate_cache(net)
|
||||
return i
|
||||
end
|
||||
end
|
||||
break -- If we get here, we didn't find the connected node even though we should have.
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
waterworks.find_network_for_pipe_hash = function(hash)
|
||||
for i, net in ipairs(pipe_networks) do
|
||||
if net.pipes[hash] then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return -1
|
||||
end
|
280
mods/waterworks/nodes.lua
Normal file
@ -0,0 +1,280 @@
|
||||
|
||||
minetest.register_node("waterworks:pipe", {
|
||||
description = "Waterworks Pipe",
|
||||
tiles = {
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets_offset.png", scale=4, align_style="world"},
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets_offset_2.png", scale=4, align_style="world"},
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets.png", scale=4, align_style="world"},
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets_offset_2.png", scale=4, align_style="world"},
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets.png", scale=4, align_style="world"},
|
||||
{name="waterworks_pipe.png^waterworks_pipe_rivets_offset_2.png", scale=4, align_style="world"},
|
||||
},
|
||||
connects_to = {"group:waterworks_pipe", "group:waterworks_connected", "group:waterworks_inert"},
|
||||
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "connected",
|
||||
fixed = {-0.25,-0.25,-0.25,0.25,0.25,0.25},
|
||||
connect_top = {-0.375, 0, -0.375, 0.375, 0.5, 0.375},
|
||||
connect_bottom = {-0.375, -0.5, -0.375, 0.375, 0, 0.375},
|
||||
connect_back = {-0.375, -0.375, 0, 0.375, 0.375, 0.5},
|
||||
connect_right = {0, -0.375, -0.375, 0.5, 0.375, 0.375},
|
||||
connect_front = {-0.375, -0.375, -0.5, 0.375, 0.375, 0},
|
||||
connect_left = {-0.5, -0.375, -0.375, 0, 0.375, 0.375},
|
||||
disconnected = {-0.375,-0.375,-0.375,0.375,0.375,0.375},
|
||||
},
|
||||
paramtype = "light",
|
||||
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_pipe = 1},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
on_construct = function(pos)
|
||||
waterworks.place_pipe(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_pipe(pos)
|
||||
end,
|
||||
})
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
minetest.register_node("waterworks:valve_on", {
|
||||
description = "Waterworks Valve (open)",
|
||||
tiles = {"waterworks_metal.png^waterworks_valve_seam.png^waterworks_valve_on.png",},
|
||||
connects_to = {"group:waterworks_pipe", "group:waterworks_connected", "group:waterworks_inert"},
|
||||
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "connected",
|
||||
fixed = {-0.4375,-0.4375,-0.4375,0.4375,0.4375,0.4375},
|
||||
connect_top = {-0.375, 0.4375, -0.375, 0.375, 0.5, 0.375},
|
||||
connect_bottom = {-0.375, -0.5, -0.375, 0.375, -0.4375, 0.375},
|
||||
connect_back = {-0.375, -0.375, 0.4375, 0.375, 0.375, 0.5},
|
||||
connect_right = {0.4375, -0.375, -0.375, 0.5, 0.375, 0.375},
|
||||
connect_front = {-0.375, -0.375, -0.5, 0.375, 0.375, -0.4375},
|
||||
connect_left = {-0.5, -0.375, -0.375, -0.4375, 0.375, 0.375},
|
||||
},
|
||||
paramtype = "light",
|
||||
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_pipe = 1},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
on_construct = function(pos)
|
||||
waterworks.place_pipe(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_pipe(pos)
|
||||
end,
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
node.name = "waterworks:valve_off"
|
||||
minetest.set_node(pos, node)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_node("waterworks:valve_off", {
|
||||
description = "Waterworks Valve (closed)",
|
||||
tiles = {"waterworks_metal.png^waterworks_valve_seam.png^waterworks_valve_off.png",},
|
||||
connects_to = {"group:waterworks_pipe", "group:waterworks_connected", "group:waterworks_inert"},
|
||||
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "connected",
|
||||
fixed = {-0.4375,-0.4375,-0.4375,0.4375,0.4375,0.4375},
|
||||
connect_top = {-0.375, 0.4375, -0.375, 0.375, 0.5, 0.375},
|
||||
connect_bottom = {-0.375, -0.5, -0.375, 0.375, -0.4375, 0.375},
|
||||
connect_back = {-0.375, -0.375, 0.4375, 0.375, 0.375, 0.5},
|
||||
connect_right = {0.4375, -0.375, -0.375, 0.5, 0.375, 0.375},
|
||||
connect_front = {-0.375, -0.375, -0.5, 0.375, 0.375, -0.4375},
|
||||
connect_left = {-0.5, -0.375, -0.375, -0.4375, 0.375, 0.375},
|
||||
},
|
||||
paramtype = "light",
|
||||
drops = "waterworks:valve_on",
|
||||
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_inert = 1, not_in_creative_inventory = 1},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
|
||||
node.name = "waterworks:valve_on"
|
||||
minetest.set_node(pos, node)
|
||||
end,
|
||||
})
|
||||
|
||||
-----------------------------------------------------------------
|
||||
|
||||
local place_inlet = function(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
local target = vector.subtract(pos, dir)
|
||||
waterworks.place_connected(pos, "inlet", {pos = pos, target = target, pressure = target.y})
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("infotext", "Inlet elevation " .. tostring(target.y))
|
||||
end
|
||||
minetest.register_node("waterworks:inlet", {
|
||||
description = "Waterworks Inlet",
|
||||
tiles = {
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png^waterworks_connected_back.png",
|
||||
"waterworks_metal.png^waterworks_inlet.png",
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_connected = 1},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
paramtype = "light",
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {{-0.375, -0.375, -0.375, 0.375, 0.375, 0.5}, {-0.5, -0.5, -0.5, 0.5, 0.5, -0.375}},
|
||||
},
|
||||
_waterworks_update_connected = place_inlet,
|
||||
on_construct = function(pos)
|
||||
place_inlet(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
end,
|
||||
on_rotate = function(pos, node, user, mode, new_param2)
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
node.param2 = new_param2
|
||||
minetest.swap_node(pos, node)
|
||||
place_inlet(pos)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
local place_pumped_inlet = function(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
local target = vector.subtract(pos, dir)
|
||||
waterworks.place_connected(pos, "inlet", {pos = pos, target = target, pressure = target.y + 100})
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("infotext", "Pump effective elevation " .. tostring(target.y + 100))
|
||||
end
|
||||
minetest.register_node("waterworks:pumped_inlet", {
|
||||
description = "Waterworks Pumped Inlet",
|
||||
tiles = {
|
||||
"waterworks_turbine_base.png",
|
||||
"waterworks_turbine_base.png",
|
||||
"waterworks_turbine_side.png^[transformFX",
|
||||
"waterworks_turbine_side.png",
|
||||
"waterworks_metal.png^waterworks_connected_back.png",
|
||||
"waterworks_turbine_base.png^waterworks_turbine.png",
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_connected = 1},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
paramtype = "light",
|
||||
drawtype = "normal",
|
||||
_waterworks_update_connected = place_pumped_inlet,
|
||||
on_construct = function(pos)
|
||||
place_pumped_inlet(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
end,
|
||||
on_rotate = function(pos, node, user, mode, new_param2)
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
node.param2 = new_param2
|
||||
minetest.swap_node(pos, node)
|
||||
place_pumped_inlet(pos)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
local place_outlet = function(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
local target = vector.subtract(pos, dir)
|
||||
waterworks.place_connected(pos, "outlet", {pos = pos, target = target, pressure = target.y})
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("infotext", "Outlet elevation " .. tostring(target.y))
|
||||
end
|
||||
minetest.register_node("waterworks:outlet", {
|
||||
description = "Waterworks Outlet",
|
||||
tiles = {
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png^waterworks_connected_back.png",
|
||||
"waterworks_metal.png^waterworks_outlet.png",
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_connected = 1},
|
||||
|
||||
paramtype = "light",
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {{-0.375, -0.375, -0.375, 0.375, 0.375, 0.5}, {-0.5, -0.5, -0.5, 0.5, 0.5, -0.375}},
|
||||
},
|
||||
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
_waterworks_update_connected = place_outlet,
|
||||
on_construct = function(pos)
|
||||
place_outlet(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_connected(pos, "outlet")
|
||||
end,
|
||||
on_rotate = function(pos, node, user, mode, new_param2)
|
||||
waterworks.remove_connected(pos, "outlet")
|
||||
node.param2 = new_param2
|
||||
minetest.swap_node(pos, node)
|
||||
place_outlet(pos)
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
local place_grate = function(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
local target = vector.subtract(pos, dir)
|
||||
waterworks.place_connected(pos, "outlet", {pos = pos, target = target, pressure = target.y})
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("infotext", "Grate elevation " .. tostring(target.y))
|
||||
end
|
||||
minetest.register_node("waterworks:grate", {
|
||||
description = "Waterworks Grate",
|
||||
tiles = {
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png",
|
||||
"waterworks_metal.png^waterworks_connected_back.png",
|
||||
"waterworks_metal.png^waterworks_grate.png",
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
is_ground_content = false,
|
||||
groups = {oddly_breakable_by_hand = 1, waterworks_connected = 1},
|
||||
|
||||
paramtype = "light",
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {{-0.375, -0.375, -0.375, 0.375, 0.375, 0.5}, {-0.5, -0.5, -0.5, 0.5, 0.5, -0.375}},
|
||||
},
|
||||
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
_waterworks_update_connected = place_outlet,
|
||||
on_construct = function(pos)
|
||||
place_outlet(pos)
|
||||
end,
|
||||
on_destruct = function(pos)
|
||||
waterworks.remove_connected(pos, "outlet")
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
end,
|
||||
on_rotate = function(pos, node, user, mode, new_param2)
|
||||
waterworks.remove_connected(pos, "outlet")
|
||||
waterworks.remove_connected(pos, "inlet")
|
||||
node.param2 = new_param2
|
||||
minetest.swap_node(pos, node)
|
||||
place_outlet(pos)
|
||||
return true
|
||||
end,
|
||||
})
|
BIN
mods/waterworks/screenshot.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
mods/waterworks/screenshots/waterworks_figure_1.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
mods/waterworks/screenshots/waterworks_figure_2.png
Normal file
After Width: | Height: | Size: 32 KiB |
1
mods/waterworks/settingtypes.txt
Normal file
@ -0,0 +1 @@
|
||||
waterworks_make_default_water_non_renewable (Disable self-replication of default water) bool false
|
17
mods/waterworks/textures/LICENSE.txt
Normal file
@ -0,0 +1,17 @@
|
||||
The following textures are licensed under the MIT and CC0 licenses by FaceDeer:
|
||||
|
||||
waterworks_connected_back
|
||||
waterworks_grate
|
||||
waterworks_inlet
|
||||
waterworks_metal
|
||||
waterworks_outlet
|
||||
waterworks_pipe
|
||||
waterworks_pipe_rivets
|
||||
waterworks_pipe_rivets_offset
|
||||
waterworks_pipe_rivets_offset_2
|
||||
waterworks_turbine
|
||||
waterworks_turbine_base
|
||||
waterworks_turbine_side
|
||||
waterworks_valve_off
|
||||
waterworks_valve_on
|
||||
waterworks_valve_seam
|
BIN
mods/waterworks/textures/waterworks_connected_back.png
Normal file
After Width: | Height: | Size: 675 B |
BIN
mods/waterworks/textures/waterworks_grate.png
Normal file
After Width: | Height: | Size: 941 B |
BIN
mods/waterworks/textures/waterworks_inlet.png
Normal file
After Width: | Height: | Size: 909 B |
BIN
mods/waterworks/textures/waterworks_metal.png
Normal file
After Width: | Height: | Size: 611 B |
BIN
mods/waterworks/textures/waterworks_outlet.png
Normal file
After Width: | Height: | Size: 925 B |
BIN
mods/waterworks/textures/waterworks_pipe.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
mods/waterworks/textures/waterworks_pipe_rivets.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
mods/waterworks/textures/waterworks_pipe_rivets_offset.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
mods/waterworks/textures/waterworks_pipe_rivets_offset_2.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
mods/waterworks/textures/waterworks_turbine.png
Normal file
After Width: | Height: | Size: 832 B |
BIN
mods/waterworks/textures/waterworks_turbine_base.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
mods/waterworks/textures/waterworks_turbine_side.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
mods/waterworks/textures/waterworks_valve_off.png
Normal file
After Width: | Height: | Size: 766 B |
BIN
mods/waterworks/textures/waterworks_valve_on.png
Normal file
After Width: | Height: | Size: 758 B |
BIN
mods/waterworks/textures/waterworks_valve_seam.png
Normal file
After Width: | Height: | Size: 140 B |