write something there

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

92
mods/worldgate/API.md Normal file
View file

@ -0,0 +1,92 @@
API
---
All of the API functions for Worldgate are defined in the `worldgate` global variable.
### `worldgate.add_gate(def)`
This function adds a worldgate to the world. This is useful for adding your own custom worldgates to the world. The `def` parameter must be a table with the following fields:
```lua
{
-- position: The x/y/z location where the worldgate will be generated. This
-- value must be a vector created with vector.new.
position = vector.new(...),
-- base: A schematic specifier that identifies a 'base' schematic that forms
-- the base, or bottom half, of the worldgate which typically contains a
-- Telemosaic beacon and range extender marker nodes.
base = worldgate.get_random_base(pcgr),
-- decor: A schematic specifier that identifies a 'decor' schematic that forms
-- a decoration placed on the worldgate base. This is typically some form of
-- housing or adornment for the worldgate.
decor = worldgate.get_random_decor(pcgr),
-- quality: Determines the quality of range extenders that generate as part
-- of this worldgate. It must be an integer with a value of -1, 0, or 1 which
-- corresponds to lower quality, equal quality, or better quality extenders,
-- respectively.
quality = worldgate.get_random_quality(pcgr),
-- exact: A boolean value that specifies if the worldgate should be placed at
-- the exact position specified. If true, the worldgate will be placed at the
-- point specified by the position parameter regardless of surrounding
-- terrain. If false, the mod will attempt to place the worldgate according to
-- the available terrain which favors the heightmap first, below air second,
-- then any random position in the mapchunk if all else fails.
exact = false,
-- destination: A vector that matches the position of another worldgate that
-- this worldgate will be linked to automatically. If this value is nil, then
-- the gate's beacon will be deactivated and it will have no destination.
destination = nil,
}
```
### `worldgate.get_random_base(pcgr)`
This function returns a path to a random schematic file in the `worldgate/schematics/base/` directory that corresponds to a worldgate base. Every built-in base schematic will contain a single beacon and extender marker nodes. The optional argument is a PcgRandom object that can be used to choose the random base schematic.
### `worldgate.get_random_decor(pcgr)`
This function returns a path to a random schematic file in the `worldgate/schematics/decor/` directory that corresponds to a worldgate decoration. Decorations typically provide some form of housing or adornment surrounding the worldgate's Telemosaic beacon. A decoration schematic is not necessary for a worldgate to function, but decor does look pretty nice!
The optional argument is a PcgRandom object that can be used to choose the random decor schematic.
### `worldgate.get_random_quality(pcgr)`
This function returns a random value of either -1, 0, or 1, values which correspond to the quality values of a worldgate. This is useful for creating gates with a random quality. The optional argument is a PcgRandom object that can be used to choose the random value.
### `worldgate.get_gates_for_mapblock(position)`
This function returns a list of all gates that should be generated for the mapblock of the given vector location. The `position` parameter must be a vector created with `vector.new`.
### `worldgate.reigster_on_worldgate_generated(fn)`
This function registers a callback function that will be called when a worldgate is successfully generated in the world. The function is called with three parameters:
- `location`: A vector representing the point at which the worldgate was actually placed in the world
- `gate`: The definition of the generated worldgate as specified for `worldgate.add_gate(def)`
- `strategy`: A string that specifies the placement algorithm that the mapgen function used to place the worldgate, in order of mapgen preference:
- `"exact"`: The location is the worldgate's `position` value specified with `exact = true`
- `"heightmap"`: A suitable location was found matching the heightmap for the worldgate's mapchunk
- `"grounded"`: A suitable location was found by probing downwards from a random air node
- `"random"`: A location was selected at random with no regards for the surrounding terrain
### `worldgate.register_on_worldgate_failed(fn)`
This function registers a callback function that will be called when a worldgate tries but fails to generate. This is most likely to happen if mod settings prevent worldgates from spawning in midair or underwater and such a position is selected via the `random` placement strategy during mapgen. A gate that fails to generate in this manner will not attempt to be generated again.
The callback function is called with just one parameter
- `gate`: The gate definition of the worldgate that failed to generate as specified for `worldgate.add_gate(def)`
Dark and Dangerous API
----------------------
**WARNING: ACCESSING THESE FUNCTIONS/VARIABLES CAN CAUSE UNPREDICTABLE LOSS/DAMAGE TO YOUR WORLD; DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING**
### `worldgate.add_gate_unsafe(def)`
This function fills the same role as `worldgate.add_gate(def)`, but it doesn't perform any validation checks to ensure that the gate is valid. This function is used internally during native gate generation for faster performance. Use only if you're sure that your gate definitions do not require validation.

9
mods/worldgate/LICENSE Normal file
View file

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

24
mods/worldgate/README.md Normal file
View file

