Reorganise texture pack/terrain atlas/animations files

This commit is contained in:
UnknownShadow200 2018-11-30 17:02:45 +11:00
parent b888213adc
commit 3ae356beba
22 changed files with 712 additions and 770 deletions

View File

@ -1,376 +0,0 @@
#include "Animations.h"
#include "ExtMath.h"
#include "TerrainAtlas.h"
#include "Platform.h"
#include "Event.h"
#include "Funcs.h"
#include "Graphics.h"
#include "Chat.h"
#include "World.h"
#include "Options.h"
#include "ErrorHandler.h"
#include "Errors.h"
#include "Stream.h"
#include "Bitmap.h"
#define LIQUID_ANIM_MAX 64
/*########################################################################################################################*
*-----------------------------------------------------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 bool L_rndInitalised;
static void LavaAnimation_Tick(BitmapCol* ptr, int size) {
int mask = size - 1, shift = Math_Log2(size);
float soupHeat, potHeat, col;
int x, y, i = 0;
if (!L_rndInitalised) {
Random_InitFromCurrentTime(&L_rnd);
L_rndInitalised = true;
}
for (y = 0; y < size; y++) {
for (x = 0; x < size; x++) {
/* Calculate the colour 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 int8_t 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 */
col = 2.0f * L_soupHeat[i];
Math_Clamp(col, 0.0f, 1.0f);
ptr->R = (uint8_t)(col * 100.0f + 155.0f);
ptr->G = (uint8_t)(col * col * 255.0f);
ptr->B = (uint8_t)(col * col * col * col * 128.0f);
ptr->A = 255;
ptr++; i++;
}
}
}
/*########################################################################################################################*
*----------------------------------------------------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 bool W_rndInitalised;
static void WaterAnimation_Tick(BitmapCol* ptr, int size) {
int mask = size - 1, shift = Math_Log2(size);
float soupHeat, col;
int x, y, i = 0;
if (!W_rndInitalised) {
Random_InitFromCurrentTime(&W_rnd);
W_rndInitalised = true;
}
for (y = 0; y < size; y++) {
for (x = 0; x < size; x++) {
/* Calculate the colour 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 */
col = W_soupHeat[i];
Math_Clamp(col, 0.0f, 1.0f);
col = col * col;
ptr->R = (uint8_t)(32.0f + col * 32.0f);
ptr->G = (uint8_t)(50.0f + col * 64.0f);
ptr->A = (uint8_t)(146.0f + col * 50.0f);
ptr->B = 255;
ptr++; i++;
}
}
}
/*########################################################################################################################*
*-------------------------------------------------------Animations--------------------------------------------------------*
*#########################################################################################################################*/
struct AnimationData {
TextureLoc TexLoc; /* Tile (not pixel) coordinates in terrain.png */
uint16_t FrameX, FrameY; /* Top left pixel coordinates of start frame in animatons.png */
uint16_t FrameSize; /* Size of each frame in pixel coordinates */
uint16_t State; /* Current animation frame index */
uint16_t StatesCount; /* Total number of animation frames */
int16_t Tick, TickDelay;
};
static Bitmap anims_bmp;
static struct AnimationData anims_list[ATLAS1D_MAX_ATLASES];
static int anims_count;
static bool anims_validated, anims_useLavaAnim, anims_useWaterAnim;
#define ANIM_MIN_ARGS 7
static void Animations_ReadDescription(struct Stream* stream, const String* path) {
String line; char lineBuffer[STRING_SIZE * 2];
String parts[ANIM_MIN_ARGS];
int count;
struct AnimationData data = { 0 };
uint8_t tileX, tileY;
uint8_t buffer[2048];
struct Stream buffered;
ReturnCode 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) { Chat_LogError2(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_TryParseUInt8(&parts[0], &tileX) || tileX >= ATLAS2D_TILES_PER_ROW) {
Chat_Add1("&cInvalid anim tile X coord: %s", &line); continue;
}
if (!Convert_TryParseUInt8(&parts[1], &tileY) || tileY >= ATLAS2D_MAX_ROWS_COUNT) {
Chat_Add1("&cInvalid anim tile Y coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[2], &data.FrameX)) {
Chat_Add1("&cInvalid anim frame X coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[3], &data.FrameY)) {
Chat_Add1("&cInvalid anim frame Y coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[4], &data.FrameSize)) {
Chat_Add1("&cInvalid anim frame size: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[5], &data.StatesCount)) {
Chat_Add1("&cInvalid anim states count: %s", &line); continue;
}
if (!Convert_TryParseInt16(&parts[6], &data.TickDelay)) {
Chat_Add1("&cInvalid anim tick delay: %s", &line); 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;
}
}
#define ANIMS_FAST_SIZE 64
static void Animations_Draw(struct AnimationData* data, TextureLoc texLoc, int size) {
int dstX = Atlas1D_Index(texLoc), srcX;
int dstY = Atlas1D_RowId(texLoc) * Atlas_TileSize;
GfxResourceID tex;
uint8_t buffer[Bitmap_DataSize(ANIMS_FAST_SIZE, ANIMS_FAST_SIZE)];
uint8_t* ptr = buffer;
Bitmap frame;
/* cannot allocate memory on the stack for very big animation.png frames */
if (size > ANIMS_FAST_SIZE) {
ptr = Mem_Alloc(size * size, 4, "anim frame");
}
Bitmap_Create(&frame, size, size, ptr);
if (!data) {
if (texLoc == 30) {
LavaAnimation_Tick((BitmapCol*)frame.Scan0, size);
} else if (texLoc == 14) {
WaterAnimation_Tick((BitmapCol*)frame.Scan0, size);
}
} else {
srcX = data->FrameX + data->State * size;
Bitmap_CopyBlock(srcX, data->FrameY, 0, 0, &anims_bmp, &frame, size);
}
tex = Atlas1D_TexIds[dstX];
if (tex) { Gfx_UpdateTexturePart(tex, 0, dstY, &frame, Gfx_Mipmaps); }
if (size > ANIMS_FAST_SIZE) Mem_Free(ptr);
}
static void Animations_Apply(struct AnimationData* data) {
TextureLoc loc;
data->Tick--;
if (data->Tick >= 0) return;
data->State++;
data->State %= data->StatesCount;
data->Tick = data->TickDelay;
loc = data->TexLoc;
if (loc == 30 && anims_useLavaAnim) return;
if (loc == 14 && anims_useWaterAnim) return;
Animations_Draw(data, loc, data->FrameSize);
}
static bool Animations_IsDefaultZip(void) {
String texPack; char texPackBuffer[STRING_SIZE];
if (World_TextureUrl.length) return false;
String_InitArray(texPack, texPackBuffer);
Options_Get(OPT_DEFAULT_TEX_PACK, &texPack, "default.zip");
return 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 > Atlas_TileSize || tileY >= Atlas_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 {
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, size;
if (anims_useLavaAnim) {
size = min(Atlas_TileSize, 64);
Animations_Draw(NULL, 30, size);
}
if (anims_useWaterAnim) {
size = min(Atlas_TileSize, 64);
Animations_Draw(NULL, 14, size);
}
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]);
}
}
static void Animations_PackChanged(void* obj) {
Animations_Clear();
anims_useLavaAnim = Animations_IsDefaultZip();
anims_useWaterAnim = anims_useLavaAnim;
}
static void Animations_FileChanged(void* obj, struct Stream* stream, const String* name) {
ReturnCode res;
if (String_CaselessEqualsConst(name, "animations.png")) {
res = Png_Decode(&anims_bmp, stream);
if (!res) return;
Chat_LogError2(res, "decoding", name);
Mem_Free(anims_bmp.Scan0);
anims_bmp.Scan0 = NULL;
} else if (String_CaselessEqualsConst(name, "animations.txt")) {
Animations_ReadDescription(stream, name);
} else if (String_CaselessEqualsConst(name, "uselavaanim")) {
anims_useLavaAnim = true;
} else if (String_CaselessEqualsConst(name, "usewateranim")) {
anims_useWaterAnim = true;
}
}
/*########################################################################################################################*
*--------------------------------------------------Animations component---------------------------------------------------*
*#########################################################################################################################*/
static void Animations_Init(void) {
ScheduledTask_Add(GAME_DEF_TICKS, Animations_Tick);
Event_RegisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged);
Event_RegisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged);
}
static void Animations_Free(void) {
Animations_Clear();
Event_UnregisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged);
Event_UnregisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged);
}
struct IGameComponent Animations_Component = {
Animations_Init, /* Init */
Animations_Free /* Free */
};

