package gfx import ( "io/fs" "path/filepath" "github.com/chompy/roguelike_rpg/internal/util" "github.com/hajimehoshi/ebiten/v2" lua "github.com/rosbit/luago" ) type Effect interface { Loop() bool Apply(time float32, op *ebiten.DrawImageOptions) (bool, error) Draw(time float32, screen *ebiten.Image, image *ebiten.Image, op *ebiten.DrawImageOptions) (bool, error) } func ApplyEffects(effects []Effect, time float32, op *ebiten.DrawImageOptions) (bool, error) { if op == nil { op = &ebiten.DrawImageOptions{} } hasPlaying := false for _, effect := range effects { playing, err := effect.Apply(time, op) if err != nil { return false, err } if playing && !effect.Loop() { hasPlaying = true } } return hasPlaying, nil } // --- LUA VISUAL EFFECT type LuaEffect struct { loop bool ctx *lua.LuaContext } func LoadLuaEffectFromPath(assetFS fs.FS, path string) (*LuaEffect, error) { luaScript, err := util.ReadString(assetFS, path) if err != nil { return nil, err } luaCtx, err := lua.NewContext() if err != nil { return nil, err } if err := luaCtx.LoadScript(luaScript, map[string]any{}); err != nil { return nil, err } res, err := luaCtx.CallFunc("Loop") if err != nil { return nil, err } loop, ok := res.(bool) if !ok { loop = false } return &LuaEffect{ loop: loop, ctx: luaCtx, }, nil } func LoadLuaEffect(assetFS fs.FS, name string) (*LuaEffect, error) { return LoadLuaEffectFromPath(assetFS, filepath.Join("gfx", "effects", name+".lua")) } func (e *LuaEffect) Loop() bool { return e.loop } func (e *LuaEffect) Apply(time float32, op *ebiten.DrawImageOptions) (bool, error) { res, err := e.ctx.CallFunc("Apply", time, map[string]any{ "GeoM": &op.GeoM, "ColorScale": &op.ColorScale, }) if err != nil { return false, err } playing, ok := res.(bool) if ok { return playing, nil } return false, nil } func (e *LuaEffect) Draw(time float32, screen *ebiten.Image, image *ebiten.Image, op *ebiten.DrawImageOptions) (bool, error) { return false, nil } // --- SHADER VISUAL EFFECT type ShaderEffect struct { duration float32 shader *ebiten.Shader loop bool } func LoadShaderEffectFromPath(assetFS fs.FS, path string) (*ShaderEffect, error) { shaderBytes, err := util.ReadBytes(assetFS, path) if err != nil { return nil, err } shader, err := ebiten.NewShader(shaderBytes) if err != nil { return nil, err } return &ShaderEffect{ duration: 0.5, shader: shader, loop: false, }, nil } func LoadShaderEffect(assetFS fs.FS, name string) (*ShaderEffect, error) { return LoadShaderEffectFromPath(assetFS, filepath.Join("gfx", "effects", name+".kage")) } func (e *ShaderEffect) Loop() bool { return e.loop } func (e *ShaderEffect) Apply(time float32, op *ebiten.DrawImageOptions) (bool, error) { return time < e.duration || e.loop, nil } func (e *ShaderEffect) Draw(time float32, screen *ebiten.Image, image *ebiten.Image, op *ebiten.DrawImageOptions) (bool, error) { imageSize := image.Bounds().Size() sop := &ebiten.DrawRectShaderOptions{} sop.Uniforms = map[string]any{ "Time": time, } sop.GeoM = op.GeoM sop.Images = [4]*ebiten.Image{image, image, image, image} screen.DrawRectShader(imageSize.X, imageSize.Y, e.shader, sop) return time < e.duration || e.loop, nil } func (e *ShaderEffect) Duration() float32 { return e.duration }