diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2ce215ae6..b6b240221 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,6 +12,8 @@ New Features sprite's center as the anchor instead of the upper right. This is highly useful when dealing with rotated sprites. +- Animated sprites! Both animated GIF and WEBP images are supported. + - Sprite anti-aliasing, scaling and rotation! Optional separate X & Y scaling. - Video anti-aliasing, scaling and rotation! Optional separate X & Y scaling. diff --git a/src/singe.c b/src/singe.c index 229a58d12..2b5eeb0be 100644 --- a/src/singe.c +++ b/src/singe.c @@ -107,13 +107,19 @@ typedef struct SpriteS { int32_t id; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpadded" + IMG_Animation *animation; SDL_Surface *originalSurface; SDL_Surface *surface; #pragma GCC diagnostic pop - double angle; - double scaleX; - double scaleY; - int smooth; + double angle; + double scaleX; + double scaleY; + int32_t smooth; + int32_t currentFrame; + bool loop; + bool animating; + uint32_t lastTick; + uint32_t ticks; UT_hash_handle hh; } SpriteT; @@ -142,6 +148,7 @@ typedef struct VideoS { bool wasPlayingBeforePause; SDL_Texture *texture; SDL_Surface *surface; + SDL_Surface *rotatedZoomedSurface; double angle; double scaleX; double scaleY; @@ -389,13 +396,19 @@ int32_t apiSoundFullStop(lua_State *L); int32_t apiSoundUnload(lua_State *L); int32_t apiSpriteDraw(lua_State *L); +int32_t apiSpriteGetFrame(lua_State *L); int32_t apiSpriteGetHeight(lua_State *L); int32_t apiSpriteGetWidth(lua_State *L); +int32_t apiSpriteIsPlaying(lua_State *L); int32_t apiSpriteLoad(lua_State *L); +int32_t apiSpriteLoop(lua_State *L); +int32_t apiSpritePause(lua_State *L); +int32_t apiSpritePlay(lua_State *L); int32_t apiSpriteQuality(lua_State *L); int32_t apiSpriteRotate(lua_State *L); int32_t apiSpriteRotateAndScale(lua_State *L); int32_t apiSpriteScale(lua_State *L); +int32_t apiSpriteSetFrame(lua_State *L); int32_t apiSpriteUnload(lua_State *L); int32_t apiVideoDraw(lua_State *L); @@ -1932,11 +1945,12 @@ int32_t apiSoundUnload(lua_State *L) { int32_t apiSpriteDraw(lua_State *L) { - int32_t n = lua_gettop(L); - int32_t id = -1; - double d = 0; - bool center = false; - SpriteT *sprite = NULL; + int32_t n = lua_gettop(L); + int32_t id = -1; + double d = 0; + bool center = false; + bool newFrame = false; + SpriteT *sprite = NULL; SDL_Rect dest; int ox; int oy; @@ -1985,6 +1999,36 @@ int32_t apiSpriteDraw(lua_State *L) { //utilSay("spriteDraw: x=%d y=%d c=%d id=%d", dest.x, dest.y, center, id); HASH_FIND_INT(_global.spriteList, &id, sprite); if (!sprite) luaDie(L, "spriteDraw", "No sprite at index %d in apiSpriteDraw.", id); + // Figure out animation frame, if needed + if (sprite->animation != NULL && sprite->animating) { + // Find time passed + sprite->ticks += SDL_GetTicks() - sprite->lastTick; + sprite->lastTick = SDL_GetTicks(); + // Find desired frame + while (sprite->animating) { + if (sprite->animation->delays[sprite->currentFrame] < sprite->ticks) { + sprite->ticks -= sprite->animation->delays[sprite->currentFrame]; + sprite->currentFrame++; + newFrame = true; + if (sprite->currentFrame >= sprite->animation->count) { + if (sprite->loop) { + sprite->currentFrame = 0; + } else { + sprite->currentFrame = sprite->animation->count - 1; + sprite->animating = false; + } + } + } else { + break; + } + } + if (newFrame) { + SDL_FreeSurface(sprite->originalSurface); + sprite->originalSurface = surfaceCopy(sprite->animation->frames[sprite->currentFrame]); + SDL_FreeSurface(sprite->surface); + sprite->surface = rotozoomSurfaceXY(sprite->originalSurface, 360 - sprite->angle, sprite->scaleX, sprite->scaleY, sprite->smooth); + } + } if ((n == 3) || (n == 4)) { // No scaling, find width dest.w = sprite->surface->w; @@ -2016,6 +2060,34 @@ int32_t apiSpriteDraw(lua_State *L) { } +int32_t apiSpriteGetFrame(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t result = -1; + int32_t id = -1; + double d; + SpriteT *sprite = NULL; + + if (n == 1) { + if (lua_isstring(L, 1)) { + d = lua_tonumber(L, 1); id = (int32_t)d; + // Get our sprite structure + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spriteGetFrame", "No sprite at index %d in apiSpriteGetFrame.", id); + result = sprite->currentFrame; + } + } + + if (result >= 0) { + luaTrace(L, "spriteGetFrame", "%d", result); + } else { + luaDie(L, "spriteGetFrame", "Failed!"); + } + + lua_pushinteger(L, result); + return 1; +} + + int32_t apiSpriteGetHeight(lua_State *L) { int32_t n = lua_gettop(L); int32_t result = -1; @@ -2072,18 +2144,67 @@ int32_t apiSpriteGetWidth(lua_State *L) { } +int32_t apiSpriteIsPlaying(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t result = -1; + int32_t id = -1; + double d; + SpriteT *sprite = NULL; + + if (n == 1) { + if (lua_isstring(L, 1)) { + d = lua_tonumber(L, 1); id = (int32_t)d; + // Get our sprite structure + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spriteIsPlaying", "No sprite at index %d in apiSpriteIsPlaying.", id); + result = sprite->animating; + } + } + + if (result >= 0) { + luaTrace(L, "spriteIsPlaying", "%d", result); + } else { + luaDie(L, "spriteIsPlaying", "Failed!"); + } + + lua_pushboolean(L, result); + return 1; +} + + int32_t apiSpriteLoad(lua_State *L) { int32_t n = lua_gettop(L); int32_t result = -1; const char *name = NULL; SpriteT *sprite = NULL; + int32_t x = 0; if (n == 1) { if (lua_isstring(L, 1)) { sprite = (SpriteT *)calloc(1, sizeof(SpriteT)); if (!sprite) luaDie(L, "spriteLoad", "Unable to allocate new sprite."); name = lua_tostring(L, 1); - sprite->originalSurface = IMG_Load(name); + // Try to load requested file as an animation first + sprite->animation = IMG_LoadAnimation(name); + if (sprite->animation) { + // We got something + if (sprite->animation->count < 2) { + // Only one frame - meh + IMG_FreeAnimation(sprite->animation); + sprite->animation = NULL; + } else { + // Set up our transparency + for (x=0; xanimation->count; x++) { + SDL_SetColorKey(sprite->animation->frames[x], true, 0); + } + // Load first frame onto surface + sprite->originalSurface = surfaceCopy(sprite->animation->frames[0]); + } + } + if (sprite->animation == NULL) { + // Load as single image + sprite->originalSurface = IMG_Load(name); + } if (!sprite->originalSurface) luaDie(L, "spriteLoad", "%s", IMG_GetError()); SDL_SetColorKey(sprite->originalSurface, true, 0); sprite->surface = surfaceCopy(sprite->originalSurface); @@ -2095,7 +2216,7 @@ int32_t apiSpriteLoad(lua_State *L) { } } - if (sprite->surface) { + if (sprite->originalSurface) { luaTrace(L, "spriteLoad", "%d %s", result, name); } else { luaDie(L, "spriteLoad", "Failed!"); @@ -2106,6 +2227,89 @@ int32_t apiSpriteLoad(lua_State *L) { } +int32_t apiSpriteLoop(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t id = -1; + int32_t l; + double d; + SpriteT *sprite = NULL; + + if (n == 2) { + if (lua_isboolean(L, 1)) { + if (lua_isnumber(L, 2)) { + d = lua_toboolean(L, 1); l = (int32_t)d; + d = lua_tonumber(L, 2); id = (int32_t)d; + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spriteLoop", "No sprite at index %d in apiSpriteLoop.", id); + sprite->loop = l; + } + } + } + + if (id >= 0) { + luaTrace(L, "spriteLoop", "%d %d", id, sprite->loop); + } else { + luaDie(L, "spriteLoop", "Failed!"); + } + + return 0; +} + + +int32_t apiSpritePause(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t id = -1; + double d; + SpriteT *sprite = NULL; + + if (n == 1) { + if (lua_isnumber(L, 1)) { + d = lua_tonumber(L, 1); id = (int32_t)d; + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spritePause", "No sprite at index %d in apiSpritePause.", id); + sprite->animating = false; + } + } + + if (id >= 0) { + luaTrace(L, "spritePause", "%d %d", id, sprite->animating); + } else { + luaDie(L, "spritePause", "Failed!"); + } + + return 0; +} + + +int32_t apiSpritePlay(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t id = -1; + double d; + SpriteT *sprite = NULL; + + if (n == 1) { + if (lua_isnumber(L, 1)) { + d = lua_tonumber(L, 1); id = (int32_t)d; + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spritePlay", "No sprite at index %d in apiSpritePlay.", id); + // Do we need to reset the tick counter? + if (sprite->animating == false) { + sprite->lastTick = SDL_GetTicks(); + } + sprite->animating = true; + } + } + + if (id >= 0) { + luaTrace(L, "spritePlay", "%d %d", id, sprite->animating); + } else { + luaDie(L, "spritePlay", "Failed!"); + } + + return 0; +} + + int32_t apiSpriteQuality(lua_State *L) { int32_t n = lua_gettop(L); int32_t id = -1; @@ -2254,6 +2458,38 @@ int32_t apiSpriteScale(lua_State *L) { } +int32_t apiSpriteSetFrame(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t id = -1; + int32_t f; + double d; + SpriteT *sprite = NULL; + + if (n == 2) { + if (lua_isnumber(L, 1)) { + if (lua_isnumber(L, 2)) { + d = lua_tonumber(L, 1); f = (int32_t)d; + d = lua_tonumber(L, 2); id = (int32_t)d; + HASH_FIND_INT(_global.spriteList, &id, sprite); + if (!sprite) luaDie(L, "spriteSetFrame", "No sprite at index %d in apiSpriteSetFrame.", id); + if ((f >= 0) && (f < sprite->animation->count)) { + sprite->currentFrame = f; + sprite->ticks = 0; + } + } + } + } + + if (id >= 0) { + luaTrace(L, "spriteSetFrame", "%d %d", id, sprite->currentFrame); + } else { + luaDie(L, "spriteSetFrame", "Failed!"); + } + + return 0; +} + + int32_t apiSpriteUnload(lua_State *L) { int32_t n = lua_gettop(L); bool result = false; @@ -2268,8 +2504,9 @@ int32_t apiSpriteUnload(lua_State *L) { HASH_FIND_INT(_global.spriteList, &id, sprite); if (!sprite) luaDie(L, "spriteUnload", "No sprite at index %d in apiSpriteUnload.", id); HASH_DEL(_global.spriteList, sprite); - SDL_FreeSurface(sprite->surface); - SDL_FreeSurface(sprite->originalSurface); + if (sprite->surface) SDL_FreeSurface(sprite->surface); + if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface); + if (sprite->animation) IMG_FreeAnimation(sprite->animation); free(sprite); result = true; } @@ -2296,7 +2533,6 @@ int32_t apiVideoDraw(lua_State *L) { VideoT *video = NULL; bool center = false; SDL_Rect dest; - SDL_Surface *surface = NULL; // videoDraw(id, x1, y1, x2, y2) - Simple/Stretched draw // videoDraw(id, x1, y1, c) - Scaled/Rotated draw @@ -2340,17 +2576,23 @@ int32_t apiVideoDraw(lua_State *L) { // Render frame into overlay if ((dest.w == 0) && (dest.h == 0)) { - surface = rotozoomSurfaceXY(video->surface, 360 - video->angle, video->scaleX, video->scaleY, video->smooth); - dest.w = surface->w; - dest.h = surface->h; + if (frame != video->lastFrame) { + if (video->rotatedZoomedSurface != NULL) { + SDL_FreeSurface(video->rotatedZoomedSurface); + video->rotatedZoomedSurface = NULL; + } + video->rotatedZoomedSurface = rotozoomSurfaceXY(video->surface, 360 - video->angle, video->scaleX, video->scaleY, video->smooth); + SDL_SetColorKey(video->rotatedZoomedSurface, true, 0); + } + dest.w = video->rotatedZoomedSurface->w; + dest.h = video->rotatedZoomedSurface->h; // Scaled/Rotated draw if (center) { // Move video so the drawing coordinate is the center of the video dest.x -= dest.w * 0.5; dest.y -= dest.h * 0.5; } - SDL_BlitSurface(surface, NULL, _global.overlay, &dest); - SDL_FreeSurface(surface); + SDL_BlitSurface(video->rotatedZoomedSurface, NULL, _global.overlay, &dest); } else { // Simple/Stretched draw if (SDL_BlitScaled(video->surface, NULL, _global.overlay, &dest) != 0) luaDie(L, "videoDraw", "%s", SDL_GetError()); @@ -2646,8 +2888,10 @@ int32_t apiVideoLoad(lua_State *L) { HASH_ADD_INT(_global.videoList, id, video); // Select desired default audio track if (_global.conf->audioOutputTrack < videoGetAudioTracks(video->id)) { - videoSetAudioTrack(video->id, _global.conf->audioOutputTrack); + videoSetAudioTrack(video->handle, _global.conf->audioOutputTrack); } + // Set default volume + videoSetVolume(video->handle, _global.conf->volumeNonVldp, _global.conf->volumeNonVldp); } } @@ -2983,6 +3227,7 @@ int32_t apiVideoUnload(lua_State *L) { HASH_DEL(_global.videoList, video); videoUnload(video->handle); if (video->surface) SDL_FreeSurface(video->surface); + if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface); free(video); result = true; } @@ -4388,13 +4633,19 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { lua_register(_global.luaContext, "soundUnload", apiSoundUnload); lua_register(_global.luaContext, "spriteDraw", apiSpriteDraw); + lua_register(_global.luaContext, "spriteGetFrame", apiSpriteGetFrame); lua_register(_global.luaContext, "spriteGetHeight", apiSpriteGetHeight); lua_register(_global.luaContext, "spriteGetWidth", apiSpriteGetWidth); + lua_register(_global.luaContext, "spriteIsPlaying", apiSpriteIsPlaying); lua_register(_global.luaContext, "spriteLoad", apiSpriteLoad); + lua_register(_global.luaContext, "spriteLoop", apiSpriteLoop); + lua_register(_global.luaContext, "spritePause", apiSpritePause); + lua_register(_global.luaContext, "spritePlay", apiSpritePlay); lua_register(_global.luaContext, "spriteQuality", apiSpriteQuality); lua_register(_global.luaContext, "spriteRotate", apiSpriteRotate); lua_register(_global.luaContext, "spriteRotateAndScale", apiSpriteRotateAndScale); lua_register(_global.luaContext, "spriteScale", apiSpriteScale); + lua_register(_global.luaContext, "spriteSetFrame", apiSpriteSetFrame); lua_register(_global.luaContext, "spriteUnload", apiSpriteUnload); lua_register(_global.luaContext, "videoDraw", apiVideoDraw); @@ -4976,8 +5227,9 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { HASH_DEL(_global.spriteList, sprite); #pragma GCC diagnostic pop progTrace("Unloading sprite handle %d", sprite->id); - SDL_FreeSurface(sprite->surface); - SDL_FreeSurface(sprite->originalSurface); + if (sprite->surface) SDL_FreeSurface(sprite->surface); + if (sprite->originalSurface) SDL_FreeSurface(sprite->originalSurface); + if (sprite->animation) IMG_FreeAnimation(sprite->animation); free(sprite); } @@ -4986,6 +5238,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { HASH_DEL(_global.videoList, video); progTrace("Unloading video handle %d", video->id); if (video->surface) SDL_FreeSurface(video->surface); + if (video->rotatedZoomedSurface) SDL_FreeSurface(video->rotatedZoomedSurface); free(video); }