View File

@ -1,14 +0,0 @@
#ifndef CC_ANIMS_H
#define CC_ANIMS_H
#include "Core.h"
/* Texture animations, and water and lava liquid animations.
Copyright 2014 - 2017 ClassicalSharp | Licensed under BSD-3
Based off the incredible work from https://dl.dropboxusercontent.com/u/12694594/lava.txt
mirrored at https://github.com/UnknownShadow200/ClassicalSharp/wiki/Minecraft-Classic-lava-animation-algorithm
Water animation originally written by cybertoon, big thanks!
*/
struct IGameComponent;
struct ScheduledTask;
extern struct IGameComponent Animations_Component;
#endif

View File

@ -1,13 +1,12 @@
#include "Block.h"
#include "Funcs.h"
#include "ExtMath.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Game.h"
#include "Entity.h"
#include "Inventory.h"
#include "Event.h"
#include "Platform.h"
#include "Bitmap.h"
#include "GameStructs.h"
bool Block_IsLiquid[BLOCK_COUNT];

View File

@ -12,7 +12,7 @@
#include "BlockID.h"
#include "Block.h"
#include "PackedCol.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "VertexStructs.h"
int Builder_SidesLevel, Builder_EdgeLevel;

View File

@ -218,7 +218,6 @@
<ClInclude Include="Inventory.h" />
<ClInclude Include="IsometricDrawer.h" />
<ClInclude Include="Lighting.h" />
<ClInclude Include="Animations.h" />
<ClInclude Include="MapGenerator.h" />
<ClInclude Include="MapRenderer.h" />
<ClInclude Include="Options.h" />
@ -231,7 +230,6 @@
<ClInclude Include="ServerConnection.h" />
<ClInclude Include="Stream.h" />
<ClInclude Include="GameStructs.h" />
<ClInclude Include="TerrainAtlas.h" />
<ClInclude Include="TexturePack.h" />
<ClInclude Include="Utils.h" />
<ClInclude Include="PackedCol.h" />
@ -280,7 +278,6 @@
<ClCompile Include="IsometricDrawer.c" />
<ClCompile Include="Input.c" />
<ClCompile Include="Lighting.c" />
<ClCompile Include="Animations.c" />
<ClCompile Include="Entity.c" />
<ClCompile Include="MapRenderer.c" />
<ClCompile Include="Options.c" />
@ -296,7 +293,6 @@
<ClCompile Include="ServerConnection.c" />
<ClCompile Include="Stream.c" />
<ClCompile Include="String.c" />
<ClCompile Include="TerrainAtlas.c" />
<ClCompile Include="TexturePack.c" />
<ClCompile Include="Utils.c" />
<ClCompile Include="Vectors.c" />

View File

@ -195,9 +195,6 @@
<ClInclude Include="Builder.h">
<Filter>Header Files\MeshBuilder</Filter>
</ClInclude>
<ClInclude Include="TerrainAtlas.h">
<Filter>Header Files\TexturePack</Filter>
</ClInclude>
<ClInclude Include="Picking.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
@ -216,9 +213,6 @@
<ClInclude Include="Particle.h">
<Filter>Header Files\Entities</Filter>
</ClInclude>
<ClInclude Include="Animations.h">
<Filter>Header Files\TexturePack</Filter>
</ClInclude>
<ClInclude Include="Widgets.h">
<Filter>Header Files\2D</Filter>
</ClInclude>
@ -353,9 +347,6 @@
<ClCompile Include="Program.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TerrainAtlas.c">
<Filter>Source Files\TexturePack</Filter>
</ClCompile>
<ClCompile Include="Entity.c">
<Filter>Source Files\Entities</Filter>
</ClCompile>
@ -386,9 +377,6 @@
<ClCompile Include="Particle.c">
<Filter>Source Files\Entities</Filter>
</ClCompile>
<ClCompile Include="Animations.c">
<Filter>Source Files\TexturePack</Filter>
</ClCompile>
<ClCompile Include="Widgets.c">
<Filter>Source Files\2D</Filter>
</ClCompile>

View File

