aboutsummaryrefslogtreecommitdiff
path: root/src/specks.lua
diff options
context:
space:
mode:
authoriamcheeseman <[email protected]>2026-03-30 17:52:23 -0400
committeriamcheeseman <[email protected]>2026-03-30 17:52:23 -0400
commitf52aaaad971d11fa145a391e521e37ea3c3b230e (patch)
treebaf5ae7bd8b284209a2551386fd324b5f57e7d44 /src/specks.lua
parent7859709ac3b1500e3037c5dceacb77bcff778e89 (diff)
parentfeafbd1e1a6a1846ab5fe416ee31e2e14f603edd (diff)
Merge branch 'main' of ssh://codeberg.org/the-gutter/gutshot
Diffstat (limited to 'src/specks.lua')
-rw-r--r--src/specks.lua283
1 files changed, 283 insertions, 0 deletions
diff --git a/src/specks.lua b/src/specks.lua
new file mode 100644
index 0000000..a703d04
--- /dev/null
+++ b/src/specks.lua
@@ -0,0 +1,283 @@
+Speck_Sys = {}
+Speck_Sys.__index = Speck_Sys
+
+local SPAWN_FUNCTIONS = {
+ Rectangle = function (speck_sys)
+ local w = (speck_sys.spawn_width or 0) / 2
+ local h = (speck_sys.spawn_height or 0) / 2
+
+ return randf_range(-w, w), randf_range(-h, h)
+ end,
+
+ Circle = function (speck_sys)
+ local angle = randf_range(0, math.pi * 2)
+ local r = speck_sys.spawn_radius or 0
+ r = r * lmath.random()
+ return r*math.cos(angle), r*math.sin(angle)
+ end,
+
+ Point = function (speck_sys)
+ return 0, 0
+ end
+}
+
+function Speck_Sys.new()
+ local self = setmetatable({}, Speck_Sys)
+
+ self.data = {
+ size = 0,
+ pos = {},
+ vel = {},
+
+ alive = {},
+ lifetime = {},
+ lifetime_max = {},
+
+ scale_start = {},
+ scale_end = {}
+ }
+
+ self.free_ids = {}
+ self.free_ids_size = 0
+
+ self.bounce = false
+ self.gradient = "res/img/test_gradient.png"
+
+ self.spawn_shape = "Point"
+
+ self.spawn_amount_min = 1
+ self.spawn_amount_max = 1
+
+ self.scale_curve = "Lerp"
+ self.scale_start_min = 0.8
+ self.scale_start_max = 1.25
+
+ self.scale_end_min = 0
+ self.scale_end_max = 0
+
+ self.forcex = 0
+ self.forcey = 0
+
+ self.initial_velx = 0
+ self.initial_vely = 0
+ self.spread = 0
+
+ self.lifetime_min = 1
+ self.lifetime_max = 5
+
+ self.interval = 0.1
+
+ self.texture_path = "res/img/speck.png"
+
+ self.x = 0
+ self.y = 0
+ self.spawn_timer = self.interval
+
+ self.oneshot = false
+ self.emitting = true
+ return self
+end
+
+function Speck_Sys:check_bounce(i)
+ if not self.bounce then
+ return false
+ end
+
+ local scn = get_current_scene()
+ assert(scn, "No scene set.")
+
+ return has_tile(
+ scn.tilemap,
+ to_tile_coords(self.data.pos[i].x, self.data.pos[i].y))
+end
+
+function Speck_Sys:spawn_particles()
+ local data = self.data
+ local amt = lmath.random(self.spawn_amount_min, self.spawn_amount_max)
+
+ local shape_func = SPAWN_FUNCTIONS[self.spawn_shape]
+ for i = 1, amt do
+ local id
+ if self.free_ids_size > 0 then
+ id = self.free_ids[self.free_ids_size]
+ self.free_ids_size = self.free_ids_size - 1
+ else
+ self.data.size = self.data.size + 1
+ id = self.data.size
+ end
+
+ data.alive[id] = true
+
+ local offx, offy = shape_func(self)
+ data.pos[id] = { x = self.x + offx, y = self.y + offy }
+ data.vel[id] = { x = self.initial_velx, y = self.initial_vely }
+
+ local beta = self.spread * math.pi / 180
+ beta = beta / 2
+ beta = randf_range(-beta, beta)
+
+ local rvx = (data.vel[id].x * math.cos(beta))
+ - (data.vel[id].y * math.sin(beta))
+ local rvy = (data.vel[id].x * math.sin(beta))
+ + (data.vel[id].y * math.cos(beta))
+ data.vel[id].x = rvx
+ data.vel[id].y = rvy
+
+ data.lifetime[id] = randf_range(
+ self.lifetime_min, self.lifetime_max)
+ data.lifetime_max[id] = data.lifetime[id]
+
+ data.scale_start[id] = randf_range(
+ self.scale_start_min, self.scale_start_max)
+ data.scale_end[id] = randf_range(
+ self.scale_end_min, self.scale_end_max)
+ end
+end
+
+function Speck_Sys:update(dt)
+ local data = self.data
+
+ -- particle spawning
+ self.spawn_timer = self.spawn_timer - dt
+ if self.spawn_timer <= 0 and self.emitting then
+ self.spawn_timer = self.interval
+
+ self:spawn_particles()
+ if self.oneshot then
+ self.emitting = false
+ end
+ end
+
+ -- particles processing
+ for i = 1, data.size do
+ if not data.alive[i] then
+ goto next_speck_update
+ end
+
+ if data.lifetime[i] <= 0 then
+ data.alive[i] = false
+
+ self.free_ids_size = self.free_ids_size + 1
+ self.free_ids[self.free_ids_size] = i
+ goto next_speck_update
+ end
+
+ data.vel[i].x = data.vel[i].x + self.forcex * dt
+ data.vel[i].y = data.vel[i].y + self.forcey * dt
+
+ -- move and bounce
+ data.pos[i].x = data.pos[i].x + data.vel[i].x * dt
+ if self:check_bounce(i) then
+ data.vel[i].x = -data.vel[i].x
+ data.pos[i].x = data.pos[i].x + data.vel[i].x * dt
+ end
+
+ data.pos[i].y = data.pos[i].y + data.vel[i].y * dt
+ if self:check_bounce(i) then
+ data.vel[i].y = -data.vel[i].y
+ data.pos[i].y = data.pos[i].y + data.vel[i].y * dt
+ end
+
+ data.lifetime[i] = data.lifetime[i] - dt
+
+ ::next_speck_update::
+ end
+end
+
+function Speck_Sys:is_empty()
+ if self.data.size == 0 then
+ return true
+ end
+ return self.free_ids_size == self.data.size
+end
+
+function Speck_Sys:draw()
+ local scale_curve = EASING_FUNCTIONS[self.scale_curve]
+ local data = self.data
+ local tex = get_tex(self.texture_path)
+
+ for i = 1, data.size do
+ if not data.alive[i] then
+ goto next_speck_draw
+ end
+
+ local anim = 1 - data.lifetime[i] / data.lifetime_max[i]
+ local scale = scale_curve(data.scale_start[i], data.scale_end[i], anim)
+
+ local w, h = tex:getDimensions()
+
+ local gradient = get_tex_data(self.gradient)
+ local sample_pos = math.floor((gradient:getWidth() - 1) * anim)
+ lg.setColor(gradient:getPixel(sample_pos, 0.5))
+
+ lg.draw(
+ tex, data.pos[i].x, data.pos[i].y, 0, scale, scale, w / 2, h / 2
+ )
+ lg.setColor(1, 1, 1, 1)
+ ::next_speck_draw::
+ end
+end
+
+
+function export_speck_sys(sys, filename)
+ local EXPORTED_ARGS = {
+ "spawn_shape",
+ "spawn_amount_min",
+ "spawn_amount_max",
+ "spawn_width",
+ "spawn_height",
+ "spawn_radius",
+ "scale_curve",
+ "scale_start_min",
+ "scale_start_max",
+ "scale_end_min",
+ "scale_end_max",
+ "forcex",
+ "forcey",
+ "initial_velx",
+ "initial_vely",
+ "spread",
+ "lifetime_min",
+ "lifetime_max",
+ "interval",
+ "texture_path",
+ "gradient",
+ "oneshot",
+ "bounce",
+ }
+ local exp = {}
+
+ for i, arg in ipairs(EXPORTED_ARGS) do
+ exp[arg] = sys[arg]
+ end
+ export_to_source(exp, filename)
+end
+
+local speck_bank = {}
+function load_specks_from(path)
+ path = path or "res/speck"
+ local files = lf.getDirectoryItems(path)
+
+ for _, file in ipairs(files) do
+ local filepath = path.."/"..file
+
+ if lf.getInfo(filepath).type == "directory" then
+ load_specks_from(filepath)
+ else
+ if is_filetype(filepath, {"speck.lua"}) then
+ local data = lf.load(filepath)()
+ speck_bank[filepath] = data
+ end
+ end
+ end
+end
+
+function load_speck_sys(filename)
+ local sys = Speck_Sys.new()
+ local loaded = speck_bank[filename]
+ for key, data in pairs(loaded) do
+ sys[key] = data
+ end
+ return sys
+end
+