Add API and player-buildable Portal support

Adds a simple API for obtaining island information, and uses the Portals API in the nether mod (if present) to register a portal to the cloudlands.
master
Treer 2020-02-09 00:52:34 +11:00
parent f6bebe9f52
commit 9df4c3ac51
3 changed files with 185 additions and 10 deletions

View File

@ -10,6 +10,7 @@ read_globals = {
"default",
"biomeinfo",
"schemlib",
"nether",
"DIR_DELIM",
"intllib",
"ItemStack",

View File

@ -46,7 +46,12 @@ local DEBUG_SKYTREES = false -- dev logging
-- notice problems requiring it.
local OVERDRAW = 0
local coreTypes = {
cloudlands = {} -- API functions can be accessed via this global:
-- cloudlands.get_island_details(minp, maxp) -- returns an array of island-information-tables, y is ignored.
-- cloudlands.find_nearest_island(x, z, search_radius) -- returns a single island-information-table, or nil
-- cloudlands.get_height_at(x, z) -- returns (y, isWater), or nil if no island here
cloudlands.coreTypes = {
{
territorySize = 200,
coresPerTerritory = 3,
@ -295,6 +300,75 @@ interop.get_biome_key = function(pos)
return minetest.get_biome_data(pos).biome
end
end
--[[==============================
Portals
==============================]]--
if minetest.get_modpath("nether") ~= nil and minetest.global_exists("nether") and nether.register_portal ~= nil then
-- The Portals API is available
-- Register a test portal to Hallelujah Mountains. This is just testing out the API.
-- (The Portals API may still just be a pull-request https://github.com/minetest-mods/nether/pull/8)
local frameNode = "nether:brick"
if minetest.registered_nodes[frameNode] ~= nil then
nether.register_portal("cloudlands_portal", {
shape = nether.PortalShape_Traditional,
frame_node_name = frameNode,
wormhole_node_color = 5, -- 5 is red
particle_color = "#D08",
particle_texture = {
name = "nether_particle_anim1.png",
animation = {
type = "vertical_frames",
aspect_w = 7,
aspect_h = 7,
length = 1,
},
scale = 1.5
},
title = "Cloudlands Portal",
book_of_portals_pagetext =
"Construction requires 14 blocks of tin. A finished frame is four blocks wide, five blocks high, and stands vertically, like a doorway." .. "\n\n" ..
"There are floating islands of hills and forests up there, over the edges of which is a perilous drop all the way back down to sea level.",
is_within_realm = function(pos) -- return true if pos is inside the Nether
return pos.y > cloudlands.realm_boundary_height
end,
find_realm_anchorPos = function(surface_anchorPos)
-- TODO: Once paramat finishes adjusting the floatlands, implement a surface algorithm that finds land
local destination_pos = nil
local island = cloudlands.find_nearest_island(surface_anchorPos.x, surface_anchorPos.z, 70)
if island == nil then island = cloudlands.find_nearest_island(surface_anchorPos.x, surface_anchorPos.z, 150) end
if island == nil then island = cloudlands.find_nearest_island(surface_anchorPos.x, surface_anchorPos.z, 400) end
if island ~= nil then
local y, isWater = cloudlands.get_height_at(island.x, island.z)
if y ~= nil and not isWater then
destination_pos = {x = island.x, y = y, z = island.z}
-- a y_factor of 0 makes the search ignore the altitude of the portals (as long as they are in the Cloudlands realm)
local existing_portal_location, existing_portal_orientation = nether.find_nearest_working_portal("cloudlands_portal", destination_pos, 10, 0)
if existing_portal_location ~= nil then
return existing_portal_location, existing_portal_orientation
end
end
end
return destination_pos
end
})
end
-- store a "realm_boundary_height", above which is probably cloudlands, and below which is probably the native mapgen
-- You must construct a portal below the realm_boundary_height for it to take you to the cloudlands
local maxMapgenHeight = 50 -- not really the max, close enough
cloudlands.realm_boundary_height = math_max(maxMapgenHeight, round((maxMapgenHeight + (ALTITUDE - ALTITUDE_AMPLITUDE)) / 2))
end
--[[==============================
SkyTrees
==============================]]--
@ -1082,10 +1156,10 @@ end
-- gets an array of all cores which may intersect the draw distance
local function getCores(minp, maxp)
cloudlands.get_island_details = function(minp, maxp)
local result = {}
for _,coreType in pairs(coreTypes) do
for _,coreType in pairs(cloudlands.coreTypes) do
addCores(
result,
coreType,
@ -1103,6 +1177,105 @@ local function getCores(minp, maxp)
return result;
end
cloudlands.find_nearest_island = function(x, z, search_radius)
local coreList = {}
for _,coreType in pairs(cloudlands.coreTypes) do
addCores(
coreList,
coreType,
x - (search_radius + coreType.radiusMax),
z - (search_radius + coreType.radiusMax),
x + (search_radius + coreType.radiusMax),
z + (search_radius + coreType.radiusMax)
)
end
-- remove islands only after cores have all generated to avoid the restriction
-- settings from rearranging islands.
if region_restrictions then removeUnwantedIslands(coreList) end
local result = nil
for _,core in ipairs(coreList) do
local distance = math.hypot(core.x - x, core.z - z)
if distance >= core.radius then
core.distance = 1 + distance - core.radius
else
-- distance is fractional
core.distance = distance / (core.radius + 1)
end
if result == nil or core.distance < result.distance then result = core end
end
return result;
end
cloudlands.get_height_at = function(x, z)
local result, isWater = nil, false;
local pos = {x = x, z = z}
local coreList = cloudlands.get_island_details(pos, pos)
for _,core in ipairs(coreList) do
-- duplicates the code from renderCores() to find surface height
-- See the renderCores() version for explanatory comments
local horz_easing
local distanceSquared = (x - core.x)*(x - core.x) + (z - core.z)*(z - core.z)
local radiusSquared = core.radius * core.radius
local noise_weighting = 1
local shapeType = math_floor(core.depth + core.radius + core.x) % 5
if shapeType < 2 then -- convex, see renderCores() implementatin for comments
horz_easing = 1 - distanceSquared / radiusSquared
elseif shapeType == 2 then -- conical, see renderCores() implementatin for comments
horz_easing = 1 - math_sqrt(distanceSquared) / core.radius
else -- concave, see renderCores() implementatin for comments
local radiusRoot = math_sqrt(core.radius)
local squared = 1 - distanceSquared / radiusSquared
local distance = math_sqrt(distanceSquared)
local distance_normalized = distance / core.radius
local root = 1 - math_sqrt(distance) / radiusRoot
horz_easing = math_min(1, 0.8*distance_normalized*squared + 1.2*(1-distance_normalized)*root)
noise_weighting = 0.63
end
if core.radius + core.depth > 80 then noise_weighting = 0.6 end
if core.radius + core.depth > 120 then noise_weighting = 0.35 end
local surfaceNoise = noise_surfaceMap:get2d({x = x, y = z})
if DEBUG_GEOMETRIC then surfaceNoise = SURFACEMAP_OFFSET end
local coreTop = ALTITUDE + core.y
local surfaceHeight = coreTop + round(surfaceNoise * 3 * (core.thickness + 1) * horz_easing)
if result == nil or math_max(coreTop, surfaceHeight) > result then
local coreBottom = math_floor(coreTop - (core.thickness + core.depth))
local yBottom = coreBottom
if result ~= nil then yBottom = math_max(yBottom, result + 1) end
for y = math_max(coreTop, surfaceHeight), yBottom, -1 do
local vert_easing = math_min(1, (y - coreBottom) / core.depth)
local densityNoise = noise_density:get3d({x = x, y = y - coreTop, z = z})
densityNoise = noise_weighting * densityNoise + (1 - noise_weighting) * DENSITY_OFFSET
if DEBUG_GEOMETRIC then densityNoise = DENSITY_OFFSET end
if densityNoise * ((horz_easing + vert_easing) / 2) >= REQUIRED_DENSITY then
result = surfaceHeight
isWater = y == coreTop and coreTop > surfaceHeight
break;
end
end
end
end
return result, isWater
end
local function setCoreBiomeData(core)
local pos = {x = core.x, y = ALTITUDE + core.y, z = core.z}
if LOWLAND_BIOMES then pos.y = LOWLAND_BIOME_ALTITUDE end
@ -1873,7 +2046,7 @@ local function addDetail_secrets(decoration_list, core, data, area, minp, maxp)
local book_itemstack = ItemStack(stackName_writtenBook)
local book_data = {}
book_data.title = "Weddell Outpost"
-- Instead of being a stand-alone line, the McNish line is tacked on the end of the
-- Instead of being a stand-alone line, the McNish line is tacked on the end of the
-- journey sentence because otherwise it gets truncated off by default:book_written
book_data.text = [[The aerostat is lost.
@ -2224,8 +2397,8 @@ local function renderCores(cores, minp, maxp, blockseed)
nodeId_dust = minetest.get_content_id(core.biome.node_dust)
for _, location in ipairs(core.dustLocations) do
if data[location] == nodeId_air and data[location - area.ystride] ~= nodeId_air then
data[location] = nodeId_dust
if data[location] == nodeId_air and data[location - area.ystride] ~= nodeId_air then
data[location] = nodeId_dust
end
end
end
@ -2300,8 +2473,9 @@ local function on_generated(minp, maxp, blockseed)
local osClockT0 = os.clock()
if DEBUG then memUsageT0 = collectgarbage("count") end
local maxCoreThickness = coreTypes[1].thicknessMax -- the first island type is the biggest/thickest
local maxCoreDepth = coreTypes[1].radiusMax * 3 / 2
local largestCoreType = cloudlands.coreTypes[1] -- the first island type is the biggest/thickest
local maxCoreThickness = largestCoreType.thicknessMax
local maxCoreDepth = largestCoreType.radiusMax * 3 / 2
local maxSufaceRise = 3 * (maxCoreThickness + 1)
if minp.y > ALTITUDE + (ALTITUDE_AMPLITUDE + maxSufaceRise + 10) or -- the 10 is an arbitrary number because sometimes the noise values exceed their normal range.
@ -2314,7 +2488,7 @@ local function on_generated(minp, maxp, blockseed)
init_mapgen()
init_secrets()
end
local cores = getCores(minp, maxp)
local cores = cloudlands.get_island_details(minp, maxp)
if DEBUG then
minetest.log("info", "Cores for on_generated(): " .. #cores)

View File

@ -1,5 +1,5 @@
name = cloudlands
optional_depends = vines, schemlib, default, mcl_core, ethereal, biomeinfo
optional_depends = vines, schemlib, default, mcl_core, ethereal, biomeinfo, nether
description = """
Hallelujah Mountains for Minetest