@ -1056,3 +1056,156 @@ void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stre
stream->Write = ZLib_StreamWriteFirst;
stream->Close = ZLib_StreamClose;
}
/*########################################################################################################################*
*--------------------------------------------------------ZipEntry---------------------------------------------------------*
*#########################################################################################################################*/
#define ZIP_MAXNAMELEN 512
static ReturnCode Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) {
struct Stream* stream = state->Input;
uint8_t header[26];
uint32_t compressedSize, uncompressedSize;
int method, pathLen, extraLen;
String path; char pathBuffer[ZIP_MAXNAMELEN];
struct Stream portion, compStream;
struct InflateState inflate;
ReturnCode res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
method = Stream_GetU16_LE(&header[4]);
compressedSize = Stream_GetU32_LE(&header[14]);
uncompressedSize = Stream_GetU32_LE(&header[18]);
/* Some .zip files don't set these in local file header */
if (!compressedSize) compressedSize = entry->CompressedSize;
if (!uncompressedSize) uncompressedSize = entry->UncompressedSize;
pathLen = Stream_GetU16_LE(&header[22]);
extraLen = Stream_GetU16_LE(&header[24]);
if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN;
path = String_Init(pathBuffer, pathLen, pathLen);
if ((res = Stream_Read(stream, pathBuffer, pathLen))) return res;
if (!state->SelectEntry(&path)) return 0;
/* local file may have extra data before actual data (e.g. ZIP64) */
if ((res = stream->Skip(stream, extraLen))) return res;
if (method == 0) {
Stream_ReadonlyPortion(&portion, stream, uncompressedSize);
state->ProcessEntry(&path, &portion, entry);
} else if (method == 8) {
Stream_ReadonlyPortion(&portion, stream, compressedSize);
Inflate_MakeStream(&compStream, &inflate, &portion);
state->ProcessEntry(&path, &compStream, entry);
} else {
Platform_Log1("Unsupported.zip entry compression method: %i", &method);
}
return 0;
}
static ReturnCode Zip_ReadCentralDirectory(struct ZipState* state, struct ZipEntry* entry) {
struct Stream* stream = state->Input;
uint8_t header[42];
int pathLen, extraLen, commentLen;
ReturnCode res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
entry->Crc32 = Stream_GetU32_LE(&header[12]);
entry->CompressedSize = Stream_GetU32_LE(&header[16]);
entry->UncompressedSize = Stream_GetU32_LE(&header[20]);
pathLen = Stream_GetU16_LE(&header[24]);
extraLen = Stream_GetU16_LE(&header[26]);
commentLen = Stream_GetU16_LE(&header[28]);
entry->LocalHeaderOffset = Stream_GetU32_LE(&header[38]);
/* skip data following central directory entry header */
return stream->Skip(stream, pathLen + extraLen + commentLen);
}
static ReturnCode Zip_ReadEndOfCentralDirectory(struct ZipState* state, uint32_t* centralDirectoryOffset) {
struct Stream* stream = state->Input;
uint8_t header[18];
ReturnCode res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
state->EntriesCount = Stream_GetU16_LE(&header[6]);
*centralDirectoryOffset = Stream_GetU32_LE(&header[12]);
return 0;
}
enum ZipSig {
ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50,
ZIP_SIG_CENTRALDIR = 0x02014b50,
ZIP_SIG_LOCALFILEHEADER = 0x04034b50
};
static void Zip_DefaultProcessor(const String* path, struct Stream* data, struct ZipEntry* entry) { }
static bool Zip_DefaultSelector(const String* path) { return true; }
void Zip_Init(struct ZipState* state, struct Stream* input) {
state->Input = input;
state->EntriesCount = 0;
state->ProcessEntry = Zip_DefaultProcessor;
state->SelectEntry = Zip_DefaultSelector;
}
ReturnCode Zip_Extract(struct ZipState* state) {
struct Stream* stream = state->Input;
uint32_t stream_len, centralDirOffset;
uint32_t sig = 0;
int i, count;
ReturnCode res;
state->EntriesCount = 0;
if ((res = stream->Length(stream, &stream_len))) return res;
/* At -22 for nearly all zips, but try a bit further back in case of comment */
count = min(257, stream_len);
for (i = 22; i < count; i++) {
res = stream->Seek(stream, stream_len - i);
if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig == ZIP_SIG_ENDOFCENTRALDIR) break;
}
if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR;
res = Zip_ReadEndOfCentralDirectory(state, &centralDirOffset);
if (res) return res;
res = stream->Seek(stream, centralDirOffset);
if (res) return ZIP_ERR_SEEK_CENTRAL_DIR;
if (state->EntriesCount > ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES;
/* Read all the central directory entries */
for (count = 0; count < state->EntriesCount; count++) {
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig == ZIP_SIG_CENTRALDIR) {
res = Zip_ReadCentralDirectory(state, &state->Entries[count]);
if (res) return res;
} else if (sig == ZIP_SIG_ENDOFCENTRALDIR) {
break;
} else {
return ZIP_ERR_INVALID_CENTRAL_DIR;
}
}
/* Now read the local file header entries */
for (i = 0; i < count; i++) {
struct ZipEntry* entry = &state->Entries[i];
res = stream->Seek(stream, entry->LocalHeaderOffset);
if (res) return ZIP_ERR_SEEK_LOCAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR;
res = Zip_ReadLocalFileHeader(state, entry);
if (res) return res;
}
return 0;
}

View File

@ -1,6 +1,6 @@
#ifndef CC_DEFLATE_H
#define CC_DEFLATE_H
#include "Core.h"
#include "String.h"
/* Decodes data compressed using DEFLATE in a streaming manner.
Partially based off information from
https://handmade.network/forums/wip/t/2363-implementing_a_basic_png_reader_the_handmade_way
@ -65,11 +65,14 @@ struct InflateState {
uint8_t Window[INFLATE_WINDOW_SIZE]; /* Holds circular buffer of recent output data, used for LZ77 */
};
void Inflate_Init(struct InflateState* state, struct Stream* source);
/* Initialises DEFLATE decompressor state to defaults. */
CC_EXPORT void Inflate_Init(struct InflateState* state, struct Stream* source);
/* Attempts to decompress all currently pending data. */
/* NOTE: This is a low level call - usually you should use Inflate_MakeStream. */
void Inflate_Process(struct InflateState* state);
/* Deompresses input data read from another stream using DEFLATE. Read only stream. */
/* NOTE: This only uncompresses pure DEFLATE compressed data. */
/* If the data starts with a GZIP or ZLIB header, use GZipHeader_Read or ZLibHeader_Read to skip it. */
/* If data starts with a GZIP or ZLIB header, use GZipHeader_Read or ZLibHeader_Read to first skip it. */
CC_EXPORT void Inflate_MakeStream(struct Stream* stream, struct InflateState* state, struct Stream* underlying);
@ -100,8 +103,35 @@ struct GZipState { struct DeflateState Base; uint32_t Crc32, Size; };
/* Compresses input data using GZIP, then writes compressed output to another stream. Write only stream. */
/* GZIP compression is GZIP header, followed by DEFLATE compressed data, followed by GZIP footer. */
CC_EXPORT void GZip_MakeStream(struct Stream* stream, struct GZipState* state, struct Stream* underlying);
struct ZLibState { struct DeflateState Base; uint32_t Adler32; };
/* Compresses input data using ZLIB, then writes compressed output to another stream. Write only stream. */
/* ZLIB compression is ZLIB header, followed by DEFLATE compressed data, followed by ZLIB footer. */
CC_EXPORT void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stream* underlying);
/* Minimal data needed to describe an entry in a .zip archive. */
struct ZipEntry { uint32_t CompressedSize, UncompressedSize, LocalHeaderOffset, Crc32; };
#define ZIP_MAX_ENTRIES 2048
/* Stores state for reading and processing entries in a .zip archive. */
struct ZipState {
/* Source of the .zip archive data. Must be seekable. */
struct Stream* Input;
/* Callback function to process the data in a .zip archive entry. */
/* NOTE: data stream may not be seekable. (entry data might be compressed) */
void (*ProcessEntry)(const String* path, struct Stream* data, struct ZipEntry* entry);
/* Predicate used to select which entries in a .zip archive get proessed. */
/* NOTE: returning false entirely skips the entry. (avoids pointless seek to entry) */
bool (*SelectEntry)(const String* path);
/* Number of entries in the .zip archive. */
int EntriesCount;
/* Data for each entry in the .zip archive. */
struct ZipEntry Entries[ZIP_MAX_ENTRIES];
};
/* Initialises .zip archive reader state to defaults. */
CC_EXPORT void Zip_Init(struct ZipState* state, struct Stream* input);
/* Reads and processes the entries in a .zip archive. */
/* NOTE: Must have been initialised with Zip_Init first. */
CC_EXPORT ReturnCode Zip_Extract(struct ZipState* state);
#endif

View File

@ -1,5 +1,5 @@
#include "Drawer.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Constants.h"
bool Drawer_Tinted;

View File

@ -13,7 +13,7 @@
#include "Stream.h"
#include "Block.h"
#include "Event.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Platform.h"
#include "Camera.h"
#include "Particle.h"

View File

@ -19,7 +19,6 @@
#include "Model.h"
#include "Particle.h"
#include "AsyncDownloader.h"
#include "Animations.h"
#include "Inventory.h"
#include "InputHandler.h"
#include "ServerConnection.h"
@ -33,7 +32,6 @@
#include "Menus.h"
#include "Audio.h"
#include "Stream.h"
#include "TerrainAtlas.h"
int Game_Width, Game_Height;
double Game_Accumulator;

View File

@ -4,7 +4,7 @@
#include "PackedCol.h"
#include "ExtMath.h"
#include "Block.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Block.h"
static float iso_scale;

View File

@ -10,7 +10,7 @@
#include "Game.h"
#include "Graphics.h"
#include "Platform.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Utils.h"
#include "World.h"

View File

@ -7,7 +7,6 @@
#include "Drawer2D.h"
#include "Graphics.h"
#include "Funcs.h"
#include "TerrainAtlas.h"
#include "Model.h"
#include "MapGenerator.h"
#include "ServerConnection.h"

View File

@ -7,7 +7,7 @@
#include "Camera.h"
#include "Event.h"
#include "ExtMath.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Drawer.h"
#include "Block.h"
#include "Stream.h"

View File

@ -23,7 +23,6 @@
#include "TexturePack.h"
#include "Gui.h"
#include "Errors.h"
#include "TerrainAtlas.h"
/* Classic state */
static uint8_t classic_tabList[ENTITIES_MAX_COUNT >> 3];

View File

@ -4,7 +4,7 @@
#include "ExtMath.h"
#include "Lighting.h"
#include "Entity.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Graphics.h"
#include "Funcs.h"
#include "Game.h"

View File

@ -7,7 +7,7 @@
#include "Drawer2D.h"
#include "Graphics.h"
#include "Funcs.h"
#include "TerrainAtlas.h"
#include "TexturePack.h"
#include "Model.h"
#include "MapGenerator.h"
#include "ServerConnection.h"

View File

@ -1,113 +0,0 @@
#include "TerrainAtlas.h"
#include "Block.h"
#include "ExtMath.h"
#include "Funcs.h"
#include "Graphics.h"
#include "Platform.h"
Bitmap Atlas_Bitmap;
int Atlas_TileSize, Atlas_RowsCount;
int Atlas1D_Count, Atlas1D_TilesPerAtlas;
int Atlas1D_Mask, Atlas1D_Shift;
float Atlas1D_InvTileSize;
GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES];
TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index) {
TextureRec rec;
int y = Atlas1D_RowId(texLoc);
*index = Atlas1D_Index(texLoc);
/* Adjust coords to be slightly inside - fixes issues with AMD/ATI cards */
rec.U1 = 0.0f;
rec.V1 = y * Atlas1D_InvTileSize;
rec.U2 = (uCount - 1) + UV2_Scale;
rec.V2 = rec.V1 + UV2_Scale * Atlas1D_InvTileSize;
return rec;
}
static void Atlas_Convert2DTo1D(void) {
int tileSize = Atlas_TileSize;
int tilesPerAtlas = Atlas1D_TilesPerAtlas;
int atlasesCount = Atlas1D_Count;
Bitmap atlas1D;
int atlasX, atlasY;
int tile = 0, i, y;
Platform_Log2("Loaded new atlas: %i bmps, %i per bmp", &atlasesCount, &tilesPerAtlas);
Bitmap_Allocate(&atlas1D, tileSize, tilesPerAtlas * tileSize);
for (i = 0; i < atlasesCount; i++) {
for (y = 0; y < tilesPerAtlas; y++, tile++) {
atlasX = Atlas2D_TileX(tile) * tileSize;
atlasY = Atlas2D_TileY(tile) * tileSize;
Bitmap_CopyBlock(atlasX, atlasY, 0, y * tileSize,
&Atlas_Bitmap, &atlas1D, tileSize);
}
Atlas1D_TexIds[i] = Gfx_CreateTexture(&atlas1D, true, Gfx_Mipmaps);
}
Mem_Free(atlas1D.Scan0);
}
static void Atlas_Update1D(void) {
int maxAtlasHeight, maxTilesPerAtlas, maxTiles;
maxAtlasHeight = min(4096, Gfx_MaxTexHeight);
maxTilesPerAtlas = maxAtlasHeight / Atlas_TileSize;
maxTiles = Atlas_RowsCount * ATLAS2D_TILES_PER_ROW;
Atlas1D_TilesPerAtlas = min(maxTilesPerAtlas, maxTiles);
Atlas1D_Count = Math_CeilDiv(maxTiles, Atlas1D_TilesPerAtlas);
Atlas1D_InvTileSize = 1.0f / Atlas1D_TilesPerAtlas;
Atlas1D_Mask = Atlas1D_TilesPerAtlas - 1;
Atlas1D_Shift = Math_Log2(Atlas1D_TilesPerAtlas);
}
void Atlas_Update(Bitmap* bmp) {
Atlas_Bitmap = *bmp;
Atlas_TileSize = bmp->Width / ATLAS2D_TILES_PER_ROW;
Atlas_RowsCount = bmp->Height / Atlas_TileSize;
Atlas_RowsCount = min(Atlas_RowsCount, ATLAS2D_MAX_ROWS_COUNT);
Block_RecalculateAllSpriteBB();
Atlas_Update1D();
Atlas_Convert2DTo1D();
}
static GfxResourceID Atlas_LoadTile_Raw(TextureLoc texLoc, Bitmap* element) {
int size = Atlas_TileSize;
int x = Atlas2D_TileX(texLoc), y = Atlas2D_TileY(texLoc);
if (y >= Atlas_RowsCount) return GFX_NULL;
Bitmap_CopyBlock(x * size, y * size, 0, 0, &Atlas_Bitmap, element, size);
return Gfx_CreateTexture(element, false, Gfx_Mipmaps);
}
GfxResourceID Atlas_LoadTile(TextureLoc texLoc) {
int tileSize = Atlas_TileSize;
Bitmap tile;
GfxResourceID texId;
uint8_t scan0[Bitmap_DataSize(64, 64)];
/* Try to allocate bitmap on stack if possible */
if (tileSize > 64) {
Bitmap_Allocate(&tile, tileSize, tileSize);
texId = Atlas_LoadTile_Raw(texLoc, &tile);
Mem_Free(tile.Scan0);
return texId;
} else {
Bitmap_Create(&tile, tileSize, tileSize, scan0);
return Atlas_LoadTile_Raw(texLoc, &tile);
}
}
void Atlas_Free(void) {
int i;
Mem_Free(Atlas_Bitmap.Scan0);
Atlas_Bitmap.Scan0 = NULL;
for (i = 0; i < Atlas1D_Count; i++) {
Gfx_DeleteTexture(&Atlas1D_TexIds[i]);
}
}

View File

