diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Animations.c |
initial commit
Diffstat (limited to 'src/Animations.c')
-rw-r--r-- | src/Animations.c | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/Animations.c b/src/Animations.c new file mode 100644 index 0000000..5242c26 --- /dev/null +++ b/src/Animations.c @@ -0,0 +1,409 @@ +#include "TexturePack.h" +#include "String.h" +#include "Constants.h" +#include "Stream.h" +#include "Graphics.h" +#include "Event.h" +#include "Game.h" +#include "Funcs.h" +#include "Errors.h" +#include "Chat.h" +#include "ExtMath.h" +#include "Options.h" +#include "Logger.h" + +#ifdef CC_BUILD_ANIMATIONS +static void Animations_Update(int loc, struct Bitmap* bmp, int stride); + +#ifdef CC_BUILD_LOWMEM + #define LIQUID_ANIM_MAX 16 +#else + #define LIQUID_ANIM_MAX 64 +#endif + +#define WATER_TEX_LOC 14 +#define LAVA_TEX_LOC 30 + +#ifndef CC_BUILD_WEB +/* Based off the incredible work from https://dl.dropboxusercontent.com/u/12694594/lava.txt + mirrored at https://github.com/UnknownShadow200/ClassiCube/wiki/Minecraft-Classic-lava-animation-algorithm + Water animation originally written by cybertoon, big thanks! +*/ +/*########################################################################################################################* +*-----------------------------------------------------Lava animation------------------------------------------------------* +*#########################################################################################################################*/ +static float L_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float L_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float L_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static RNGState L_rnd; +static cc_bool L_rndInited; + +static void LavaAnimation_Tick(void) { + BitmapCol pixels[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; + BitmapCol* ptr = pixels; + float soupHeat, potHeat, color; + int size, mask, shift; + int x, y, i = 0; + struct Bitmap bmp; + + size = min(Atlas2D.TileSize, LIQUID_ANIM_MAX); + mask = size - 1; + shift = Math_ilog2(size); + + if (!L_rndInited) { + Random_SeedFromCurrentTime(&L_rnd); + L_rndInited = true; + } + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + /* Calculate the color at this coordinate in the heatmap */ + + /* Lookup table for (int)(1.2 * sin([ANGLE] * 22.5 * MATH_DEG2RAD)); */ + /* [ANGLE] is integer x/y, so repeats every 16 intervals */ + static cc_int8 sin_adj_table[16] = { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0 }; + int xx = x + sin_adj_table[y & 0xF], yy = y + sin_adj_table[x & 0xF]; + + soupHeat = + L_soupHeat[((yy - 1) & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[((yy - 1) & mask) << shift | (xx & mask)] + + L_soupHeat[((yy - 1) & mask) << shift | ((xx + 1) & mask)] + + + L_soupHeat[(yy & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[(yy & mask) << shift | (xx & mask)] + + L_soupHeat[(yy & mask) << shift | ((xx + 1) & mask)] + + + L_soupHeat[((yy + 1) & mask) << shift | ((xx - 1) & mask)] + + L_soupHeat[((yy + 1) & mask) << shift | (xx & mask)] + + L_soupHeat[((yy + 1) & mask) << shift | ((xx + 1) & mask)]; + + potHeat = + L_potHeat[i] + /* x , y */ + L_potHeat[y << shift | ((x + 1) & mask)] + /* x + 1, y */ + L_potHeat[((y + 1) & mask) << shift | x] + /* x , y + 1 */ + L_potHeat[((y + 1) & mask) << shift | ((x + 1) & mask)];/* x + 1, y + 1 */ + + L_soupHeat[i] = soupHeat * 0.1f + potHeat * 0.2f; + + L_potHeat[i] += L_flameHeat[i]; + if (L_potHeat[i] < 0.0f) L_potHeat[i] = 0.0f; + + L_flameHeat[i] -= 0.06f * 0.01f; + if (Random_Float(&L_rnd) <= 0.005f) L_flameHeat[i] = 1.5f * 0.01f; + + /* Output the pixel */ + color = 2.0f * L_soupHeat[i]; + Math_Clamp(color, 0.0f, 1.0f); + + *ptr = BitmapCol_Make( + color * 100.0f + 155.0f, + color * color * 255.0f, + color * color * color * color * 128.0f, + 255); + + ptr++; i++; + } + } + + Bitmap_Init(bmp, size, size, pixels); + Animations_Update(LAVA_TEX_LOC, &bmp, size); +} + + +/*########################################################################################################################* +*----------------------------------------------------Water animation------------------------------------------------------* +*#########################################################################################################################*/ +static float W_soupHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float W_potHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static float W_flameHeat[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; +static RNGState W_rnd; +static cc_bool W_rndInited; + +static void WaterAnimation_Tick(void) { + BitmapCol pixels[LIQUID_ANIM_MAX * LIQUID_ANIM_MAX]; + BitmapCol* ptr = pixels; + float soupHeat, color; + int size, mask, shift; + int x, y, i = 0; + struct Bitmap bmp; + + size = min(Atlas2D.TileSize, LIQUID_ANIM_MAX); + mask = size - 1; + shift = Math_ilog2(size); + + if (!W_rndInited) { + Random_SeedFromCurrentTime(&W_rnd); + W_rndInited = true; + } + + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + /* Calculate the color at this coordinate in the heatmap */ + soupHeat = + W_soupHeat[y << shift | ((x - 1) & mask)] + + W_soupHeat[y << shift | x ] + + W_soupHeat[y << shift | ((x + 1) & mask)]; + + W_soupHeat[i] = soupHeat / 3.3f + W_potHeat[i] * 0.8f; + + W_potHeat[i] += W_flameHeat[i]; + if (W_potHeat[i] < 0.0f) W_potHeat[i] = 0.0f; + + W_flameHeat[i] -= 0.1f * 0.05f; + if (Random_Float(&W_rnd) <= 0.05f) W_flameHeat[i] = 0.5f * 0.05f; + + /* Output the pixel */ + color = W_soupHeat[i]; + Math_Clamp(color, 0.0f, 1.0f); + color = color * color; + + *ptr = BitmapCol_Make( + 32.0f + color * 32.0f, + 50.0f + color * 64.0f, + 255, + 146.0f + color * 50.0f); + + ptr++; i++; + } + } + + Bitmap_Init(bmp, size, size, pixels); + Animations_Update(WATER_TEX_LOC, &bmp, size); +} +#endif + + +/*########################################################################################################################* +*-------------------------------------------------------Animations--------------------------------------------------------* +*#########################################################################################################################*/ +struct AnimationData { + TextureLoc texLoc; /* Tile (not pixel) coordinates in terrain.png */ + cc_uint16 frameX, frameY; /* Top left pixel coordinates of start frame in animations.png */ + cc_uint16 frameSize; /* Size of each frame in pixel coordinates */ + cc_uint16 state; /* Current animation frame index */ + cc_uint16 statesCount; /* Total number of animation frames */ + cc_uint16 delay; /* Delay in ticks until next frame is drawn */ + cc_uint16 frameDelay; /* Delay between each frame */ +}; + +static struct Bitmap anims_bmp; +static struct AnimationData anims_list[ATLAS1D_MAX_ATLASES]; +static int anims_count; +static cc_bool anims_validated, useLavaAnim, useWaterAnim, alwaysLavaAnim, alwaysWaterAnim; +#define ANIM_MIN_ARGS 7 + +static void Animations_ReadDescription(struct Stream* stream, const cc_string* path) { + cc_string line; char lineBuffer[STRING_SIZE * 2]; + cc_string parts[ANIM_MIN_ARGS]; + int count; + struct AnimationData data = { 0 }; + cc_uint8 tileX, tileY; + + cc_uint8 buffer[2048]; + struct Stream buffered; + cc_result res; + + String_InitArray(line, lineBuffer); + /* ReadLine reads single byte at a time */ + Stream_ReadonlyBuffered(&buffered, stream, buffer, sizeof(buffer)); + + for (;;) { + res = Stream_ReadLine(&buffered, &line); + if (res == ERR_END_OF_STREAM) break; + if (res) { Logger_SysWarn2(res, "reading from", path); break; } + + if (!line.length || line.buffer[0] == '#') continue; + count = String_UNSAFE_Split(&line, ' ', parts, ANIM_MIN_ARGS); + if (count < ANIM_MIN_ARGS) { + Chat_Add1("&cNot enough arguments for anim: %s", &line); continue; + } + + if (!Convert_ParseUInt8(&parts[0], &tileX) || tileX >= ATLAS2D_TILES_PER_ROW) { + Chat_Add1("&cInvalid anim tile X coord: %s", &parts[0]); continue; + } + if (!Convert_ParseUInt8(&parts[1], &tileY) || tileY >= ATLAS2D_MAX_ROWS_COUNT) { + Chat_Add1("&cInvalid anim tile Y coord: %s", &parts[1]); continue; + } + if (!Convert_ParseUInt16(&parts[2], &data.frameX)) { + Chat_Add1("&cInvalid anim frame X coord: %s", &parts[2]); continue; + } + if (!Convert_ParseUInt16(&parts[3], &data.frameY)) { + Chat_Add1("&cInvalid anim frame Y coord: %s", &parts[3]); continue; + } + if (!Convert_ParseUInt16(&parts[4], &data.frameSize) || !data.frameSize) { + Chat_Add1("&cInvalid anim frame size: %s", &parts[4]); continue; + } + if (!Convert_ParseUInt16(&parts[5], &data.statesCount)) { + Chat_Add1("&cInvalid anim states count: %s", &parts[5]); continue; + } + if (!Convert_ParseUInt16(&parts[6], &data.frameDelay)) { + Chat_Add1("&cInvalid anim frame delay: %s", &parts[6]); continue; + } + + if (anims_count == Array_Elems(anims_list)) { + Chat_AddRaw("&cCannot show over 512 animations"); return; + } + + data.texLoc = tileX + (tileY * ATLAS2D_TILES_PER_ROW); + anims_list[anims_count++] = data; + } +} + +static void Animations_Update(int texLoc, struct Bitmap* bmp, int stride) { + int dstX = Atlas1D_Index(texLoc); + int dstY = Atlas1D_RowId(texLoc) * Atlas2D.TileSize; + GfxResourceID tex; + + tex = Atlas1D.TexIds[dstX]; + if (tex) Gfx_UpdateTexture(tex, 0, dstY, bmp, stride, Gfx.Mipmaps); +} + +static void Animations_Apply(struct AnimationData* data) { + struct Bitmap frame; + int loc, size; + if (data->delay) { data->delay--; return; } + + data->state++; + data->state %= data->statesCount; + data->delay = data->frameDelay; + + loc = data->texLoc; +#ifndef CC_BUILD_WEB + if (loc == LAVA_TEX_LOC && useLavaAnim) return; + if (loc == WATER_TEX_LOC && useWaterAnim) return; +#endif + + size = data->frameSize; + Bitmap_Init(frame, size, size, NULL); + + frame.scan0 = anims_bmp.scan0 + + data->frameY * anims_bmp.width + + (data->frameX + data->state * size); + Animations_Update(loc, &frame, anims_bmp.width); +} + +static cc_bool Animations_IsDefaultZip(void) { + cc_string texPack; + cc_bool optExists; + if (TexturePack_Url.length) return false; + + optExists = Options_UNSAFE_Get(OPT_DEFAULT_TEX_PACK, &texPack); + return !optExists || String_CaselessEqualsConst(&texPack, "default.zip"); +} + +static void Animations_Clear(void) { + Mem_Free(anims_bmp.scan0); + anims_count = 0; + anims_bmp.scan0 = NULL; + anims_validated = false; +} + +static void Animations_Validate(void) { + struct AnimationData* data; + int maxX, maxY, tileX, tileY; + int i, j; + + anims_validated = true; + for (i = 0; i < anims_count; i++) { + data = &anims_list[i]; + + maxX = data->frameX + data->frameSize * data->statesCount; + maxY = data->frameY + data->frameSize; + tileX = Atlas2D_TileX(data->texLoc); + tileY = Atlas2D_TileY(data->texLoc); + + if (data->frameSize > Atlas2D.TileSize || tileY >= Atlas2D.RowsCount) { + Chat_Add2("&cAnimation frames for tile (%i, %i) are bigger than the size of a tile in terrain.png", &tileX, &tileY); + } else if (maxX > anims_bmp.width || maxY > anims_bmp.height) { + Chat_Add2("&cSome of the animation frames for tile (%i, %i) are at coordinates outside animations.png", &tileX, &tileY); + } else { + /* if user has water/lava animations in their default.zip, disable built-in */ + /* However, 'usewateranim' and 'uselavaanim' files should always disable use */ + /* of custom water/lava animations, even when they exist in animations.png */ + if (data->texLoc == LAVA_TEX_LOC && !alwaysLavaAnim) useLavaAnim = false; + if (data->texLoc == WATER_TEX_LOC && !alwaysWaterAnim) useWaterAnim = false; + continue; + } + + /* Remove this animation from the list */ + for (j = i; j < anims_count - 1; j++) { + anims_list[j] = anims_list[j + 1]; + } + i--; anims_count--; + } +} + +static void Animations_Tick(struct ScheduledTask* task) { + int i; +#ifndef CC_BUILD_WEB + if (useLavaAnim) LavaAnimation_Tick(); + if (useWaterAnim) WaterAnimation_Tick(); +#endif + + if (!anims_count) return; + if (!anims_bmp.scan0) { + Chat_AddRaw("&cCurrent texture pack specifies it uses animations,"); + Chat_AddRaw("&cbut is missing animations.png"); + anims_count = 0; return; + } + + /* deferred, because when reading animations.txt, might not have read animations.png yet */ + if (!anims_validated) Animations_Validate(); + for (i = 0; i < anims_count; i++) { + Animations_Apply(&anims_list[i]); + } +} + + +/*########################################################################################################################* +*--------------------------------------------------Animations component---------------------------------------------------* +*#########################################################################################################################*/ +static void AnimationsPngProcess(struct Stream* stream, const cc_string* name) { + cc_result res = Png_Decode(&anims_bmp, stream); + if (!res) return; + + Logger_SysWarn2(res, "decoding", name); + Mem_Free(anims_bmp.scan0); + anims_bmp.scan0 = NULL; +} +static struct TextureEntry animations_entry = { "animations.png", AnimationsPngProcess }; +static struct TextureEntry animations_txt = { "animations.txt", Animations_ReadDescription }; + +static void UseWaterProcess(struct Stream* stream, const cc_string* name) { + useWaterAnim = true; + alwaysWaterAnim = true; +} +static struct TextureEntry water_entry = { "usewateranim", UseWaterProcess }; + +static void UseLavaProcess(struct Stream* stream, const cc_string* name) { + useLavaAnim = true; + alwaysLavaAnim = true; +} +static struct TextureEntry lava_entry = { "uselavaanim", UseLavaProcess }; + + +static void OnPackChanged(void* obj) { + Animations_Clear(); + useLavaAnim = Animations_IsDefaultZip(); + useWaterAnim = useLavaAnim; + alwaysLavaAnim = false; + alwaysWaterAnim = false; +} +static void OnInit(void) { + TextureEntry_Register(&animations_entry); + TextureEntry_Register(&animations_txt); + TextureEntry_Register(&water_entry); + TextureEntry_Register(&lava_entry); + + ScheduledTask_Add(GAME_DEF_TICKS, Animations_Tick); + Event_Register_(&TextureEvents.PackChanged, NULL, OnPackChanged); +} +#else +static void Animations_Clear(void) { } +static void OnInit(void) { } +#endif + +struct IGameComponent Animations_Component = { + OnInit, /* Init */ + Animations_Clear /* Free */ +}; |