@ -0,0 +1,24 @@
Worldgate
=========
Discover the Worldgates, an ancient teleportation network that can carry you to distant lands in an instant. Despite their long abandonment, the Worldgates are still highly functional. They can be found throughout the world, some more intact than others, and each adorned in stone by the elder hands that wrought them.
Gates found at the surface of the world are linked randomly to neighboring gates, and gates found at elevations above and below ground are linked to gates on the surface. To use a Worldgate, right-click its active teleportation beacon. But understand the danger of teleporting through a random gate as it may take you very far from your starting point with no way back!
The gates can be linked to any gate through use of mese crystal fragments, and additional teleportation beacons can be crafted. To learn more, see [the Telemosaic mod](https://content.minetest.net/packages/mt-mods/telemosaic/) which is the teleportation technology that underpins the Worldgates.
For the truly brave of heart who wish to fundamentally alter their Worldgate experience, see the mod settings and [the API documentation](https://github.com/EmptyStar/worldgate/blob/main/API.md).
Notes
-----
- Worldgates will only generate in parts of the world that have not yet been explored.
- By default, Worldgates will generate roughly every 1000 nodes between (0,0,0) and 29000 nodes in every direction for a possible total of 195112 Worldgates per world (although many will not generate if midair spawning is disabled which it is by default); this can be configured via mod settings.
Caveats
-------
- When you first approach a newly discovered Worldgate, it may take a few seconds to become fully active.
- Teleportation has a five-second cooldown to prevent spamming and to give new Worldgates a little extra time to configure themselves.
- Rarely, a Worldgate may fail to link to a neighboring Worldgate, thus leaving you stranded at your destination.
- Rarely, depending on the terrain, a Worldgate can fail to generate.

46
mods/worldgate/init.lua Normal file
View file

@ -0,0 +1,46 @@
--
-- Globals
--
worldgate = {
modpath = minetest.get_modpath("worldgate"),
gates = {},
hash_index = {},
forceload_index = {},
settings = {
mapgen = minetest.settings:get_bool("worldgate.mapgen",true),
native = minetest.settings:get_bool("worldgate.native",true),
native_link = minetest.settings:get_bool("worldgate.native.link",true),
native_spread = tonumber(minetest.settings:get("worldgate.native.spread",1000) or 1000),
native_xzjitter = tonumber(minetest.settings:get("worldgate.native.xzjitter",12.5) or 12.5),
ymin = tonumber(minetest.settings:get("worldgate.ymin",-29900) or -29900),
ymax = tonumber(minetest.settings:get("worldgate.ymax",29900) or 29900),
underwaterspawn = minetest.settings:get_bool("worldgate.underwaterspawn",false),
midairspawn = minetest.settings:get_bool("worldgate.midairspawn",false),
breakage = tonumber(minetest.settings:get("worldgate.breakage",8) or 8),
superextenders = minetest.settings:get_bool("worldgate.superextenders",true),
beaconglow = minetest.settings:get_bool("worldgate.beaconglow",true),
destroykeys = minetest.settings:get_bool("worldgate.destroykeys",true),
},
}
-- Disable mapgen via Asuna settings if set
if minetest.get_modpath("asuna_core") then
worldgate.settings.mapgen = minetest.settings:get_bool("asuna.content.wayfarer.worldgate",true)
end
--
-- Modules
--
local function load(file)
dofile(worldgate.modpath .. "/src/" .. file .. ".lua")
end
load("nodes")
load("functions")
load("gates")
load("mapgen")
load("logging")
load("settings_overrides")
load("link")

4
mods/worldgate/mod.conf Normal file
View file

@ -0,0 +1,4 @@
name = worldgate
title = Worldgate
author = EmptyStar
depends = telemosaic, default, stairs

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,38 @@
# Enable mapgen? Unset this to prevent all worldgates from generating.
worldgate.mapgen (Enable mapgen?) bool true
# Generate native worldgates? Native worldgates are the built-in worldgates that this mod provides by default.
worldgate.native (Generate native worldgates?) bool true
# Link native worldgates? This links surface worldgates to a random neighbor and underground/aerial worldgates to worldgates on the surface.
worldgate.native.link (Link native worldgates?) bool true
# Average number of nodes between native worldgates.
worldgate.native.spread (Distance between native worldgates) int 1000 500 25000
# The percentage of spread by which to randomly offset the x/z coordinates of native worldgates. A higher jitter randomly moves the gates further from their generated positions, thereby making their locations less predictable.
worldgate.native.xzjitter (Native worldgate x/z jitter) float 12.5 0 25
# The maximum y position of any worldgate. Any worldgates above this elevation will not generate.
worldgate.ymax (Worldgate maximum y position) int 29900 0 29900
# The minimum y position of any worldgate. Any worldgates below this elevation will not generate.
worldgate.ymin (Worldgate minimum y position) int -29900 -29900 0
# Generate worldgates underwater? If set, worldgates can spawn on the ocean floor; if unset, worldgates will spawn on the water's surface.
worldgate.underwaterspawn (Generate worldgates underwater?) bool false
# Generate worldgates in midair? If set, worldgates can be floating on their own in the air; if unset, gates that attempt to generate in the air will fail to generate.
worldgate.midairspawn (Generate worldgates in midair?) bool false
# Percentage chance of a Telemosaic extender structure node being broken when a gate generates. This causes extenders to be replaced with cobblestone.
worldgate.breakage (Percent chance of an extender being broken) int 8 0 100
# Increase extender range? This supersedes the Telemosaic configuration values for extender ranges. If you wish to use the Telemosaic settings instead, unset this configuration option and change the Telemosaic settings accordingly.
worldgate.superextenders (Increase extender range?) bool true
# Glowing Telemosaic beacons?
worldgate.beaconglow (Glowing Telemosaic beacons?) bool true
# Consume Mese Crystal Fragments after converting to a Telemosaic key? By default, Telemosaic keys are converted back to crystal fragments after use.
worldgate.destroykeys (Consume Telemosaic keys?) bool true

View file

@ -0,0 +1,122 @@
--
-- Worldgate functions
--
-- Function for selecting a random base schematic
local base_schematics = (function()
local schems = {}
for _,schematic in ipairs(minetest.get_dir_list(worldgate.modpath .. "/schematics/base/",false)) do
table.insert(schems,worldgate.modpath .. "/schematics/base/" .. schematic)
end
return schems
end)()
local base_count = #base_schematics
worldgate.schematics = {}
worldgate.schematics.base = base_schematics
function worldgate.get_random_base(pcgr)
return base_schematics[pcgr and pcgr:next(1,base_count) or math.random(1,base_count)]
end
-- Function for selecting a random decor schematic
local decor_schematics = (function()
local schems = {}
for _,schematic in ipairs(minetest.get_dir_list(worldgate.modpath .. "/schematics/decor/",false)) do
table.insert(schems,worldgate.modpath .. "/schematics/decor/" .. schematic)
end
return schems
end)()
local decor_count = #decor_schematics
worldgate.schematics.decor = decor_schematics
function worldgate.get_random_decor(pcgr)
return decor_schematics[pcgr and pcgr:next(1,decor_count) or math.random(1,decor_count)]
end
-- Function for selecting a random quality value
function worldgate.get_random_quality(pcgr)
return pcgr and pcgr:next(-1,1) or math.random(-1,1)
end
-- Function for adding new worldgates without data checks
function worldgate.add_gate_unsafe(def)
-- Add gate to list of gates
local ngates = #worldgate.gates
worldgate.gates[ngates + 1] = def
-- Index the gate via mapblock hashing
local hash = minetest.hash_node_position(def.position:divide(16):floor())
local gates = worldgate.hash_index[hash] or {}
gates[#gates + 1] = ngates + 1
worldgate.hash_index[hash] = gates
end
-- Function for adding new worldgates
local ymin = math.max(-29900,worldgate.settings.ymin)
local ymax = math.min(29900,worldgate.settings.ymax)
function worldgate.add_gate(def)
-- Position must be a valid vector
if not def.position then
error("Attempted to add a worldgate without a position")
elseif not vector.check(def.position) then
error("Worldgate position must be a vector created with vector.new")
elseif def.position.y > ymax or def.position.y < ymin then
error("Worldgate position " .. minetest.pos_to_string(def.position) .. " is beyond ymin/ymax values")
end
if not def.base then
def.base = worldgate.get_random_base()
elseif type(def.base) ~= "string" then
error("Worldgate base must be a string that identifies a schematic")
end
if not def.decor then
def.decor = worldgate.get_random_decor()
elseif type(def.decor) ~= "string" then
error("Worldgate decor must be a string that identifies a schematic")
end
if not def.quality then
def.quality = worldgate.get_random_quality()
elseif not (def.quality == -1 or def.quality == 0 or def.quality == 1) then
error("Worldgate quality must be an integer between -1 and 1 inclusive")
end
if def.exact == nil then
def.exact = false
else
def.exact = not not def.exact -- boolean cast
end
-- Add gate via unsafe function
worldgate.add_gate_unsafe(def)
end
-- Function for checking a mapblock against the gate hash index
function worldgate.get_gates_for_mapblock(pos)
local gates = worldgate.hash_index[minetest.hash_node_position(pos:divide(16):floor())] or {}
for i = 0, #gates do
gates[i] = worldgate.gates[gates[i]]
end
return gates
end
-- Function for finding a suitable placement for a worldgate within a given area
function worldgate.find_worldgate_location_in_area(minp,maxp)
end
-- Function for spawning a worldgate
function worldgate.generate_gate(pos)
end
-- Function for gate generation callbacks
worldgate.worldgate_generated_callbacks = {}
function worldgate.reigster_on_worldgate_generated(fn)
table.insert(worldgate.worldgate_generated_callbacks,fn)
end
-- Function for failed gate generation callbacks
worldgate.worldgate_failed_callbacks = {}
function worldgate.reigster_on_worldgate_failed(fn)
table.insert(worldgate.worldgate_failed_callbacks,fn)
end

View file

@ -0,0 +1,123 @@
--
-- Native worldgate generation
--
-- Generate gates if the mod is configured to do so
if worldgate.settings.native then
-- Define rng based on the world seed
local pcgr = PcgRandom(minetest.get_mapgen_setting("seed"))
-- Get distance between gates (spread)
local spread = worldgate.settings.native_spread
-- Do not generate gates beyond totalmax to prevent any wierdness with world
-- boundaries
local totalmax = 29900
-- Get minimum and maximum y values
local ymin = math.max(-totalmax,worldgate.settings.ymin)
local ymax = math.min(totalmax,worldgate.settings.ymax)
-- Cache frequently used global functions for better performance
local add_gate = worldgate.add_gate_unsafe -- native gates are made with respect to checks
local get_random_base = worldgate.get_random_base
local get_random_decor = worldgate.get_random_decor
local get_random_quality = worldgate.get_random_quality
local vn = vector.new
-- Align coordinate values with 0 for more centered/grounded generation
local alignment_offset = totalmax - spread * math.floor(totalmax / spread)
local ymin_offset = -ymin - spread * math.floor(-ymin / spread)
local ymax_offset = ymax - spread * math.floor(ymax / spread)
local min = -totalmax + alignment_offset
local max = totalmax - alignment_offset
ymin = ymin + ymin_offset
ymax = ymax - ymax_offset
-- Generate x/z values with jitter so that worldgate locations are less
-- predictable
local xzjitterp = math.floor(spread * worldgate.settings.native_xzjitter / 100)
local xzjittern = -xzjitterp
-- Function to get a gate based on an x/y/z coordinate
local function generate_gate(x,y,z)
return {
position = vn(x + pcgr:next(xzjittern,xzjitterp),y + pcgr:next(0,24),z + pcgr:next(xzjittern,xzjitterp)),
base = get_random_base(pcgr),
decor = get_random_decor(pcgr),
quality = get_random_quality(pcgr),
exact = false,
}
end
-- Probability table for gate quality bias in favor of average and above
-- average quality values (20% poor, 50% average, 30% above average)
local quality_bias = {-1,-1,0,0,0,0,0,1,1,1}
-- Link gates if configured, else generate gates with no destinations
if worldgate.settings.native_link then
-- Generate x/z gate definitions for y = 0 since these are the destination
-- gates of all native gates
local surface_gates = {}
for x = min, max, spread do
for z = min, max, spread do
local pos = vn(x,0,z)
local hpos = minetest.hash_node_position(pos)
surface_gates[hpos] = {
position = vn(x + pcgr:next(xzjittern,xzjitterp),pcgr:next(0,24),z + pcgr:next(xzjittern,xzjitterp)),
base = get_random_base(pcgr),
decor = get_random_decor(pcgr),
quality = quality_bias[pcgr:next(1,10)],
exact = false,
destination = (function() -- placeholder to be converted to an actual gate
local nhashes = {
minetest.hash_node_position(vn(x == min and x + spread or x - spread,0,z == min and z + spread or z - spread)),
minetest.hash_node_position(vn(x == min and x + spread or x - spread,0,z == max and z - spread or z + spread)),
minetest.hash_node_position(vn(x == max and x - spread or x + spread,0,z == min and z + spread or z - spread)),
minetest.hash_node_position(vn(x == max and x - spread or x + spread,0,z == max and z - spread or z + spread)),
}
local neighbors = {}
for n = 1, 4 do
n = nhashes[n]
local g = surface_gates[n]
if not g or g.destination ~= hpos then
neighbors[#neighbors + 1] = n
end
end
return neighbors[1] and neighbors[pcgr:next(1,#neighbors)] or nhashes[pcgr:next(1,4)]
end)(),
}
end
-- Override gate generation function to generate destinations
generate_gate = function(x,y,z)
local pos = vn(x,y,z)
local gate
if y == 0 then
gate = surface_gates[minetest.hash_node_position(pos)]
gate.destination = surface_gates[gate.destination].position
else
gate = {
position = vn(x + pcgr:next(xzjittern,xzjitterp),y + pcgr:next(0,24),z + pcgr:next(xzjittern,xzjitterp)),
base = get_random_base(pcgr),
decor = get_random_decor(pcgr),
quality = get_random_quality(pcgr),
exact = false,
}
pos.y = 0
gate.destination = surface_gates[minetest.hash_node_position(pos)].position
end
return gate
end
end
end
-- Generate gates
for x = min, max, spread do
for y = ymin, ymax, spread do
for z = min, max, spread do
add_gate(generate_gate(x,y,z))
end
end
end
end

188
mods/worldgate/src/link.lua Normal file
View file

@ -0,0 +1,188 @@
--
-- Worldgate linking functions
--
-- Telemosaic location hashing and unhashing functions
local function hash_pos(pos)
return math.floor(pos.x + 0.5)..':'..
math.floor(pos.y + 0.5)..':'..
math.floor(pos.z + 0.5)
end
local function unhash_pos(hash)
local list = string.split(hash, ':')
local p = {
x = tonumber(list[1]),
y = tonumber(list[2]),
z = tonumber(list[3])
}
if p.x and p.y and p.z then
return p
end
end
-- Polyfill for vector.in_area
if not vector.in_area then
function vector.in_area(pos, min, max)
return (pos.x >= min.x) and (pos.x <= max.x) and
(pos.y >= min.y) and (pos.y <= max.y) and
(pos.z >= min.z) and (pos.z <= max.z)
end
end
-- Garbage collection index to manage force-loaded chunks
local forceloader = {
index = {},
load = function(self,blockpos)
-- Get the reference counter for the mapblock
local hash = minetest.hash_node_position(blockpos)
local refcounter = self.index[hash] or 0
-- Force-load mapblock if it is not yet force-loaded
if refcounter == 0 then
minetest.forceload_block(blockpos,true)
end
-- Store updated reference counter
refcounter = refcounter + 1
self.index[hash] = refcounter
end,
unload = function(self,blockpos)
-- Get the reference counter for the mapblock
local hash = minetest.hash_node_position(blockpos)
local refcounter = self.index[hash] or 1
-- Decrement reference counter and unload mapblock if there are no more
-- references to it
refcounter = refcounter - 1
if refcounter == 0 then
minetest.forceload_free_block(blockpos,true)
end
-- Store updated reference counter
self.index[hash] = refcounter
end,
}
-- Function for linking and force-loading gate destinations
function worldgate.link(pos)
-- Get meta/destination
local nodemeta = minetest.get_meta(pos)
local destination = nodemeta:get("telemosaic:dest")
local do_worldgate_link = false
-- Check for worldgate destination and convert to vector
local dpos = nil
if not destination then
dpos = nodemeta:get("worldgate:destination")
if not dpos then
return true
end
destination = minetest.string_to_pos(dpos)
do_worldgate_link = true
else
destination = vector.new(unhash_pos(destination))
end
-- Look for players near this gate
local players = minetest.get_connected_players()
for i = 1, #players do
-- If a player is found, then force-load the destination
local player = players[i]
if player and player:get_pos():in_area(pos:add(-32),pos:add(32)) then
if not nodemeta:get("worldgate:player_nearby") then
-- Define destination area to emerge
local mapblock = destination:divide(16):floor():multiply(16)
local emin = mapblock:add(vector.new(0,-32,0))
local emax = mapblock:add(vector.new(0,47,0))
-- Emerge the destination mapblocks
minetest.emerge_area(emin,emax,function(blockpos,action,calls_remaining)
-- Do worldgate link to Telemosaic if defined for this gate
if calls_remaining == 0 then
if do_worldgate_link then
destination = nil
local beacons = minetest.find_nodes_in_area(emin:add(vector.new(-1,-1,-1)),emax:add(vector.new(1,1,1)),"group:telemosaic")
for beacon = 1, #beacons do
beacon = beacons[beacon]
local spos = minetest.get_meta(beacon):get("worldgate:source")
if dpos == spos then
destination = beacon
nodemeta:set_string("telemosaic:dest",hash_pos(beacon))
break
end
end
end
if destination then
-- Force-load destination mapblocks and own mapblock
forceloader:load(pos:divide(16):floor())
for p = -32, 32, 16 do
forceloader:load(destination:add(vector.new(0,p,0)):divide(16):floor())
end
else
-- Beacon cannot be linked
minetest.swap_node(pos,{ name = "telemosaic:beacon_off", param2 = 0 })
return false
end
end
end)
-- Flag this beacon as having a player nearby
nodemeta:set_string("worldgate:player_nearby","1")
end
return true
end
end
-- No players found, unload all force-loaded blocks and unflag the beacon
if nodemeta:get("worldgate:player_nearby") then
forceloader:unload(pos:divide(16):floor())
for p = -32, 32, 16 do
forceloader:unload(destination:add(vector.new(0,p,0)):divide(16):floor())
end
nodemeta:set_string("worldgate:player_nearby","")
end
return true
end
-- Run linking function on node timer
for _,beacon in ipairs({
"telemosaic:beacon",
"telemosaic:beacon_protected",
}) do
minetest.override_item(beacon,{
on_timer = worldgate.link,
})
end
-- Queue for linking function calls
local linkq = {}
-- Run linking function when a player teleports
local tt = telemosaic.teleport
telemosaic.teleport = function(player,src,dest)
tt(player,src,dest)
dest.y = dest.y - 1
worldgate.link(vector.new(src))
worldgate.link(vector.new(dest))
end
-- Run linking function when a beacon changes its state
local tss = telemosaic.set_state
telemosaic.set_state = function(pos,state)
tss(pos,state)
worldgate.link(vector.new(pos))
end
-- Register LBM to jump-start force-load timers
minetest.register_abm({
label = "Run worldgate linking function",
nodenames = "group:telemosaic",
interval = 3,
chance = 1,
catch_up = false,
action = function(pos)
worldgate.link(pos)
end,
})

View file

@ -0,0 +1,9 @@
-- Logging for successfully generated worldgates
worldgate.reigster_on_worldgate_generated(function(pos,gate,strategy)
minetest.log("action","Worldgate generated at " .. minetest.pos_to_string(pos) .. " using the " .. strategy .. " strategy")
end)
-- Logging for worldgates that failed to generate
worldgate.reigster_on_worldgate_failed(function(gate)
minetest.log("warning","Worldgate failed to generate" .. (gate.exact and " at " or " near ") .. minetest.pos_to_string(gate.position))
end)

View file

@ -0,0 +1,301 @@
--
-- Worldgate mapgen
--
-- Do not register mapgen if worldgate mapgen is disabled
if not worldgate.settings.mapgen then
return
end
-- Spawning flags
local underwaterspawn = worldgate.settings.underwaterspawn
local midairspawn = worldgate.settings.midairspawn
-- Telemosaic location hashing function
local function hash_pos(pos)
return math.floor(pos.x + 0.5)..':'..
math.floor(pos.y + 0.5)..':'..
math.floor(pos.z + 0.5)
end
-- Common cached variables and functions
local vmcache = {} -- VoxelManip data cache, increases performance
local schematic_airspace = worldgate.modpath .. "/schematics/worldgate_airspace.mts"
local schematic_platform = worldgate.modpath .. "/schematics/worldgate_platform.mts"
local extender_break_chance = worldgate.settings.breakage
local quality_selector = {
[0] = function(pcgr) -- 25% chance for tier 1 extender to be cobblestone
return { name = (pcgr:next(1,4) == 1 and "default:cobble" or "telemosaic:extender_one"), param2 = 0 }
end,
function() return { name = "telemosaic:extender_one", param2 = 0 } end,
function() return { name = "telemosaic:extender_two", param2 = 0 } end,
function() return { name = "telemosaic:extender_three", param2 = 0 } end,
function() return { name = "telemosaic:extender_three", param2 = 0 } end,
}
local water = {
[minetest.get_content_id("mapgen_water_source")] = true,
[minetest.get_content_id("mapgen_river_water_source")] = true,
}
local vn = vector.new
-- Disallowed nodes to spawn on; no prefix means group
local disallowed_nodes = {
"leaves",
"tree",
}
local disallowed_nodes_length = #disallowed_nodes
local disallowed_cids = {[minetest.CONTENT_AIR] = true}
minetest.register_on_mods_loaded(function()
for node,def in pairs(minetest.registered_nodes) do
for i = 1, disallowed_nodes_length do
i = disallowed_nodes[i]
if node == i or minetest.get_item_group(node,i) > 0 then
disallowed_cids[minetest.get_content_id(node)] = true
end
end
end
end)
-- Bricks to cobblestone map for quality degradation
local bricks_list = {
"default:stonebrick",
"stairs:stair_stonebrick",
"stairs:stair_inner_stonebrick",
"stairs:stair_outer_stonebrick",
"stairs:slab_stonebrick",
}
local bricks_map = {
["default:stonebrick"] = "default:cobble",
["stairs:stair_stonebrick"] = "stairs:stair_cobble",
["stairs:stair_inner_stonebrick"] = "stairs:stair_inner_cobble",
["stairs:stair_outer_stonebrick"] = "stairs:stair_outer_cobble",
["stairs:slab_stonebrick"] = "stairs:slab_cobble",
}
local bricks_degrade_chance = {
[-1] = 9,
[0] = 42,
[1] = 200,
}
-- Worldgate mapgen function
minetest.register_on_generated(function(minp,maxp,blockseed)
-- Find all gates within the emerged area of the current mapchunk
local gates = {}
for x = 0, 4 do
for y = 0, 4 do
for z = 0, 4 do
local hashed = worldgate.get_gates_for_mapblock(vn(minp.x + x * 16,minp.y + y * 16,minp.z + z * 16))
for h = 1, #hashed do
gates[#gates + 1] = hashed[h]
end
end
end
end
if #gates == 0 then
return -- no gates to generate
end
-- Get LVM values
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local va = VoxelArea(emin,emax)
local ystride = va.ystride
local zstride = va.zstride
-- Generate the gates in this mapchunk, if any
for gate = 1, #gates do
gate = gates[gate]
-- Random number generator
local pcgr = PcgRandom(minetest.hash_node_position(gate.position))
-- Define location variable
local location = nil
-- Variable for tracking location selection strategy
local strategy = nil
-- Set the exact position if gate is exact, else find a suitable location
-- in the current mapchunk
if gate.exact then
location = gate.position
strategy = "exact"
else
-- Load LVM data
local vdata = vm:get_data(vmcache)
-- Constrain the area to +/- 2 vertical mapblocks for more consistent and
-- deterministic mapgen
local mapblock = gate.position:divide(16):floor():multiply(16)
emin = mapblock:add(vn(0,-32,0))
emax = mapblock:add(vn(0,47,0))
-- Function for indexing 2D heightmap array
local function index2d(x,z) -- (portions of this function © FaceDeer 2018, licensed MIT, copied from https://github.com/minetest-mods/mapgen_helper/blob/2521562a42472271d9d761f2b1e84ead59250a14/noise_manager.lua)
return x - minp.x +
(maxp.x - minp.x + 1)
*(z - minp.z)
+ 1
end
-- Probe heightmap for suitable location
local heightmap = minetest.get_mapgen_object("heightmap") or {}
for i = 1, 8 do
local randomx = pcgr:next(emin.x,emax.x)
local randomz = pcgr:next(emin.z,emax.z)
local heightmapy = heightmap[index2d(randomx,randomz)]
if not heightmapy then
goto retry_probe
end
local pos = va:index(randomx,heightmapy,randomz)
local cid = vdata[pos]
local above = vdata[pos + ystride]
if cid and cid ~= minetest.CONTENT_AIR and cid ~= minetest.CONTENT_IGNORE and above and above ~= cid then
-- Only spawn underwater if allowed
if not underwaterspawn and (water[cid] or water[above]) then
goto retry_probe
end
-- Check for valid space above
for ypos = pos + ystride * 2, pos + ystride * 10, ystride do
local ydata = vdata[ypos]
if not ydata or ydata == minetest.CONTENT_IGNORE or (not underwaterspawn and water[ydata]) then
goto retry_probe
end
end
-- A valid location was found on the heightmap
location = vn(randomx,heightmapy,randomz)
strategy = "heightmap"
break
end
::retry_probe::
end
-- If no heightmap location found, then generate on a random node under air
if not location then
local air, nodecount = minetest.find_nodes_in_area(emin,emax,"air")
local nair = nodecount.air
for i = 1, math.min(nair,8) do
local pos = va:indexp(air[pcgr:next(1,nair)])
while disallowed_cids[vdata[pos]] do
pos = pos - ystride -- probe downwards until we find something that isn't air
end
if vdata[pos] and vdata[pos] ~= minetest.CONTENT_IGNORE then
location = va:position(pos)
strategy = "grounded"
break
end
end
end
-- If mapchunk is completely solid or empty, then generate in a random location
if not location then
for i = 1, 8 do
local randomx = pcgr:next(emin.x,emax.x)
local randomy = pcgr:next(emin.y,emax.y)
local randomz = pcgr:next(emin.z,emax.z)
-- Only spawn in midair or underwater if allowed
local pos = va:indexp(vn(randomx,randomy,randomz))
for ypos = pos, pos + ystride * 11, ystride do
local ydata = vdata[ypos]
if ydata and ydata ~= minetest.CONTENT_IGNORE and (underwaterspawn or not water[ydata]) and (ypos == pos and (midairspawn or ydata ~= minetest.CONTENT_AIR) or true) then
location = va:position(pos)
strategy = "random"
break
end
end
end
end
-- Fail if no suitable location found
if not location then
-- Trigger failure callbacks
for c = 1, #worldgate.worldgate_failed_callbacks do
worldgate.worldgate_failed_callbacks[c](gate)
end
return -- cannot generate this worldgate
end
-- Adjust location by y + 1
location = location:add(vn(0,1,0))
end
-- Place airspace
minetest.place_schematic_on_vmanip(vm,location,schematic_airspace,"90",nil,true,"place_center_x,place_center_z")
-- Place platform
minetest.place_schematic_on_vmanip(vm,location:add(vn(0,-8,0)),schematic_platform,"random",nil,true,"place_center_x,place_center_z")
-- Place base
minetest.place_schematic_on_vmanip(vm,location,gate.base,"random",nil,false,"place_center_x,place_center_z")
-- Place decor
minetest.place_schematic_on_vmanip(vm,location:add(vn(0,3,0)),gate.decor,"random",nil,false,"place_center_x,place_center_z")
-- Update liquids
vm:update_liquids()
-- Write back to LVM
vm:write_to_map()
-- Process extenders based on gate quality
for _,epos in ipairs(minetest.find_nodes_in_area(location:add(vn(-6,0,-6)),location:add(vn(6,3,6)),"group:worldgate_extender")) do
if pcgr:next(1,100) <= extender_break_chance then -- chance for any extender to be broken
minetest.swap_node(epos,{ name = "default:cobble", param2 = 0 })
else
minetest.swap_node(epos,quality_selector[minetest.get_item_group(minetest.get_node(epos).name,"worldgate_extender") + gate.quality](pcgr))
end
end
-- Replace bricks with cobblestone based on gate quality
local bricks = minetest.find_nodes_in_area(location:add(vn(-6,0,-6)),location:add(vn(6,13,6)),bricks_list)
local brick_degrade_chance = bricks_degrade_chance[gate.quality]
for _,brick in ipairs(bricks) do
if pcgr:next(1,brick_degrade_chance) == 1 then
local brick_node = minetest.get_node(brick)
local cobble_node = bricks_map[brick_node.name]
if cobble_node then
minetest.swap_node(brick,{ name = cobble_node, param2 = brick_node.param2 })
end
end
end
-- Write the gate's position to the beacon's node meta for linking purposes
local beacon = (function()
for dy = 2, 0, -1 do
local beacon_location = location:add(vn(0,dy,0))
local beacon_node = minetest.get_node(beacon_location)
if beacon_node and beacon_node.name:find("^telemosaic:beacon") then
return beacon_location
end
end
end)()
local nodemeta = minetest.get_meta(beacon)
nodemeta:set_string("worldgate:source",minetest.pos_to_string(gate.position))
if gate.destination then
nodemeta:set_string("worldgate:destination",minetest.pos_to_string(gate.destination))
minetest.swap_node(beacon,{ name = "telemosaic:beacon", param2 = 0 })
end
-- Fix lighting
minetest.fix_light(location:add(vn(-6,-8,-6)),location:add(vn(6,11,6)))
-- Trigger callbacks
for c = 1, #worldgate.worldgate_generated_callbacks do
worldgate.worldgate_generated_callbacks[c](location,gate,strategy)
end
end
end)

View file

@ -0,0 +1,31 @@
--
-- Worldgate structure nodes
-- These get replaced with "real" nodes defined by games/mods/mapgen
for name,def in pairs({
Extender1 = {
color = "#00FF00",
group = "worldgate_extender",
group_value = 1,
},
Extender2 = {
color = "#0000FF",
group = "worldgate_extender",
group_value = 2,
},
Extender3 = {
color = "#FF00FF",
group = "worldgate_extender",
group_value = 3,
},
}) do
minetest.register_node("worldgate:structure_" .. name:lower(),{
description = "Worldgate Structure Node: " .. name,
groups = {
not_in_creative_inventory = 1,
oddly_breakable_by_hand = 1,
[def.group] = def.group_value,
},
color = def.color,
})
end

View file

@ -0,0 +1,78 @@
--
-- Overrides for Telemosaic settings
--
-- Give extenders a lot more range
if worldgate.settings.superextenders then
-- Global extender values
telemosaic.extender_ranges = { 250, 750, 1500 }
-- Tier 1 extenders
local tier1range = telemosaic.extender_ranges[1]
local tier1groups = minetest.registered_nodes["telemosaic:extender_one"].groups
tier1groups.telemosaic_extender = tier1range
minetest.override_item("telemosaic:extender_one",{ groups = tier1groups })
-- Tier 2 extenders
local tier2range = telemosaic.extender_ranges[2]
local tier2groups = minetest.registered_nodes["telemosaic:extender_two"].groups
tier2groups.telemosaic_extender = tier2range
minetest.override_item("telemosaic:extender_two",{ groups = tier2groups })
-- Tier 3 extenders
local tier3range = telemosaic.extender_ranges[3]
local tier3groups = minetest.registered_nodes["telemosaic:extender_three"].groups
tier3groups.telemosaic_extender = tier3range
minetest.override_item("telemosaic:extender_three",{ groups = tier3groups })
end
-- Implement a longer minimum delay for teleportation to prevent spamming and
-- weirdness with loading/unloading mapblocks
local min_delay = 5
if telemosaic.teleport_delay < min_delay then
telemosaic.teleport_delay = min_delay
end
-- Override beacons to give off light if configured
if worldgate.settings.beaconglow then
for _,beacon in ipairs({
"telemosaic:beacon",
"telemosaic:beacon_protected",
"telemosaic:beacon_err",
"telemosaic:beacon_err_protected",
}) do
minetest.override_item(beacon,{
light_source = 15,
})
end
end
-- Override right-click function to consume mese crystal shards after use as a
-- Telemosaic key, if configured
if worldgate.settings.destroykeys then
local trc = telemosaic.rightclick
telemosaic.rightclick = function(pos, node, player, itemstack, pointed_thing)
local item = itemstack:get_name()
local returned_item = trc(pos, node, player, itemstack, pointed_thing)
if item == "telemosaic:key" and returned_item:get_name() == "default:mese_crystal_fragment" then
return ItemStack()
else
return returned_item
end
end
for _,beacon in ipairs({
"telemosaic:beacon",
"telemosaic:beacon_err",
"telemosaic:beacon_disabled",
"telemosaic:beacon_off",
"telemosaic:beacon_protected",
"telemosaic:beacon_err_protected",
"telemosaic:beacon_disabled_protected",
"telemosaic:beacon_off_protected",
}) do
minetest.override_item(beacon,{
on_rightclick = telemosaic.rightclick
})
end
end