@ -1,58 +0,0 @@
#ifndef CC_TERRAINATLAS_H
#define CC_TERRAINATLAS_H
#include "Bitmap.h"
/* Represents the 2D texture atlas of terrain.png, and converted into an array of 1D textures.
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
*/
/* Number of tiles in each row */
#define ATLAS2D_TILES_PER_ROW 16
#define ATLAS2D_MASK 15
#define ATLAS2D_SHIFT 4
/* Maximum supported number of rows in the atlas. */
#ifdef EXTENDED_TEXTURES
#define ATLAS2D_MAX_ROWS_COUNT 32
#else
#define ATLAS2D_MAX_ROWS_COUNT 16
#endif
/* Maximum possible number of 1D terrain atlases. (worst case, each 1D atlas only has 1 tile) */
#define ATLAS1D_MAX_ATLASES (ATLAS2D_TILES_PER_ROW * ATLAS2D_MAX_ROWS_COUNT)
/* Bitmap that contains the textures of all tiles. */
/* Tiles are indexed left to right, top to bottom. */
extern Bitmap Atlas_Bitmap;
/* Size of each tile in pixels. (default 16x16) */
extern int Atlas_TileSize;
/* Number of rows in the atlas. (default 16, can be 32) */
extern int Atlas_RowsCount;
/* Number of 1D atlases the atlas was split into. */
extern int Atlas1D_Count;
/* Number of tiles in each 1D atlas. */
extern int Atlas1D_TilesPerAtlas;
/* Converts a tile id into 1D atlas index, and index within that atlas. */
extern int Atlas1D_Mask, Atlas1D_Shift;
/* Texture V coord that equals the size of one tile. (i.e. 1/Atlas1D_TilesPerAtlas) */
/* NOTE: The texture U coord that equals the size of one tile is 1. */
extern float Atlas1D_InvTileSize;
/* Textures for each 1D atlas. Only Atlas1D_Count of these are valid. */
extern GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES];
#define Atlas2D_TileX(texLoc) ((texLoc) & ATLAS2D_MASK) /* texLoc % ATLAS2D_TILES_PER_ROW */
#define Atlas2D_TileY(texLoc) ((texLoc) >> ATLAS2D_SHIFT) /* texLoc / ATLAS2D_TILES_PER_ROW */
/* Returns the index of the given tile id within a 1D atlas */
#define Atlas1D_RowId(texLoc) ((texLoc) & Atlas1D_Mask) /* texLoc % Atlas1D_TilesPerAtlas */
/* Returns the index of the 1D atlas within the array of 1D atlases that contains the given tile id */
#define Atlas1D_Index(texLoc) ((texLoc) >> Atlas1D_Shift) /* texLoc / Atlas1D_TilesPerAtlas */
/* Loads the given atlas and converts it into an array of 1D atlases. */
/* NOTE: Use Game_ChangeTerrainAtlas to change atlas, because that raises TextureEvents_AtlasChanged */
void Atlas_Update(Bitmap* bmp);
/* Loads the given tile into a new separate texture. */
GfxResourceID Atlas_LoadTile(TextureLoc texLoc);
/* Frees the atlas and 1D atlas textures. */
void Atlas_Free(void);
/* Returns the UV rectangle of the given tile id in the 1D atlases. */
/* That is, returns U1/U2/V1/V2 coords that make up the tile in a 1D atlas. */
/* index is set to the index of the 1D atlas that the tile is in. */
TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index);
#endif

View File

