Skip to content

Instantly share code, notes, and snippets.

@CheezusChrust
Last active July 27, 2023 20:10
Show Gist options
  • Save CheezusChrust/18845f7b41b9cceb171aa52a10d19855 to your computer and use it in GitHub Desktop.
Save CheezusChrust/18845f7b41b9cceb171aa52a10d19855 to your computer and use it in GitHub Desktop.
Basic StarfallEx chip for placing vegetation on maps - this chip is old and probably buggy, be warned
--@name Vegetation Painter
--@author Cheezus
--@shared
--[[
Basic usage:
0. Start Hammer on the SAME VERSION of the map you're currently playing on; you may need to do a fresh compile
1. Define whatever foliage models you want placed using vegetation.models table below
2. Spawn the chip and hold your use key (default E) to place foliage and your walk key (default ALT) to erase
3. Type /export to export current vegetation models to Hammer
4. Type /clear to remove current vegetation models from Hammer (in case you've made a mistake)
5.1. This will NOT work if you despawn and respawn the chip, in that case you need to remove the vegetation
in hammer manually. Sorry!
--]]
local radius = 500 --Default radius to paint foliage in, in source units - change this with '/radius'
if SERVER then
local vegetation = {}
--[[
Foliage model format:
model: What model to use
density: How often this model appears relative to all other models
radius: Distance from the center of the model to not spawn other foliage of the same type, in source units
heightOffset: Height offset, in source units
maxAngle: Don't spawn the model on terrain steeper than this angle, in degrees
useGroundAngle: Make this model conform to the angle of the terrain (good for grass and bushes)
type: models of dissimilar types will ignore eachother in terms of whether they can spawn in a certain
location or not
spawnOn: Table of physprops to spawn this type of vegetation on - see all at https://wiki.facepunch.com/gmod/Enums/MAT
(Optional) additionalKeyValues: Table of extra keyvalues to be added to the entities
EXAMPLE:
{
model = "models/props_foliage/tree_poplar_01.mdl",
density = 150,
radius = 300,
heightOffset = 0,
maxAngle = 35,
useGroundAngle = false,
type = "tree",
spawnOn = {trace.MAT_GRASS},
},
Additional Information:
- You can define keyvalues to be set on ALL entities after the vegetation.models table
- A model's additional keyvalues override global keyvalues if two of the same type are specified
--]]
vegetation.models = {
{
model = "models/foliage/tree05.mdl",
density = 150,
radius = 300,
heightOffset = 0,
maxAngle = 35,
useGroundAngle = false,
type = "tree",
spawnOn = {trace.MAT_GRASS}
},
{
model = "models/foliage/tree03b.mdl",
density = 50,
radius = 150,
heightOffset = 0,
maxAngle = 35,
useGroundAngle = false,
type = "tree",
spawnOn = {trace.MAT_GRASS}
},
{
model = "models/foliage/grass_01.mdl",
density = 50,
radius = 100,
heightOffset = 40,
maxAngle = 35,
useGroundAngle = false,
type = "grass",
spawnOn = {trace.MAT_GRASS}
}
}
vegetation.globalKeyValues = {
screenspacefade = 1,
fademindist = 100,
fademaxdist = 90,
disableselfshadowing = 1
}
local maxCPU = 0.75 --Maximum CPU the chip is allowed to use, 0 being none and 1 being the max your current sf_timebuffer allows
--=======================End of Settings=======================--
vegetation.data = {} --stupid workaround for stupid bug
vegetation.sumOfWeights = 0
local processing = false
for _, model in pairs(vegetation.models) do
vegetation.sumOfWeights = vegetation.sumOfWeights + model.density
end
local function weightedSelection()
local randomWeight = math.rand(1, vegetation.sumOfWeights)
for _, model in pairs(vegetation.models) do
randomWeight = randomWeight - model.density
if randomWeight <= 0 then return model end
end
end
local function vecDiff(v1, v2)
return math.deg(math.acos(v1:dot(v2) / (v1:getLength() * v2:getLength())))
end
local function canRun(quota)
if quotaAverage() > quotaMax() * quota or quotaUsed() > quotaMax() * quota then return false end
return true
end
local function print2(...)
print(Color(100, 255, 100), "[Vegetation Placer] ", Color(255, 255, 255), ...)
end
local function printProgress(current, final, color)
color = color or Color(255, 255, 255)
local progress = math.round((current / final) * 20)
print(color, "[" .. string.rep("=", progress) .. ">" .. string.rep(" ", (20 - progress) * 2.4) .. "] " .. math.floor((current / final) * 100) .. "%")
end
if not hammer then
print2("Hammer library not detected, get it at https://github.com/CheezusChrust/Starfall-Hammer-Library")
end
print2("Hold USE to paint")
print2("Hold WALK to erase")
print2("Type '/export' to export to hammer")
print2("Type '/radius' to change paint radius")
print2("Type '/clear' to cleanup previously exported foliage")
hook.add("think", "paint", function()
if processing then return end
if owner():keyDown(IN_KEY.USE) then
while canRun(maxCPU) and prop.propsLeft() > 0 and holograms.hologramsLeft() > 0 do
--Select foliage model at random
local model = weightedSelection()
--Get where owner is looking
local eyeTrace = trace.trace(owner():getEyePos(), owner():getEyePos() + owner():getEyeAngles():getForward() * 100000, find.all())
--Select a random position and angle based on the above position
local pos = eyeTrace.HitPos + Vector(math.rand(-radius, radius), math.rand(-radius, radius), 0)
local ang = Angle(math.rand(-1, 1), math.rand(0, 359), math.rand(-1, 1))
--Trace the position from above to determine its properties
local groundTrace = trace.trace(pos + Vector(0, 0, 500), pos + Vector(0, 0, -100))
if model.useGroundAngle == true then
ang = groundTrace.HitNormal:getAngle() + Angle(90, 0, 0)
end
--Check radius of nearby foliage to prevent spawning inside eachother
local flag = false
find.inSphere(pos, model.radius, function(ent)
if not vegetation.data[ent] then return false end
if vegetation.data[ent].type == model.type and (ent:getPos() - pos):getLength() < model.radius then
flag = true
end
end)
if flag == true then continue end
--Ensure ground is one of the physprops to receive foliage
flag = true
for _, mat in pairs(model.spawnOn) do
if groundTrace.MatType == mat then
flag = false
end
end
if flag == true then continue end
--Ensure the ground isn't too steep for the foliage
if vecDiff(groundTrace.HitNormal, Vector(0, 0, 1)) > model.maxAngle then continue end
--Due to a recent change, prop.create can no longer place collisionless props ('effects')
--Need to create them as a hologram instead
local tree
local t = trace.trace(pos + Vector(0, 0, 750), pos + Vector(0, 0, -750)).HitPos + Vector(0, 0, model.heightOffset)
try(function()
tree = prop.create(t, ang, model.model, true)
end, function()
try(function()
tree = holograms.create(t, ang, model.model)
end, function(error)
print(Color(255, 0, 0), error.message .. " (" .. model.model .. ")")
end)
end)
if not tree then continue end
vegetation.data[tree] = {}
vegetation.data[tree].type = model.type
if model.additionalKeyValues then
vegetation.data[tree].additionalKeyValues = model.additionalKeyValues
end
end
end
if owner():keyDown(IN_KEY.WALK) then
ents = find.inSphere(owner():getEyeTrace().HitPos, radius)
for _, v in pairs(ents) do
if v:isValid() and vegetation.data[v] and vegetation.data[v].type ~= nil then
v:remove()
end
end
end
end)
hook.add("PlayerSay", "chatmsg", function(ply, text)
if ply ~= owner() then return end
if processing then return text end
chat = string.explode(" ", text)
if chat[1] == "/radius" then
if chat[2] then
radius = tonumber(chat[2])
print2("Radius set to " .. chat[2] .. " units")
net.start("radius")
net.writeInt(radius, 16)
net.send()
else
print2("Please specify a radius")
end
return ""
end
if chat[1] == "/export" then
if not hammer then
print2("Could not export due to missing Hammer library - get it at https://github.com/CheezusChrust/Starfall-Hammer-Library")
return ""
end
processing = true
local queue = find.all(function(ent)
if vegetation.data[ent] then return true end
end)
hammer.startSession()
print2("Export started - THIS MAY TAKE A LONG TIME")
local total = #queue
hook.add("think", "processQueue", function()
if canRun(maxCPU) and #queue > 0 then
local ent = queue[#queue]
hammer.createProp("static", ent:getModel(), ent:getPos(), ent:getAngles())
data = vegetation.data[ent]
if vegetation.globalKeyValues then
for k, v in pairs(vegetation.globalKeyValues) do
hammer.setKeyValue("prop_static", ent:getPos(), k, tostring(v))
end
end
if data.additionalKeyValues then
for k, v in pairs(data.additionalKeyValues) do
hammer.setKeyValue("prop_static", ent:getPos(), k, tostring(v))
end
end
table.remove(queue)
if #queue % 50 == 0 then
local perc = (total - #queue) / total
printProgress(perc, 1)
end
end
if #queue == 0 then
print2("Exported " .. total .. " entities")
hammer.endSession()
processing = false
hook.remove("think", "processQueue")
end
end)
return ""
end
if chat[1] == "/clear" then
if not hammer then
print2("Could not clear due to missing Hammer library - get it at https://github.com/CheezusChrust/Starfall-Hammer-Library")
return ""
end
processing = true
local queue = find.byClass("prop_physics", function(ent)
if vegetation.data[ent] then return true end
end)
hammer.startSession()
print2("Cleaning exported entities...")
local count = 0
local total = #queue
hook.add("think", "processQueue", function()
if canRun(maxCPU) and #queue > 0 then
local ent = queue[#queue]
count = count + 1
try(function()
hammer.removeEntity("prop_static", ent:getPos())
ent:remove()
end)
table.remove(queue)
local perc = (total - #queue) / total
if #queue % 50 == 0 then
printProgress(perc, 1)
end
end
if #queue == 0 then
print2("Removed " .. count .. " entities")
hammer.endSession()
processing = false
hook.remove("think", "processQueue")
end
end)
return ""
end
end)
elseif player() == owner() then
net.receive("radius", function()
radius = net.readInt(16)
end)
render.setHUDActive(true)
hook.add("drawHud", "hud", function()
render.pushViewMatrix({
type = "3D"
})
local hitPos = trace.trace(owner():getEyePos(), owner():getEyePos() + owner():getEyeAngles():getForward() * 100000, find.all()).HitPos --player():getEyeTrace().HitPos
render.setColor(Color(0, 255, 0))
render.draw3DWireframeBox(hitPos, Angle(0), Vector(radius):setZ(500), Vector(-radius):setZ(0))
render.popViewMatrix()
end)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment