// // Created by bruno on 4/24/25. // #include "item.h" #include "../player/player.h" #include "../util/font.h" #include "../tiles/furnace.h" #include "../tiles/tilecallbacks.h" #include "../util/atlas.h" #include Item ItemRegistry[ITEMREGISTRY_SIZE]; uint16_t itemRegistryIndex = 0; double speed = 1.0f / TILE_SIZE; // fraction of tile per tick const int dirDx[8] = {-1, -1, -1, 0, 1, 1, 1, 0}; const int dirDy[8] = {1, 0, -1, -1, -1, 0, 1, 1}; const float epsilon = 0.999f; // if we can't move, back it off just below 1 bool putOntoNext(ItemOnBelt *itm, int nx, int ny, Tile *next, TileTypeReg *ntt, int newLane) { if (next->items[newLane].type == 0 && (*ntt).allowedInItems[newLane][itm->type]) { // MOVE it ItemOnBelt moved = *itm; moved.tileX = nx; moved.tileY = ny; if (!(*ntt).itemMoves) { moved.offset = 0.5f; } next->items[newLane] = moved; // clear this one itm->type = 0; return true; } else { // both slots full → wait at end itm->offset = epsilon; return false; } } OrientDirection rotateMainDirection(OrientDirection dir, int steps) { // The main directions indices array int mainDirs[] = {1, 3, 5, 7}; int count = 4; // Find index of dir in mainDirs int index = 0; for (; index < count; index++) { if (mainDirs[index] == dir) break; } if (index == count) index = 0; // fallback // Rotate steps (positive = clockwise, negative = counterclockwise) int newIndex = (index + steps) % count; if (newIndex < 0) newIndex += count; return (OrientDirection)mainDirs[newIndex]; } // Map 8 directions to main 4 for code output int map8To4DirectionCode(OrientDirection dir) { switch (dir) { case ORIENT_LEFT: return 4; // left case ORIENT_UP: return 5; // up case ORIENT_RIGHT: return 2; // right case ORIENT_DOWN: return 3; // down default: return 1; // undefined/error } } int getDirectionCode(OrientDirection dir, OrientDirection dir2) { // Apply dir2 rotation: switch (dir2) { case ORIENT_UP: // no change break; case ORIENT_LEFT: // move dir one step to the right (clockwise) dir = rotateMainDirection(dir, +1); break; case ORIENT_RIGHT: // move dir one step to the left (counterclockwise) dir = rotateMainDirection(dir, -1); break; case ORIENT_DOWN: // move dir two steps to the left (counterclockwise) dir = rotateMainDirection(dir, -2); break; default: break; } return map8To4DirectionCode(dir); } void updateItems() { for (int i = 0; i < neededUpdates.activeCount; i++) { int x = neededUpdates.tiles[i].x; int y = neededUpdates.tiles[i].y; Tile *t = &tileMap[y][x]; if (t->type == TYPE_AIR) continue; TileTypeReg tt = TileRegistry[t->type]; int dir = t->direction; bool horz = (dir == ORIENT_LEFT || dir == ORIENT_RIGHT); bool vert = (dir == ORIENT_UP || dir == ORIENT_DOWN); for (uint8_t lane = 0; lane < ItemSlotCount; lane++) { if (!tt.outputLane[lane]) continue; ItemOnBelt *itm = &t->items[lane]; if (itm->type == 0) continue; if (tt.itemMoves) { itm->offset += speed; // 1) Advance } // 2) Time to hop? if (itm->offset >= 0.5f || !tt.itemMoves) { if (tt.itemMoves) { itm->offset -= 1.0f; } int inputDir = -1; // invalid by default // Only valid if this tile is a belt and we can infer the input direction if (tt.itemMoves) { inputDir = (t->direction + 2) % 4; // Assume item entered from opposite of its direction } bool handled = false; if (tt.allDir) { for (OrientDirection outDir = 0; outDir < ORIENT_DIRECTION_COUNT; outDir++) { if (outDir == inputDir) continue; // skip input dir int nx = x + dirDx[outDir]; int ny = y + dirDy[outDir]; if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) continue; Tile *next = &tileMap[ny][nx]; TileTypeReg ntt = TileRegistry[next->type]; // Try to put item into any slot for (uint8_t nLane = 0; nLane < ItemSlotCount; nLane++) { if (putOntoNext(itm, nx, ny, next, &ntt, nLane)) { handled = true; break; } } if (handled) { t->fixedFrame = getDirectionCode(outDir, t->direction); break; }; } } else { // target coords int nx = x + dirDx[dir]; int ny = y + dirDy[dir]; // bounds & belt? if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) { if (tt.itemMoves) { itm->offset += 1.0f - speed; } continue; } Tile *next = &tileMap[ny][nx]; TileTypeReg ntt = TileRegistry[next->type]; int newLane = lane; switch (next->type) { case TYPE_BELT: int newDir = next->direction; bool nH = (newDir == ORIENT_LEFT || newDir == ORIENT_RIGHT); bool nV = (newDir == ORIENT_UP || newDir == ORIENT_DOWN); if ((horz && nH) || (vert && nV)) { // same axis → keep lane } else if (horz && nV) { // came off a horizontal: lane0=top→vertical.left, lane1=bottom→vertical.right newLane = (dir == ORIENT_RIGHT ^ newDir == ORIENT_UP ? 0 : 1); itm->offset = 0.0f; } else if (vert && nH) { // came off vertical: lane0=left→horizontal.top, lane1=right→horizontal.bottom newLane = (dir == ORIENT_UP ^ newDir == ORIENT_RIGHT ? 1 : 0); itm->offset = 0.0f; } // (diagonals fall back to same-lane) // Find a free slot in break; default: itm->offset += 1.0f - speed; } if (!putOntoNext(itm, nx, ny, next, &ntt, newLane) && (next->type != TYPE_BELT || newLane >= 2)) { for (uint8_t nLane = 0; nLane < ItemSlotCount; nLane++) { if (putOntoNext(itm, nx, ny, next, &ntt, nLane)) { break; } } } } } } const UpdateTileCallback cb = ItemTileCallbacks[t->type]; if (cb) { cb(t); } } } // easing function: cosine ease‐in‐out //static float ease_in_out(float t) { // if (t < -1.0f) t = -1.0f; // if (t > 1.0f) t = 1.0f; // // // Even symmetric easing: reflected across t = 0 // return (t < 0.0f) // ? -0.5f * (1.0f - cosf(M_PI * -t)) // negative side // : 0.5f * (1.0f - cosf(M_PI * t)); // positive side //} static float ease_in_out(float t) { // Clamp t to [-1.0, 1.0] if (t < -1.0f) t = -1.0f; if (t > 1.0f) t = 1.0f; return t; // Linear: no easing } uint8_t laneTarget = 0; void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect playerRect) { SDL_Rect rect = {0}; rect.x = item.tileX * TILE_SIZE; rect.y = item.tileY * TILE_SIZE; // get raw direction code int dir = tileMap[item.tileY][item.tileX].direction; //--- 1) build a unit vector (dxf, dyf) for this orientation float dxf = 0.0f, dyf = 0.0f; const float D = M_SQRT1_2; // 1/√2 ≈ 0.7071 switch (dir) { case ORIENT_LEFT_DOWN: dxf = -D; dyf = +D; break; case ORIENT_LEFT: dxf = -1.0f; dyf = 0.0f; break; case ORIENT_LEFT_UP: dxf = -D; dyf = -D; break; case ORIENT_UP: dxf = 0.0f; dyf = -1.0f; break; case ORIENT_RIGHT_UP: dxf = +D; dyf = -D; break; case ORIENT_RIGHT: dxf = +1.0f; dyf = 0.0f; break; case ORIENT_RIGHT_DOWN: dxf = +D; dyf = +D; break; case ORIENT_DOWN: dxf = 0.0f; dyf = +1.0f; break; default: break; } //--- 2) ease the offset and compute forward distance float t = ease_in_out(item.offset); // 0..1 float dist = t * TILE_SIZE; // 0..TILE_SIZE pixels float xOffset = dxf * dist; float yOffset = dyf * dist; switch (dir) { case ORIENT_LEFT_DOWN: xOffset += 0.0f * TILE_SIZE; yOffset += 0.0f * TILE_SIZE; break; case ORIENT_LEFT: xOffset += 0.0f * TILE_SIZE + (TILE_SIZE); yOffset += 0.26f * TILE_SIZE; break; case ORIENT_LEFT_UP: xOffset += 0.0f * TILE_SIZE; yOffset += 0.0f * TILE_SIZE; break; case ORIENT_UP: xOffset += 0.22f * TILE_SIZE; //GOTO HEHREHRHE yOffset += 0.0f * TILE_SIZE + (TILE_SIZE); break; case ORIENT_RIGHT_UP: xOffset += 0.0f * TILE_SIZE; yOffset += 0.0f * TILE_SIZE; break; case ORIENT_RIGHT: xOffset += 0.0f * TILE_SIZE - (TILE_SIZE / 2); yOffset += 0.18f * TILE_SIZE; //FIX THIS break; case ORIENT_RIGHT_DOWN: xOffset += 0.0f * TILE_SIZE; yOffset += 0.0f * TILE_SIZE; break; case ORIENT_DOWN: xOffset += 0.18f * TILE_SIZE + (TILE_SIZE / 8); yOffset += 0.0f * TILE_SIZE - (TILE_SIZE / 2); break; default: break; } //--- 3) compute perpendicular unit vector (perpX, perpY) = (-dy, dx) float perpX = -dyf; float perpY = dxf; // perp is already unit length because (dxf,dyf) is unit bool horz = (dir == ORIENT_LEFT || dir == ORIENT_RIGHT); bool vert = (dir == ORIENT_UP || dir == ORIENT_DOWN); //--- 4) lane offset // total lane spacing = TILE_SIZE/2, so half of that each side = TILE_SIZE/4 float laneSign = (lane == laneTarget ? +1.0f : -1.0f); float laneOffset = TILE_SIZE * (horz ? 0.12f : 0.14f); // = TILE_SIZE/4 xOffset += perpX * laneSign * laneOffset; yOffset += perpY * laneSign * laneOffset; //--- 5) apply to tileRect rect.x += (int) roundf(xOffset); rect.y += (int) roundf(yOffset); rect.w = TILE_SIZE / 2; rect.h = TILE_SIZE / 2; adjustRect(&rect, playerRect); // (Optional debug overlay) char tempStr[50]; SDL_Rect rectA = {0}; if (debugMode) { SDL_Rect tileArea = {item.tileX * TILE_SIZE, item.tileY * TILE_SIZE, TILE_SIZE, TILE_SIZE}; SDL_SetRenderDrawColor(renderer, 255, 0, 0, 32); adjustRect(&tileArea, playerRect); SDL_RenderFillRect(renderer, &tileArea); rectA.x = item.tileX * TILE_SIZE; rectA.y = item.tileY * TILE_SIZE; rectA.w = TILE_SIZE; rectA.h = TILE_SIZE; sprintf(tempStr, "L%d\n%f\n%f\n%f", lane, item.offset, xOffset, yOffset); SDL_SetRenderDrawColor(renderer, 255, 0, 0, 32); adjustRect(&rectA, playerRect); SDL_RenderFillRect(renderer, &rectA); } //SDL_RenderCopyx(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &tileRect); SDL_RenderCopy(renderer, atlasTexture, &ItemRegistry[item.type].beltAnimation.atlasRects[ORIENT_LEFT][( (animationStep / ItemRegistry[item.type].beltAnimation.divisor) % ( ItemRegistry[item.type].beltAnimation.frameCount - ItemRegistry[item.type].beltAnimation.startFrame)) + ItemRegistry[item.type].beltAnimation.startFrame], &rect); if (debugMode) { renderText(renderer, fonts[3], tempStr, rectA.x, rectA.y); } } void putItem(int x, int y, ItemType itemType, uint8_t lane) { tileMap[y][x].items[lane].type = itemType; tileMap[y][x].items[lane].offset = 0; tileMap[y][x].items[lane].tileX = x; tileMap[y][x].items[lane].tileY = y; } void loadItems(SDL_Renderer *renderer) { // Load tile-based items for (int i = 0; i < tileTypeIndex; i++) { TileTypeReg tile = TileRegistry[i]; Item *item = &ItemRegistry[itemRegistryIndex]; strcpy(item->name, tile.name); memcpy(&item->animation, &tile.animation, sizeof(tile.animation)); SDL_Texture *texTmp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, TILE_SIZE, TILE_SIZE); for (int frame = 0; frame < item->animation.frameCount; frame++) { for (int a = 0; a < ORIENT_DIRECTION_COUNT; a++) { SDL_SetRenderTarget(renderer, texTmp); SDL_RenderCopy(renderer, atlasTexture, &tile.animation.atlasRects[a][frame], NULL); SDL_Texture *tex = ScaleTexture(renderer, texTmp, TILE_SIZE / 2, TILE_SIZE / 2); item->beltAnimation.atlasRects[a][frame] = allocate_16x16(tex, renderer); SDL_DestroyTexture(tex); if (frame + 1 > item->beltAnimation.frameCount) { item->beltAnimation.frameCount = frame + 1; } } } SDL_DestroyTexture(texTmp); item->type = itemRegistryIndex; item->isTile = true; item->animation.divisor = tile.animation.divisor; item->beltAnimation.divisor = tile.animation.divisor; item->animation.startFrame = tile.animation.startFrame; item->beltAnimation.startFrame = tile.animation.startFrame; itemRegistryIndex++; } // Skip ahead to avoid overlap (tile items use lower indices) itemRegistryIndex = ITEMREGISTRY_SIZE / 2; // Load sprite-based items with animations DIR *dir = opendir("./assets/items"); if (!dir) { perror("Failed to open item asset folder"); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (!strstr(entry->d_name, ".png")) continue; int frame, indexItem; char name[64]; if (sscanf(entry->d_name, "%d-%20[^-]-%d.png", &indexItem, name, &frame) == 3) { // Success: you now have index, fname, and frame } else { fprintf(stderr, "Invalid format: %s\n", entry->d_name); } indexItem += ITEMREGISTRY_SIZE / 2; Item *item; item = &ItemRegistry[indexItem]; memset(item, 0, sizeof(Item)); strcpy(item->name, name); item->type = indexItem; item->isTile = false; item->miscVal = 60; item->animation.divisor = 1; item->beltAnimation.divisor = 1; // Load the texture char path[128]; snprintf(path, sizeof(path), "./assets/items/%s", entry->d_name); SDL_Texture *tex = IMG_LoadTexture(renderer, path); if (!tex) { fprintf(stderr, "Failed to load texture %s: %s\n", path, IMG_GetError()); continue; } SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); SDL_Texture *beltTex = ScaleTexture(renderer, tex, TILE_SIZE / 2, TILE_SIZE / 2); SDL_SetTextureBlendMode(beltTex, SDL_BLENDMODE_BLEND); SDL_Rect mainRect = allocate_32x32(tex, renderer); SDL_Rect beltRect = allocate_16x16(beltTex, renderer); // Assign to all orientations for (int o = 0; o < ORIENT_DIRECTION_COUNT; o++) { item->animation.atlasRects[o][frame] = mainRect; item->beltAnimation.atlasRects[o][frame] = beltRect; if (frame + 1 > item->animation.frameCount) { item->animation.frameCount = frame + 1; item->beltAnimation.frameCount = frame + 1; } } item->animation.divisor = 1; if (indexItem + 1 > itemRegistryIndex) { itemRegistryIndex = indexItem + 1; } } closedir(dir); }