@ -1,189 +1,499 @@
#include "TexturePack.h"
#include "Constants.h"
#include "Platform.h"
#include "ErrorHandler.h"
#include "Stream.h"
#include "Bitmap.h"
#include "World.h"
#include "Graphics.h"
#include "Event.h"
#include "Game.h"
#include "AsyncDownloader.h"
#include "ErrorHandler.h"
#include "Platform.h"
#include "Deflate.h"
#include "Stream.h"
#include "Funcs.h"
#include "Errors.h"
#include "Chat.h"
#include "ExtMath.h"
#include "Block.h" /* NOTE: Just for block_recalculateallspritebb */
#include "Chat.h"
#include "Options.h"
#define LIQUID_ANIM_MAX 64
/* Based off the incredible work from https://dl.dropboxusercontent.com/u/12694594/lava.txt
mirrored at https://github.com/UnknownShadow200/ClassicalSharp/wiki/Minecraft-Classic-lava-animation-algorithm
Water animation originally written by cybertoon, big thanks!
*/
/*########################################################################################################################*
*--------------------------------------------------------ZipEntry---------------------------------------------------------*
*-----------------------------------------------------Lava animation------------------------------------------------------*
*#########################################################################################################################*/
#define ZIP_MAXNAMELEN 512
static ReturnCode Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) {
struct Stream* stream = state->Input;
uint8_t contents[26];
uint32_t compressedSize, uncompressedSize;
int method, pathLen, extraLen;
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 bool L_rndInitalised;
String path; char pathBuffer[ZIP_MAXNAMELEN];
struct Stream portion, compStream;
struct InflateState inflate;
ReturnCode res;
if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res;
static void LavaAnimation_Tick(BitmapCol* ptr, int size) {
int mask = size - 1, shift = Math_Log2(size);
float soupHeat, potHeat, col;
int x, y, i = 0;
/* contents[0] (2) version needed */
/* contents[2] (2) flags */
method = Stream_GetU16_LE(&contents[4]);
/* contents[6] (4) last modified */
/* contents[10] (4) CRC32 */
compressedSize = Stream_GetU32_LE(&contents[14]);
if (!compressedSize) compressedSize = entry->CompressedSize;
uncompressedSize = Stream_GetU32_LE(&contents[18]);
if (!uncompressedSize) uncompressedSize = entry->UncompressedSize;
pathLen = Stream_GetU16_LE(&contents[22]);
extraLen = Stream_GetU16_LE(&contents[24]);
if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN;
path = String_Init(pathBuffer, pathLen, pathLen);
if ((res = Stream_Read(stream, pathBuffer, pathLen))) return res;
if (!state->SelectEntry(&path)) return 0;
if ((res = stream->Skip(stream, extraLen))) return res;
if (method == 0) {
Stream_ReadonlyPortion(&portion, stream, uncompressedSize);
state->ProcessEntry(&path, &portion, entry);
} else if (method == 8) {
Stream_ReadonlyPortion(&portion, stream, compressedSize);
Inflate_MakeStream(&compStream, &inflate, &portion);
state->ProcessEntry(&path, &compStream, entry);
} else {
Platform_Log1("Unsupported.zip entry compression method: %i", &method);
if (!L_rndInitalised) {
Random_InitFromCurrentTime(&L_rnd);
L_rndInitalised = true;
}
return 0;
}
for (y = 0; y < size; y++) {
for (x = 0; x < size; x++) {
/* Calculate the colour at this coordinate in the heatmap */
static ReturnCode Zip_ReadCentralDirectory(struct ZipState* state, struct ZipEntry* entry) {
struct Stream* stream = state->Input;
uint8_t contents[42];
int pathLen, extraLen, commentLen;
uint32_t extraDataLen;
/* Lookup table for (int)(1.2 * sin([ANGLE] * 22.5 * MATH_DEG2RAD)); */
/* [ANGLE] is integer x/y, so repeats every 16 intervals */
static int8_t 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];
ReturnCode res;
if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res;
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)] +
/* contents[0] (2) OS */
/* contents[2] (2) version needed*/
/* contents[4] (2) flags */
/* contents[6] (2) compresssion method*/
/* contents[8] (4) last modified */
entry->Crc32 = Stream_GetU32_LE(&contents[12]);
entry->CompressedSize = Stream_GetU32_LE(&contents[16]);
entry->UncompressedSize = Stream_GetU32_LE(&contents[20]);
L_soupHeat[(yy & mask) << shift | ((xx - 1) & mask)] +
L_soupHeat[(yy & mask) << shift | (xx & mask)] +
L_soupHeat[(yy & mask) << shift | ((xx + 1) & mask)] +
pathLen = Stream_GetU16_LE(&contents[24]);
extraLen = Stream_GetU16_LE(&contents[26]);
commentLen = Stream_GetU16_LE(&contents[28]);
/* contents[30] (2) disk number */
/* contents[32] (2) internal attributes */
/* contents[34] (4) external attributes */
entry->LocalHeaderOffset = Stream_GetU32_LE(&contents[38]);
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)];
extraDataLen = pathLen + extraLen + commentLen;
return stream->Skip(stream, extraDataLen);
}
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 */
static ReturnCode Zip_ReadEndOfCentralDirectory(struct ZipState* state, uint32_t* centralDirectoryOffset) {
struct Stream* stream = state->Input;
uint8_t contents[18];
L_soupHeat[i] = soupHeat * 0.1f + potHeat * 0.2f;
ReturnCode res;
if ((res = Stream_Read(stream, contents, sizeof(contents)))) return res;
L_potHeat[i] += L_flameHeat[i];
if (L_potHeat[i] < 0.0f) L_potHeat[i] = 0.0f;
/* contents[0] (2) disk number */
/* contents[2] (2) disk number start */
/* contents[4] (2) disk entries */
state->EntriesCount = Stream_GetU16_LE(&contents[6]);
/* contents[8] (4) central directory size */
*centralDirectoryOffset = Stream_GetU32_LE(&contents[12]);
/* contents[16] (2) comment length */
return 0;
}
L_flameHeat[i] -= 0.06f * 0.01f;
if (Random_Float(&L_rnd) <= 0.005f) L_flameHeat[i] = 1.5f * 0.01f;
enum ZipSig {
ZIP_SIG_ENDOFCENTRALDIR = 0x06054b50,
ZIP_SIG_CENTRALDIR = 0x02014b50,
ZIP_SIG_LOCALFILEHEADER = 0x04034b50
};
/* Output the pixel */
col = 2.0f * L_soupHeat[i];
Math_Clamp(col, 0.0f, 1.0f);
static void Zip_DefaultProcessor(const String* path, struct Stream* data, struct ZipEntry* entry) { }
static bool Zip_DefaultSelector(const String* path) { return true; }
void Zip_Init(struct ZipState* state, struct Stream* input) {
state->Input = input;
state->EntriesCount = 0;
state->ProcessEntry = Zip_DefaultProcessor;
state->SelectEntry = Zip_DefaultSelector;
}
ptr->R = (uint8_t)(col * 100.0f + 155.0f);
ptr->G = (uint8_t)(col * col * 255.0f);
ptr->B = (uint8_t)(col * col * col * col * 128.0f);
ptr->A = 255;
ReturnCode Zip_Extract(struct ZipState* state) {
struct Stream* stream = state->Input;
uint32_t stream_len, centralDirOffset;
uint32_t sig = 0;
int i, count;
ReturnCode res;
state->EntriesCount = 0;
if ((res = stream->Length(stream, &stream_len))) return res;
/* At -22 for nearly all zips, but try a bit further back in case of comment */
count = min(257, stream_len);
for (i = 22; i < count; i++) {
res = stream->Seek(stream, stream_len - i);
if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig == ZIP_SIG_ENDOFCENTRALDIR) break;
}
if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR;
res = Zip_ReadEndOfCentralDirectory(state, &centralDirOffset);
if (res) return res;
res = stream->Seek(stream, centralDirOffset);
if (res) return ZIP_ERR_SEEK_CENTRAL_DIR;
if (state->EntriesCount > ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES;
/* Read all the central directory entries */
for (count = 0; count < state->EntriesCount; count++) {
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig == ZIP_SIG_CENTRALDIR) {
res = Zip_ReadCentralDirectory(state, &state->Entries[count]);
if (res) return res;
} else if (sig == ZIP_SIG_ENDOFCENTRALDIR) {
break;
} else {
return ZIP_ERR_INVALID_CENTRAL_DIR;
ptr++; i++;
}
}
}
/* Now read the local file header entries */
for (i = 0; i < count; i++) {
struct ZipEntry* entry = &state->Entries[i];
res = stream->Seek(stream, entry->LocalHeaderOffset);
if (res) return ZIP_ERR_SEEK_LOCAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR;
/*########################################################################################################################*
*----------------------------------------------------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 bool W_rndInitalised;
res = Zip_ReadLocalFileHeader(state, entry);
if (res) return res;
static void WaterAnimation_Tick(BitmapCol* ptr, int size) {
int mask = size - 1, shift = Math_Log2(size);
float soupHeat, col;
int x, y, i = 0;
if (!W_rndInitalised) {
Random_InitFromCurrentTime(&W_rnd);
W_rndInitalised = true;
}
for (y = 0; y < size; y++) {
for (x = 0; x < size; x++) {
/* Calculate the colour 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 */
col = W_soupHeat[i];
Math_Clamp(col, 0.0f, 1.0f);
col = col * col;
ptr->R = (uint8_t)(32.0f + col * 32.0f);
ptr->G = (uint8_t)(50.0f + col * 64.0f);
ptr->A = (uint8_t)(146.0f + col * 50.0f);
ptr->B = 255;
ptr++; i++;
}
}
}
/*########################################################################################################################*
*-------------------------------------------------------Animations--------------------------------------------------------*
*#########################################################################################################################*/
struct AnimationData {
TextureLoc TexLoc; /* Tile (not pixel) coordinates in terrain.png */
uint16_t FrameX, FrameY; /* Top left pixel coordinates of start frame in animatons.png */
uint16_t FrameSize; /* Size of each frame in pixel coordinates */
uint16_t State; /* Current animation frame index */
uint16_t StatesCount; /* Total number of animation frames */
int16_t Tick, TickDelay;
};
static Bitmap anims_bmp;
static struct AnimationData anims_list[ATLAS1D_MAX_ATLASES];
static int anims_count;
static bool anims_validated, anims_useLavaAnim, anims_useWaterAnim;
#define ANIM_MIN_ARGS 7
static void Animations_ReadDescription(struct Stream* stream, const String* path) {
String line; char lineBuffer[STRING_SIZE * 2];
String parts[ANIM_MIN_ARGS];
int count;
struct AnimationData data = { 0 };
uint8_t tileX, tileY;
uint8_t buffer[2048];
struct Stream buffered;
ReturnCode 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) { Chat_LogError2(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_TryParseUInt8(&parts[0], &tileX) || tileX >= ATLAS2D_TILES_PER_ROW) {
Chat_Add1("&cInvalid anim tile X coord: %s", &line); continue;
}
if (!Convert_TryParseUInt8(&parts[1], &tileY) || tileY >= ATLAS2D_MAX_ROWS_COUNT) {
Chat_Add1("&cInvalid anim tile Y coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[2], &data.FrameX)) {
Chat_Add1("&cInvalid anim frame X coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[3], &data.FrameY)) {
Chat_Add1("&cInvalid anim frame Y coord: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[4], &data.FrameSize)) {
Chat_Add1("&cInvalid anim frame size: %s", &line); continue;
}
if (!Convert_TryParseUInt16(&parts[5], &data.StatesCount)) {
Chat_Add1("&cInvalid anim states count: %s", &line); continue;
}
if (!Convert_TryParseInt16(&parts[6], &data.TickDelay)) {
Chat_Add1("&cInvalid anim tick delay: %s", &line); 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;
}
}
#define ANIMS_FAST_SIZE 64
static void Animations_Draw(struct AnimationData* data, TextureLoc texLoc, int size) {
int dstX = Atlas1D_Index(texLoc), srcX;
int dstY = Atlas1D_RowId(texLoc) * Atlas_TileSize;
GfxResourceID tex;
uint8_t buffer[Bitmap_DataSize(ANIMS_FAST_SIZE, ANIMS_FAST_SIZE)];
uint8_t* ptr = buffer;
Bitmap frame;
/* cannot allocate memory on the stack for very big animation.png frames */
if (size > ANIMS_FAST_SIZE) {
ptr = Mem_Alloc(size * size, 4, "anim frame");
}
Bitmap_Create(&frame, size, size, ptr);
if (!data) {
if (texLoc == 30) {
LavaAnimation_Tick((BitmapCol*)frame.Scan0, size);
} else if (texLoc == 14) {
WaterAnimation_Tick((BitmapCol*)frame.Scan0, size);
}
} else {
srcX = data->FrameX + data->State * size;
Bitmap_CopyBlock(srcX, data->FrameY, 0, 0, &anims_bmp, &frame, size);
}
tex = Atlas1D_TexIds[dstX];
if (tex) { Gfx_UpdateTexturePart(tex, 0, dstY, &frame, Gfx_Mipmaps); }
if (size > ANIMS_FAST_SIZE) Mem_Free(ptr);
}
static void Animations_Apply(struct AnimationData* data) {
TextureLoc loc;
data->Tick--;
if (data->Tick >= 0) return;
data->State++;
data->State %= data->StatesCount;
data->Tick = data->TickDelay;
loc = data->TexLoc;
if (loc == 30 && anims_useLavaAnim) return;
if (loc == 14 && anims_useWaterAnim) return;
Animations_Draw(data, loc, data->FrameSize);
}
static bool Animations_IsDefaultZip(void) {
String texPack; char texPackBuffer[STRING_SIZE];
if (World_TextureUrl.length) return false;
String_InitArray(texPack, texPackBuffer);
Options_Get(OPT_DEFAULT_TEX_PACK, &texPack, "default.zip");
return 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 > Atlas_TileSize || tileY >= Atlas_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 {
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, size;
if (anims_useLavaAnim) {
size = min(Atlas_TileSize, 64);
Animations_Draw(NULL, 30, size);
}
if (anims_useWaterAnim) {
size = min(Atlas_TileSize, 64);
Animations_Draw(NULL, 14, size);
}
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]);
}
}
static void Animations_PackChanged(void* obj) {
Animations_Clear();
anims_useLavaAnim = Animations_IsDefaultZip();
anims_useWaterAnim = anims_useLavaAnim;
}
static void Animations_FileChanged(void* obj, struct Stream* stream, const String* name) {
ReturnCode res;
if (String_CaselessEqualsConst(name, "animations.png")) {
res = Png_Decode(&anims_bmp, stream);
if (!res) return;
Chat_LogError2(res, "decoding", name);
Mem_Free(anims_bmp.Scan0);
anims_bmp.Scan0 = NULL;
} else if (String_CaselessEqualsConst(name, "animations.txt")) {
Animations_ReadDescription(stream, name);
} else if (String_CaselessEqualsConst(name, "uselavaanim")) {
anims_useLavaAnim = true;
} else if (String_CaselessEqualsConst(name, "usewateranim")) {
anims_useWaterAnim = true;
}
}
/*########################################################################################################################*
*--------------------------------------------------Animations component---------------------------------------------------*
*#########################################################################################################################*/
static void Animations_Init(void) {
ScheduledTask_Add(GAME_DEF_TICKS, Animations_Tick);
Event_RegisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged);
Event_RegisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged);
}
static void Animations_Free(void) {
Animations_Clear();
Event_UnregisterVoid(&TextureEvents_PackChanged, NULL, Animations_PackChanged);
Event_UnregisterEntry(&TextureEvents_FileChanged, NULL, Animations_FileChanged);
}
struct IGameComponent Animations_Component = {
Animations_Init, /* Init */
Animations_Free /* Free */
};
/*########################################################################################################################*
*------------------------------------------------------TerrainAtlas-------------------------------------------------------*
*#########################################################################################################################*/
Bitmap Atlas_Bitmap;
int Atlas_TileSize, Atlas_RowsCount;
int Atlas1D_Count, Atlas1D_TilesPerAtlas;
int Atlas1D_Mask, Atlas1D_Shift;
float Atlas1D_InvTileSize;
GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES];
TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index) {
TextureRec rec;
int y = Atlas1D_RowId(texLoc);
*index = Atlas1D_Index(texLoc);
/* Adjust coords to be slightly inside - fixes issues with AMD/ATI cards */
rec.U1 = 0.0f;
rec.V1 = y * Atlas1D_InvTileSize;
rec.U2 = (uCount - 1) + UV2_Scale;
rec.V2 = rec.V1 + UV2_Scale * Atlas1D_InvTileSize;
return rec;
}
static void Atlas_Convert2DTo1D(void) {
int tileSize = Atlas_TileSize;
int tilesPerAtlas = Atlas1D_TilesPerAtlas;
int atlasesCount = Atlas1D_Count;
Bitmap atlas1D;
int atlasX, atlasY;
int tile = 0, i, y;
Platform_Log2("Loaded new atlas: %i bmps, %i per bmp", &atlasesCount, &tilesPerAtlas);
Bitmap_Allocate(&atlas1D, tileSize, tilesPerAtlas * tileSize);
for (i = 0; i < atlasesCount; i++) {
for (y = 0; y < tilesPerAtlas; y++, tile++) {
atlasX = Atlas2D_TileX(tile) * tileSize;
atlasY = Atlas2D_TileY(tile) * tileSize;
Bitmap_CopyBlock(atlasX, atlasY, 0, y * tileSize,
&Atlas_Bitmap, &atlas1D, tileSize);
}
Atlas1D_TexIds[i] = Gfx_CreateTexture(&atlas1D, true, Gfx_Mipmaps);
}
Mem_Free(atlas1D.Scan0);
}
static void Atlas_Update1D(void) {
int maxAtlasHeight, maxTilesPerAtlas, maxTiles;
maxAtlasHeight = min(4096, Gfx_MaxTexHeight);
maxTilesPerAtlas = maxAtlasHeight / Atlas_TileSize;
maxTiles = Atlas_RowsCount * ATLAS2D_TILES_PER_ROW;
Atlas1D_TilesPerAtlas = min(maxTilesPerAtlas, maxTiles);
Atlas1D_Count = Math_CeilDiv(maxTiles, Atlas1D_TilesPerAtlas);
Atlas1D_InvTileSize = 1.0f / Atlas1D_TilesPerAtlas;
Atlas1D_Mask = Atlas1D_TilesPerAtlas - 1;
Atlas1D_Shift = Math_Log2(Atlas1D_TilesPerAtlas);
}
void Atlas_Update(Bitmap* bmp) {
Atlas_Bitmap = *bmp;
Atlas_TileSize = bmp->Width / ATLAS2D_TILES_PER_ROW;
Atlas_RowsCount = bmp->Height / Atlas_TileSize;
Atlas_RowsCount = min(Atlas_RowsCount, ATLAS2D_MAX_ROWS_COUNT);
Block_RecalculateAllSpriteBB();
Atlas_Update1D();
Atlas_Convert2DTo1D();
}
static GfxResourceID Atlas_LoadTile_Raw(TextureLoc texLoc, Bitmap* element) {
int size = Atlas_TileSize;
int x = Atlas2D_TileX(texLoc), y = Atlas2D_TileY(texLoc);
if (y >= Atlas_RowsCount) return GFX_NULL;
Bitmap_CopyBlock(x * size, y * size, 0, 0, &Atlas_Bitmap, element, size);
return Gfx_CreateTexture(element, false, Gfx_Mipmaps);
}
GfxResourceID Atlas_LoadTile(TextureLoc texLoc) {
int tileSize = Atlas_TileSize;
Bitmap tile;
GfxResourceID texId;
uint8_t scan0[Bitmap_DataSize(64, 64)];
/* Try to allocate bitmap on stack if possible */
if (tileSize > 64) {
Bitmap_Allocate(&tile, tileSize, tileSize);
texId = Atlas_LoadTile_Raw(texLoc, &tile);
Mem_Free(tile.Scan0);
return texId;
} else {
Bitmap_Create(&tile, tileSize, tileSize, scan0);
return Atlas_LoadTile_Raw(texLoc, &tile);
}
}
void Atlas_Free(void) {
int i;
Mem_Free(Atlas_Bitmap.Scan0);
Atlas_Bitmap.Scan0 = NULL;
for (i = 0; i < Atlas1D_Count; i++) {
Gfx_DeleteTexture(&Atlas1D_TexIds[i]);
}
return 0;
}

