Files
LilGuy/internal/portal/portal.go

289 lines
5.4 KiB
Go

package portal
import (
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"github.com/atridad/LilGuy/internal/config"
)
type PortalSide int
const (
SideLeft PortalSide = iota
SideRight
SideTop
SideBottom
)
func (s PortalSide) String() string {
switch s {
case SideLeft:
return "Left"
case SideRight:
return "Right"
case SideTop:
return "Top"
case SideBottom:
return "Bottom"
default:
return "Unknown"
}
}
type Portal struct {
ID string
X float64
Y float64
Width float64
Height float64
Side PortalSide
DestinationMap string
DestinationPortal string
Color color.NRGBA
GlowColor color.NRGBA
Enabled bool
Visible bool
GlowIntensity float64
SpawnOffsetX float64
SpawnOffsetY float64
}
type TransitionEvent struct {
Portal *Portal
HeroX float64
HeroY float64
DestinationMap string
DestinationPortal string
}
func NewPortal(id string, side PortalSide, x, y, width, height float64) *Portal {
return &Portal{
ID: id,
X: x,
Y: y,
Width: width,
Height: height,
Side: side,
Color: color.NRGBA{R: 100, G: 100, B: 255, A: 180},
GlowColor: color.NRGBA{R: 150, G: 150, B: 255, A: 100},
Enabled: true,
Visible: true,
GlowIntensity: 0,
}
}
// Contains checks if a point is within the portal bounds
func (p *Portal) Contains(x, y float64) bool {
return x >= p.X && x <= p.X+p.Width && y >= p.Y && y <= p.Y+p.Height
}
// Overlaps checks if a rectangular area overlaps with the portal
func (p *Portal) Overlaps(x, y, width, height float64) bool {
return x+width > p.X && x < p.X+p.Width &&
y+height > p.Y && y < p.Y+p.Height
}
func (p *Portal) CanTransition() bool {
return p.Enabled && p.DestinationMap != ""
}
func (p *Portal) Update(dt float64) {
if !p.Enabled {
return
}
if dt > 0.1 {
dt = 0.1
}
p.GlowIntensity += dt * 2.0
if p.GlowIntensity > 6.28 { // 2*PI
p.GlowIntensity -= 6.28
}
}
func (p *Portal) Draw(screen *ebiten.Image) {
if !p.Visible {
return
}
drawX := float32(int(p.X + 0.5))
drawY := float32(int(p.Y + 0.5))
drawWidth := float32(int(p.Width + 0.5))
drawHeight := float32(int(p.Height + 0.5))
vector.FillRect(
screen,
drawX,
drawY,
drawWidth,
drawHeight,
p.Color,
false,
)
if p.Enabled {
glowPulse := 0.5 + 0.5*float64(p.GlowIntensity)/6.28
glowAlpha := uint8(float64(p.GlowColor.A) * glowPulse)
glowColor := color.NRGBA{
R: p.GlowColor.R,
G: p.GlowColor.G,
B: p.GlowColor.B,
A: glowAlpha,
}
borderWidth := float32(4)
vector.StrokeRect(
screen,
drawX-borderWidth/2,
drawY-borderWidth/2,
drawWidth+borderWidth,
drawHeight+borderWidth,
borderWidth,
glowColor,
false,
)
}
}
type Manager struct {
Portals []*Portal
OnTransition func(event TransitionEvent)
transitionCooldown float64
}
func NewManager() *Manager {
return &Manager{
Portals: make([]*Portal, 0),
transitionCooldown: 0,
}
}
func (m *Manager) AddPortal(portal *Portal) {
m.Portals = append(m.Portals, portal)
}
func (m *Manager) GetPortalByID(id string) *Portal {
for _, p := range m.Portals {
if p.ID == id {
return p
}
}
return nil
}
func (m *Manager) RemovePortal(id string) {
for i, p := range m.Portals {
if p.ID == id {
m.Portals = append(m.Portals[:i], m.Portals[i+1:]...)
return
}
}
}
func (m *Manager) Clear() {
m.Portals = make([]*Portal, 0)
}
func (m *Manager) Update(dt float64) {
// clamp delta time to prevent physics issues
if dt > 0.1 {
dt = 0.1
}
for _, p := range m.Portals {
p.Update(dt)
}
if m.transitionCooldown > 0 {
m.transitionCooldown -= dt
if m.transitionCooldown < 0 {
m.transitionCooldown = 0
}
}
}
func (m *Manager) CheckCollision(x, y, width, height float64) *Portal {
if m.transitionCooldown > 0 {
return nil
}
for _, p := range m.Portals {
if !p.CanTransition() {
continue
}
if p.Overlaps(x, y, width, height) {
return p
}
}
return nil
}
func (m *Manager) TriggerTransition(portal *Portal, heroX, heroY float64) {
if portal == nil || !portal.CanTransition() {
return
}
if m.OnTransition != nil {
event := TransitionEvent{
Portal: portal,
HeroX: heroX,
HeroY: heroY,
DestinationMap: portal.DestinationMap,
DestinationPortal: portal.DestinationPortal,
}
m.OnTransition(event)
}
m.transitionCooldown = config.PortalTransitionCooldown
}
func (m *Manager) Draw(screen *ebiten.Image) {
for _, p := range m.Portals {
p.Draw(screen)
}
}
func CreateSidePortal(id string, side PortalSide, screenWidth, screenHeight float64) *Portal {
var x, y, width, height float64
switch side {
case SideLeft:
x = 0
y = 0
width = config.PortalThickness
height = screenHeight
case SideRight:
x = screenWidth - config.PortalThickness
y = 0
width = config.PortalThickness
height = screenHeight
case SideTop:
x = 0
y = 0
width = screenWidth
height = config.PortalThickness
case SideBottom:
x = 0
y = screenHeight - config.PortalThickness
width = screenWidth
height = config.PortalThickness
}
return NewPortal(id, side, x, y, width, height)
}
func CreateDoorPortal(id string, side PortalSide, x, y float64) *Portal {
const doorWidth = 80.0
const doorHeight = 120.0
return NewPortal(id, side, x, y, doorWidth, doorHeight)
}