// // Created by rov on 12/27/25. // #include "objects.h" #include "window.h" #include #include #include #include #include bool Objects_Init(Objects_t *objects) { for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { objects->thingDataPool[i] = malloc(1536); if (objects->thingDataPool[i]) { memset(objects->thingDataPool[i], 0, 1536); } } memset(objects->activeObjectList, 0, sizeof(objects->activeObjectList)); SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_Init: memory allocated"); if (!Objects_LoadData(objects)) { return false; } return Objects_LoadSprites(objects); } bool Objects_LoadData(Objects_t *objects) { SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: loading object data."); SDL_IOStream *file = SDL_IOFromFile("MUDOBJ.CLI", "rb"); if (!file) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadData: couldn't open file MUDOBJ.CLI", NULL); return false; } int objectsLoaded = 0; while (true) { uint16_t src; uint8_t categoryID, thingID; // Read IDs if (SDL_ReadIO(file, &categoryID, 1) != 1) { break; } if (SDL_ReadIO(file, &thingID, 1) != 1) { break; } // Store header info ThingHeader *header = (ThingHeader *) ((uint8_t *) objects->thingDataPool[categoryID] + thingID * 6); SDL_ReadIO(file, &header->properties, 2); SDL_ReadIO(file, &src, 2); header->flags = src + 4; // Calculate total sprites needed const int countWidth = (src >> 4 & 3) + 1; const int countHeight = (src >> 6 & 3) + 1; const int countPattern = (src & 0xF) + 1; const int countLayers = (src >> 8 & 1) + 1; const int countAnim = (src >> 9 & 1) + 1; const int countSides = (src >> 10 & 1) + 1; // Total sprites to read for this object const int totalSprites = countWidth * countHeight * countPattern * countLayers * countAnim * countSides; const size_t bufferSize = sizeof(uint16_t) + totalSprites * sizeof(uint16_t); uint16_t *destBuffer = objects->activeObjectList[categoryID]; if (destBuffer == NULL) { destBuffer = (uint16_t *) malloc(bufferSize); if (!destBuffer) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: cannot allocate memory"); break; } destBuffer[0] = 0; objects->activeObjectList[categoryID] = destBuffer; } else { const size_t currentCount = destBuffer[0]; const size_t newTotalSize = sizeof(uint16_t) + (currentCount + totalSprites) * sizeof(uint16_t); destBuffer = (uint16_t *) realloc(destBuffer, newTotalSize); objects->activeObjectList[categoryID] = destBuffer; } header->spriteIndex = destBuffer[0]; // Read sprites // Pointer to where we start writing new sprites: Buffer + 1 (Skip count) + Current Count uint16_t *writePtr = &destBuffer[1 + destBuffer[0]]; const size_t readCount = SDL_ReadIO(file, writePtr, totalSprites * 2); // Update the item count in the buffer header // Note: SDL_ReadIO returns bytes, so we divide by 2 for item count const uint16_t itemsRead = (uint16_t) (readCount / 2); destBuffer[0] += itemsRead; objectsLoaded += itemsRead; } SDL_CloseIO(file); SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadData: data loaded (%d objects).", objectsLoaded); return true; } bool Objects_LoadSprites(Objects_t *objects) { SDL_Renderer* renderer = Window_GetRenderer(); SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: loading object sprites."); // Free existing sprites to prevent memory leaks for (int i = 0; i < MAX_SPRITES; i++) { if (objects->spriteTextures[i] != NULL) { SDL_DestroyTexture(objects->spriteTextures[i]); objects->spriteTextures[i] = NULL; } } SDL_IOStream *file = SDL_IOFromFile("MUDOBJ.SPR", "rb"); if (!file) { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadSprites: couldn't open file MUDOBJ.SPR.", NULL); return false; } uint16_t spriteCount = 0; if (SDL_ReadIO(file, &spriteCount, 2) != 2) { SDL_CloseIO(file); SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", "Objects_LoadSprites: couldn't get sprite count.", NULL); return false; } int spritesLoaded = 0; for (int i = 0; i < spriteCount; i++) { // Read ID (2 bytes) uint16_t spriteID; if (SDL_ReadIO(file, &spriteID, 2) != 2) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read sprite ID."); break; } uint16_t spriteSize; if (SDL_ReadIO(file, &spriteSize, 2) != 2) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read sprite size."); break; } // Ensure ID is within bounds if (spriteID >= MAX_SPRITES) { SDL_LogWarn(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: Sprite ID %d exceeds table size (%d). Skipping.", spriteID, MAX_SPRITES); // Skip the data bytes so we don't desync the file stream SDL_SeekIO(file, spriteSize - 2, SDL_IO_SEEK_CUR); continue; } const uint16_t bytesToRead = spriteSize - 2; if (bytesToRead <= 0) { continue; } uint8_t *spriteBuffer = malloc(spriteSize); if (!spriteBuffer) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: cannot allocate memory"); break; } if (SDL_ReadIO(file, spriteBuffer, bytesToRead) != bytesToRead) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: couldn't read full sprite data"); break; } objects->spriteTextures[spriteID] = Objects_TextureFromRaw(renderer, spriteBuffer, bytesToRead); free(spriteBuffer); spritesLoaded++; } SDL_CloseIO(file); SDL_LogInfo(SDL_LOG_CATEGORY_CUSTOM, "Objects_LoadSprites: %d sprites loaded.", spritesLoaded); return true; } void Objects_Destroy(const Objects_t *objects) { for (int i = 0; i < THING_DATA_POOL_SIZE; i++) { free(objects->thingDataPool[i]); } for (int i = 0; i < MAX_SPRITES; i++) { SDL_DestroyTexture(objects->spriteTextures[i]); } } SDL_Texture * Objects_GetSpriteTexture(const Objects_t* objects, const uint16_t spriteID) { if (spriteID < 0 || spriteID >= MAX_SPRITES) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_GetSpriteTexture: sprite ID out of range (ID: %d).", spriteID); return NULL; } return objects->spriteTextures[spriteID]; } SDL_Texture * Objects_TextureFromRaw(SDL_Renderer *renderer, const uint8_t *rawData, const uint16_t size) { if (!rawData) { return NULL; } SDL_Surface* surface = SDL_CreateSurface(SPRITE_SIZE, SPRITE_SIZE, SDL_PIXELFORMAT_ARGB8888); if (!surface) { SDL_LogError(SDL_LOG_CATEGORY_CUSTOM, "Objects_TextureFromRaw: couldn't create surface"); return NULL; } SDL_LockSurface(surface); uint32_t* pixels = surface->pixels; memset(pixels, 0, SPRITE_SIZE * SPRITE_SIZE * 4); int currentPixel = 0; int readOffset = 0; while (readOffset < size && currentPixel < 1024) { const uint16_t transparentRaw = *(uint16_t*) (rawData + readOffset); readOffset += 2; const uint16_t transparentBytes = transparentRaw % 0x5A0; const uint16_t skipPixels = transparentBytes / 3; currentPixel += skipPixels; const uint16_t colorBytes = *(uint16_t*)(rawData + readOffset); readOffset += 2; const uint16_t drawPixels = colorBytes / 3; for (int i = 0; i < drawPixels; i++) { if (currentPixel >= 1024) { break; } const uint8_t b = rawData[readOffset++]; const uint8_t g = rawData[readOffset++]; const uint8_t r = rawData[readOffset++]; // Flip image const int x = currentPixel % SPRITE_SIZE; const int y = currentPixel / SPRITE_SIZE; const int flippedY = 31 - y; pixels[flippedY * SPRITE_SIZE + x] = SDL_MapRGBA(SDL_GetPixelFormatDetails(surface->format), NULL, r, g, b, 255); currentPixel++; } } SDL_UnlockSurface(surface); SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); SDL_DestroySurface(surface); return texture; }