289 lines
5.4 KiB
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)
|
|
}
|