515 lines
18 KiB
C
515 lines
18 KiB
C
//
|
||
// 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 <dirent.h>
|
||
|
||
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);
|
||
}
|