Files
factorygame/items/item.c
2025-06-11 15:42:06 +02:00

530 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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.prevTile = moved.tile;
moved.tile.x = nx;
moved.tile.y = 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};
if (steps == -1) {
mainDirs[1] = 7;
mainDirs[3] = 3;
mainDirs[0] = 5;
mainDirs[2] = 1;
}
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;
if (nx == itm->prevTile.x && ny == itm->prevTile.y) {
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];
if (nx == itm->prevTile.x && ny == itm->prevTile.y) {
continue;
}
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 easeinout
//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.tile.x * TILE_SIZE;
rect.y = item.tile.y * TILE_SIZE;
// get raw direction code
int dir = tileMap[item.tile.y][item.tile.x].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.tile.x * TILE_SIZE, item.tile.y * TILE_SIZE,
TILE_SIZE, TILE_SIZE};
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 32);
adjustRect(&tileArea, playerRect);
SDL_RenderFillRect(renderer, &tileArea);
rectA.x = item.tile.x * TILE_SIZE;
rectA.y = item.tile.y * 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].tile.x = x;
tileMap[y][x].items[lane].tile.y = y;
tileMap[y][x].items[lane].prevTile = tileMap[y][x].items[lane].tile;
}
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);
}