local LiquidSurface = {} LiquidSurface.__index = LiquidSurface local DEFAULT_COLORS = { water = { 0.35, 0.65, 1, 0.5}, lava = { 1, 0.35, 0.1, 0.8 } } local function toNumber(value, fallback) local number = tonumber(value) if number == nil then return fallback end return number end local function getSurfaceBounds(entity) local polygon = entity and entity.polygon or nil if not polygon or #polygon == 0 then return entity.x or 0, entity.y or 0, entity.width or 0, entity.height or 0 end local minX, minY = math.huge, math.huge local maxX, maxY = -math.huge, -math.huge for _, point in ipairs(polygon) do local px = (entity.x or 0) + (point.x or 0) local py = (entity.y or 0) + (point.y or 0) minX = math.min(minX, px) minY = math.min(minY, py) maxX = math.max(maxX, px) maxY = math.max(maxY, py) end return minX, minY, maxX - minX, maxY - minY end function LiquidSurface:new(source) local self = setmetatable({}, LiquidSurface) local properties = source.properties or {} local x, y, width, height = getSurfaceBounds(source) local configuredHeight = toNumber(properties.height, height) self.x = x self.y = y self.width = math.max(2, width) self.height = math.max(1, configuredHeight) self.bottomY = self.y + self.height self.type = "water" self.liquidType = properties.liquid_type or "water" self.color = DEFAULT_COLORS[self.liquidType] or DEFAULT_COLORS.water self.tension = 0.0002 self.dampening = 0.000005 self.spread = 0.05 self.propagationPasses = 8 self.maxSpeed = 20 self.maxWaveAmplitude = 1000 self.columnsLength = math.max(2, math.floor(self.width)) self.columns = {} for _ = 1, self.columnsLength do table.insert(self.columns, self:createColumn()) end return self end function LiquidSurface:createColumn() return { height = self.height, targetHeight = self.height, speed = 0 } end function LiquidSurface:isFixedColumn(index) return index <= 2 or index >= self.columnsLength - 1 end function LiquidSurface:updateColumn(index, dtScale) if self:isFixedColumn(index) then return end local column = self.columns[index] local heightDiff = column.targetHeight - column.height local accel = self.tension * heightDiff - column.speed * self.dampening column.speed = column.speed + accel * dtScale column.speed = math.max(-self.maxSpeed, math.min(self.maxSpeed, column.speed)) column.height = column.height + column.speed * dtScale local lo = column.targetHeight - self.maxWaveAmplitude local hi = column.targetHeight + self.maxWaveAmplitude column.height = math.max(lo, math.min(hi, column.height)) end function LiquidSurface:splash(worldX, speed) if worldX < self.x or worldX >= (self.x + self.columnsLength) then return end local index = math.min(self.columnsLength, math.max(1, math.floor(worldX - self.x) + 1)) if self:isFixedColumn(index) then return end self.columns[index].speed = self.columns[index].speed - (speed or 2) end function LiquidSurface:isTouched(x, y, dx, dy) return math.abs(y - self.y) < math.abs(dy) and x >= self.x and x < (self.x + self.columnsLength) end function LiquidSurface:update(dt) local dtScale = math.max(0, dt or 0) * 60 for i = 1, self.columnsLength do self:updateColumn(i, dtScale) end local leftDeltas = {} local rightDeltas = {} for _ = 1, self.propagationPasses do for i = 1, self.columnsLength do if i > 1 and not self:isFixedColumn(i - 1) then leftDeltas[i] = self.spread * (self.columns[i].height - self.columns[i - 1].height) * dtScale self.columns[i - 1].speed = self.columns[i - 1].speed + leftDeltas[i] end if i < self.columnsLength and not self:isFixedColumn(i + 1) then rightDeltas[i] = self.spread * (self.columns[i].height - self.columns[i + 1].height) * dtScale self.columns[i + 1].speed = self.columns[i + 1].speed + rightDeltas[i] end end for i = 1, self.columnsLength do if i > 1 and not self:isFixedColumn(i - 1) and leftDeltas[i] then self.columns[i - 1].height = self.columns[i - 1].height + leftDeltas[i] end if i < self.columnsLength and not self:isFixedColumn(i + 1) and rightDeltas[i] then self.columns[i + 1].height = self.columns[i + 1].height + rightDeltas[i] end end end end function LiquidSurface:drawFill() love.graphics.setColor(self.color) for i = 2, self.columnsLength do local p1x = self.x + (i - 2) local p1y = self.bottomY - self.columns[i - 1].height local p2x = self.x + (i - 1) local p2y = self.bottomY - self.columns[i].height local p3x, p3y = p2x, self.bottomY local p4x, p4y = p1x, self.bottomY love.graphics.polygon("fill", p1x, p1y, p3x, p3y, p2x, p2y, p3x, p3y, p4x, p4y, p2x, p2y ) end end function LiquidSurface:drawLine() local linePoints = {} for i = 1, self.columnsLength do linePoints[#linePoints + 1] = self.x + (i - 1) linePoints[#linePoints + 1] = self.bottomY - self.columns[i].height end love.graphics.setColor(1, 1, 1, 1) love.graphics.setLineWidth(1) love.graphics.setLineStyle("rough") love.graphics.line(linePoints) love.graphics.setLineStyle("smooth") love.graphics.setColor(1, 1, 1, 1) end function LiquidSurface:draw() self:drawFill() self:drawLine() end return LiquidSurface