1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-10 02:26:29 -05:00
OpenDiablo2/d2core/d2systems/sprite_factory.go
gravestench 05dae775e4 scene rendering now works
* scene systems now render their objects to the main viewport
* added SegmentedSprite component
* added SegmentedSprite to sprite factory
2020-12-07 12:44:11 -08:00

240 lines
6.0 KiB
Go

package d2systems
import (
"github.com/gravestench/akara"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2animation"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
)
const (
fmtCreateSpriteErr = "could not create sprite from image `%s` and palette `%s`"
)
// NewSpriteFactorySubsystem creates a new sprite factory which is intended
// to be embedded in the game object factory system.
func NewSpriteFactorySubsystem(b *akara.BaseSystem, l *d2util.Logger) *SpriteFactory {
spritesToRender := akara.NewFilter().
Require(d2components.Animation). // we want to process entities that have an animation ...
Forbid(d2components.Renderable). // ... but are missing a surface
Build()
spritesToUpdate := akara.NewFilter().
Require(d2components.Animation). // we want to process entities that have an animation ...
Require(d2components.Renderable). // ... but are missing a surface
Build()
sys := &SpriteFactory{
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(spritesToRender, spritesToUpdate),
Logger: l,
}
sys.BaseSystem = b
sys.World.AddSystem(sys)
return sys
}
type spriteLoadQueueEntry struct {
spriteImage, spritePalette akara.EID
}
type spriteLoadQueue = map[akara.EID]spriteLoadQueueEntry
// SpriteFactory is responsible for queueing sprites to be loaded (as animations),
// as well as binding the animation to a renderer if one is present (which generates the sprite surfaces).
type SpriteFactory struct {
*akara.BaseSubscriberSystem
*d2util.Logger
*RenderSystem
*d2components.FilePathMap
*d2components.PositionMap
*d2components.Dc6Map
*d2components.DccMap
*d2components.PaletteMap
*d2components.AnimationMap
*d2components.RenderableMap
*d2components.SegmentedSpriteMap
loadQueue spriteLoadQueue
spritesToRender *akara.Subscription
spritesToUpdate *akara.Subscription
}
// Init the sprite factory, injecting the necessary components
func (t *SpriteFactory) Init(world *akara.World) {
t.Info("initializing sprite factory ...")
t.loadQueue = make(spriteLoadQueue)
t.spritesToRender = t.Subscriptions[0]
t.spritesToUpdate = t.Subscriptions[1]
t.FilePathMap = t.InjectMap(d2components.FilePath).(*d2components.FilePathMap)
t.PositionMap = t.InjectMap(d2components.Position).(*d2components.PositionMap)
t.Dc6Map = t.InjectMap(d2components.Dc6).(*d2components.Dc6Map)
t.DccMap = t.InjectMap(d2components.Dcc).(*d2components.DccMap)
t.PaletteMap = t.InjectMap(d2components.Palette).(*d2components.PaletteMap)
t.AnimationMap = t.InjectMap(d2components.Animation).(*d2components.AnimationMap)
t.RenderableMap = t.InjectMap(d2components.Renderable).(*d2components.RenderableMap)
t.SegmentedSpriteMap = t.InjectMap(d2components.SegmentedSprite).(*d2components.SegmentedSpriteMap)
}
// Update processes the load queue which attempting to create animations, as well as
// binding existing animations to a renderer if one is present.
func (t *SpriteFactory) Update() {
for _, eid := range t.spritesToUpdate.GetEntities() {
t.updateSprite(eid)
}
for _, eid := range t.spritesToRender.GetEntities() {
t.tryRenderingSprite(eid)
}
for spriteID := range t.loadQueue {
t.tryCreatingAnimation(spriteID)
}
}
// Sprite queues a sprite animation to be loaded
func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID {
spriteID := t.NewEntity()
t.AddPosition(spriteID).Set(x, y)
imgID, palID := t.NewEntity(), t.NewEntity()
t.AddFilePath(imgID).Path = imgPath
t.AddFilePath(palID).Path = palPath
t.loadQueue[spriteID] = spriteLoadQueueEntry{
spriteImage: imgID,
spritePalette: palID,
}
return spriteID
}
// SegmentedSprite queues a segmented sprite animation to be loaded.
// A segmented sprite is a sprite that has many frames that form the entire sprite.
func (t *SpriteFactory) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID {
spriteID := t.Sprite(x, y, imgPath, palPath)
s := t.AddSegmentedSprite(spriteID)
s.Xsegments = xseg
s.Ysegments = yseg
s.FrameOffset = frame
return spriteID
}
func (t *SpriteFactory) tryCreatingAnimation(id akara.EID) {
entry := t.loadQueue[id]
imageID, paletteID := entry.spriteImage, entry.spritePalette
imagePath, found := t.GetFilePath(imageID)
if !found {
return
}
palettePath, found := t.GetFilePath(paletteID)
if !found {
return
}
palette, found := t.GetPalette(paletteID)
if !found {
return
}
var anim d2interface.Animation
var err error
if dc6, found := t.GetDc6(imageID); found {
anim, err = t.createDc6Animation(dc6, palette)
}
if dcc, found := t.GetDcc(imageID); found {
anim, err = t.createDccAnimation(dcc, palette)
}
if err != nil {
t.Errorf(fmtCreateSpriteErr, imagePath.Path, palettePath.Path)
t.RemoveEntity(id)
t.RemoveEntity(imageID)
t.RemoveEntity(paletteID)
}
t.AddAnimation(id).Animation = anim
delete(t.loadQueue, id)
}
func (t *SpriteFactory) tryRenderingSprite(eid akara.EID) {
if t.RenderSystem == nil {
return
}
if t.RenderSystem.renderer == nil {
return
}
anim, found := t.GetAnimation(eid)
if !found {
return
}
if anim.Animation == nil {
return
}
anim.BindRenderer(t.renderer)
sfc := anim.GetCurrentFrameSurface()
t.AddRenderable(eid).Surface = sfc
}
func (t *SpriteFactory) updateSprite(eid akara.EID) {
if t.RenderSystem == nil {
return
}
if t.RenderSystem.renderer == nil {
return
}
anim, found := t.GetAnimation(eid)
if !found {
return
}
if anim.Animation == nil {
return
}
renderable, found := t.GetRenderable(eid)
if !found {
return
}
renderable.Surface = anim.GetCurrentFrameSurface()
}
func (t *SpriteFactory) createDc6Animation(
dc6 *d2components.Dc6Component,
pal *d2components.PaletteComponent,
) (d2interface.Animation, error) {
return d2animation.NewDC6Animation(dc6.DC6, pal.Palette, 0)
}
func (t *SpriteFactory) createDccAnimation(
dcc *d2components.DccComponent,
pal *d2components.PaletteComponent,
) (d2interface.Animation, error) {
return d2animation.NewDCCAnimation(dcc.DCC, pal.Palette, 0)
}