tibia-rme/scripts/forest_generator.lua
Eduardo Dantas ea6b515552
ci: rework build workflows and vcpkg baseline (#176)
New Features:
- Docker support: new container build and .dockerignore to reduce build-context size.
- Automated vcpkg baseline updater.

CI/CD Improvements:
- Introduces consolidated reusable CI with platform build and Docker workflows.
- Adds cache-cleanup and autofix/format workflows; removes several legacy platform-specific CI files.

Chores:
- Reorganized CMake presets and build configuration; updated build scripts and project metadata.

Style:
- Large-scale formatting/whitespace normalization across many scripts and build files.
2026-05-01 19:45:45 -03:00

210 lines
4.6 KiB
Lua

-- @Title: Forest Generator
-- @Description: Generates a forest using Poisson Disk Sampling for natural tree placement
-- @Author: VLL Systems
-- @Version: 1.0.0
if not app.hasMap() then
print("[Forest] No map open.")
return
end
local dlg = Dialog({
title = "Forest Generator",
width = 400,
})
dlg:label({
text = "Generate a forest with naturally spaced trees.",
})
dlg:separator()
dlg:number({
id = "cx",
label = "Start X:",
value = 1000,
min = 1,
max = 65000,
})
dlg:number({
id = "cy",
label = "Start Y:",
value = 1000,
min = 1,
max = 65000,
})
dlg:number({
id = "cz",
label = "Floor Z:",
value = 7,
min = 0,
max = 15,
})
dlg:newrow()
dlg:number({
id = "width",
label = "Width:",
value = 30,
min = 5,
max = 200,
})
dlg:number({
id = "height",
label = "Height:",
value = 30,
min = 5,
max = 200,
})
dlg:newrow()
dlg:number({
id = "spacing",
label = "Tree Spacing:",
value = 3,
min = 2,
max = 10,
})
dlg:number({
id = "seed",
label = "Seed:",
value = 42,
min = 1,
max = 99999,
})
dlg:newrow()
dlg:input({
id = "groundBrush",
label = "Ground Brush:",
text = "grass",
})
dlg:newrow()
dlg:check({
id = "organic",
text = "Organic shape (noise edge)",
selected = true,
})
dlg:check({
id = "flowers",
text = "Add flowers between trees",
selected = true,
})
dlg:newrow()
dlg:separator()
dlg:button({
id = "go",
text = "Generate Forest",
focus = true,
onclick = function(d)
d:close()
end,
})
dlg:show()
local data = dlg.data
local cx = math.floor(data.cx)
local cy = math.floor(data.cy)
local cz = math.floor(data.cz)
local w = math.floor(data.width)
local h = math.floor(data.height)
local spacing = math.floor(data.spacing)
local seed = math.floor(data.seed)
local groundBrush = data.groundBrush
local useOrganic = data.organic
local useFlowers = data.flowers
-- Tree item IDs (adjust for your server)
local TREES = { 2700, 2701, 2702, 2703, 2704, 2706, 2707, 2708, 2709 }
local FLOWERS = { 2725, 2726, 2727, 2728 }
local MUSHROOMS = { 2736, 2737, 2738, 2739 }
print(string.format("[Forest] Generating %dx%d forest at (%d,%d,%d)...", w, h, cx, cy, cz))
-- Generate tree positions using Poisson Disk Sampling
local treePositions = geo.poissonDiskSampling(cx, cy, cx + w, cy + h, spacing, {
seed = seed,
})
print(string.format("[Forest] Poisson disk generated %d tree positions (spacing=%d)", #treePositions, spacing))
-- Helper: check if point is inside organic shape
local function isInsideForest(x, y)
if not useOrganic then
return true
end
local centerX = cx + w / 2
local centerY = cy + h / 2
local dx = (x - centerX) / (w / 2)
local dy = (y - centerY) / (h / 2)
local dist = math.sqrt(dx * dx + dy * dy)
local noiseVal = noise.simplex(x * 1.0, y * 1.0, seed, 0.08)
local threshold = 0.85 + noiseVal * 0.25
return dist <= threshold
end
local map = app.map
local treeCount = 0
local flowerCount = 0
local groundCount = 0
math.randomseed(seed)
app.transaction("Generate Forest", function()
-- Pass 1: Paint ground
for dy = 0, h do
for dx = 0, w do
local x = cx + dx
local y = cy + dy
if isInsideForest(x, y) then
local tile = map:getOrCreateTile(x, y, cz)
if tile then
tile:applyBrush(groundBrush, false)
groundCount = groundCount + 1
end
end
end
end
-- Pass 2: Place trees
for _, pos in ipairs(treePositions) do
local x = math.floor(pos.x)
local y = math.floor(pos.y)
if isInsideForest(x, y) then
local tile = map:getOrCreateTile(x, y, cz)
if tile and tile.hasGround then
local treeId = TREES[math.random(1, #TREES)]
tile:addItem(treeId)
treeCount = treeCount + 1
end
end
end
-- Pass 3: Scatter flowers/mushrooms between trees
if useFlowers then
local decoPositions = geo.randomScatter(cx + 1, cy + 1, cx + w - 1, cy + h - 1, math.floor(w * h * 0.08), {
seed = seed + 200,
minDistance = 1,
})
for _, pos in ipairs(decoPositions) do
local x = math.floor(pos.x)
local y = math.floor(pos.y)
if isInsideForest(x, y) then
local tile = map:getTile(x, y, cz)
if tile and tile.hasGround and tile.itemCount < 2 then
local decoList = math.random() > 0.7 and MUSHROOMS or FLOWERS
local decoId = decoList[math.random(1, #decoList)]
tile:addItem(decoId)
flowerCount = flowerCount + 1
end
end
end
end
-- Pass 4: Borderize
for dy = -2, h + 2 do
for dx = -2, w + 2 do
local tile = map:getOrCreateTile(cx + dx, cy + dy, cz)
if tile then
tile:borderize()
end
end
end
end)
app.setCameraPosition(cx + math.floor(w / 2), cy + math.floor(h / 2), cz)
print(string.format("[Forest] Done! Ground: %d, Trees: %d, Flowers: %d", groundCount, treeCount, flowerCount))