View File

@ -1,38 +1,69 @@
#ifndef CC_TEXPACKS_H
#define CC_TEXPACKS_H
#include "Utils.h"
/* Extracts entries from a .zip archive stream (mostly resources for .zip texture pack)
Caches terrain atlases and texture packs to avoid making redundant downloads.
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
*/
#include "String.h"
#include "Bitmap.h"
/* Contains everything relating to texture packs.
- Extracting the textures from a .zip archive
- Caching terrain atlases and texture packs to avoid redundant downloads
- Terrain atlas (including breaking it down into multiple 1D atlases)
- Texture animations (including default water/lava ones)
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 */
struct Stream;
struct AsyncRequest;
struct IGameComponent;
extern struct IGameComponent Animations_Component;
/* Minimal data needed to describe an entry in a .zip archive. */
struct ZipEntry { uint32_t CompressedSize, UncompressedSize, LocalHeaderOffset, Crc32; };
/* Number of tiles in each row */
#define ATLAS2D_TILES_PER_ROW 16
#define ATLAS2D_MASK 15
#define ATLAS2D_SHIFT 4
/* Maximum supported number of rows in the atlas. */
#ifdef EXTENDED_TEXTURES
#define ATLAS2D_MAX_ROWS_COUNT 32
#else
#define ATLAS2D_MAX_ROWS_COUNT 16
#endif
/* Maximum possible number of 1D terrain atlases. (worst case, each 1D atlas only has 1 tile) */
#define ATLAS1D_MAX_ATLASES (ATLAS2D_TILES_PER_ROW * ATLAS2D_MAX_ROWS_COUNT)
#define ZIP_MAX_ENTRIES 2048
/* Stores state for reading and processing entries in a .zip archive. */
struct ZipState {
/* Source of the .zip archive data. Must be seekable. */
struct Stream* Input;
/* Callback function to process the data in a .zip archive entry. */
/* Note that data stream may not be seekable. (entry data might be compressed) */
void (*ProcessEntry)(const String* path, struct Stream* data, struct ZipEntry* entry);
/* Predicate used to select which entries in a .zip archive get proessed.*/
/* Return false to skip the entry. (this avoids seeking to the entry's data) */
bool (*SelectEntry)(const String* path);
/* Number of entries in the .zip archive. */
int EntriesCount;
/* Data for each entry in the .zip archive. */
struct ZipEntry Entries[ZIP_MAX_ENTRIES];
};
/* Bitmap that contains the textures of all tiles. */
/* Tiles are indexed left to right, top to bottom. */
extern Bitmap Atlas_Bitmap;
/* Size of each tile in pixels. (default 16x16) */
extern int Atlas_TileSize;
/* Number of rows in the atlas. (default 16, can be 32) */
extern int Atlas_RowsCount;
/* Number of 1D atlases the atlas was split into. */
extern int Atlas1D_Count;
/* Number of tiles in each 1D atlas. */
extern int Atlas1D_TilesPerAtlas;
/* Converts a tile id into 1D atlas index, and index within that atlas. */
extern int Atlas1D_Mask, Atlas1D_Shift;
/* Texture V coord that equals the size of one tile. (i.e. 1/Atlas1D_TilesPerAtlas) */
/* NOTE: The texture U coord that equals the size of one tile is 1. */
extern float Atlas1D_InvTileSize;
/* Textures for each 1D atlas. Only Atlas1D_Count of these are valid. */
extern GfxResourceID Atlas1D_TexIds[ATLAS1D_MAX_ATLASES];
/* Initialises .zip archive reader state. */
void Zip_Init(struct ZipState* state, struct Stream* input);
/* Reads and processes the entries in a .zip archive. */
/* Must have been initialised with Zip_Init first. */
ReturnCode Zip_Extract(struct ZipState* state);
#define Atlas2D_TileX(texLoc) ((texLoc) & ATLAS2D_MASK) /* texLoc % ATLAS2D_TILES_PER_ROW */
#define Atlas2D_TileY(texLoc) ((texLoc) >> ATLAS2D_SHIFT) /* texLoc / ATLAS2D_TILES_PER_ROW */
/* Returns the index of the given tile id within a 1D atlas */
#define Atlas1D_RowId(texLoc) ((texLoc) & Atlas1D_Mask) /* texLoc % Atlas1D_TilesPerAtlas */
/* Returns the index of the 1D atlas within the array of 1D atlases that contains the given tile id */
#define Atlas1D_Index(texLoc) ((texLoc) >> Atlas1D_Shift) /* texLoc / Atlas1D_TilesPerAtlas */
/* Loads the given atlas and converts it into an array of 1D atlases. */
/* NOTE: Use Game_ChangeTerrainAtlas to change atlas, because that raises TextureEvents_AtlasChanged */
void Atlas_Update(Bitmap* bmp);
/* Loads the given tile into a new separate texture. */
GfxResourceID Atlas_LoadTile(TextureLoc texLoc);
/* Frees the atlas and 1D atlas textures. */
void Atlas_Free(void);
/* Returns the UV rectangle of the given tile id in the 1D atlases. */
/* That is, returns U1/U2/V1/V2 coords that make up the tile in a 1D atlas. */
/* index is set to the index of the 1D atlas that the tile is in. */
TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index);
/* Initialises cache state. (e.g. loading accepted/denied lists) */
void TextureCache_Init(void);