More progress on audio and rendering
This commit is contained in:
BIN
assets/audio/testaid.mid
Normal file
BIN
assets/audio/testaid.mid
Normal file
Binary file not shown.
180
items/item.c
180
items/item.c
@@ -16,103 +16,103 @@ 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
|
||||
void updateItems() {
|
||||
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
|
||||
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;
|
||||
|
||||
for (int y = 0; y < MAP_HEIGHT; y++) {
|
||||
for (int x = 0; x < MAP_WIDTH; x++) {
|
||||
Tile *t = &tileMap[y][x];
|
||||
TileTypeReg tt = TileRegistry[t->type];
|
||||
if (t->type == TYPE_AIR) continue;
|
||||
int dir = t->direction;
|
||||
bool horz = (dir == ORIENT_LEFT || dir == ORIENT_RIGHT);
|
||||
bool vert = (dir == ORIENT_UP || dir == ORIENT_DOWN);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for (uint8_t lane = 0; lane < ItemSlotCount; lane++) {
|
||||
if (!tt.outputLane[lane]) continue;
|
||||
ItemOnBelt *itm = &t->items[lane];
|
||||
if (itm->type == 0) continue;
|
||||
// 2) Time to hop?
|
||||
if (itm->offset >= 0.5f || !tt.itemMoves) {
|
||||
if (tt.itemMoves) {
|
||||
itm->offset += speed;
|
||||
// 1) Advance
|
||||
itm->offset -= 1.0f;
|
||||
}
|
||||
|
||||
// 2) Time to hop?
|
||||
if (itm->offset >= 0.5f || !tt.itemMoves) {
|
||||
// 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;
|
||||
itm->offset += 1.0f - speed;
|
||||
}
|
||||
|
||||
// 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 (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;
|
||||
} else {
|
||||
// both slots full → wait at end
|
||||
itm->offset = epsilon;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
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 (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;
|
||||
} else {
|
||||
// both slots full → wait at end
|
||||
itm->offset = epsilon;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
const UpdateTileCallback cb = ItemTileCallbacks[t->type];
|
||||
if (cb) {
|
||||
cb(t);
|
||||
}
|
||||
}
|
||||
const UpdateTileCallback cb = ItemTileCallbacks[t->type];
|
||||
if (cb) {
|
||||
cb(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,8 +129,10 @@ void registerItem(char name[20], SDL_Renderer *renderer) {
|
||||
ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT],
|
||||
TILE_SIZE / 2, TILE_SIZE / 2);
|
||||
SDL_SetTextureBlendMode(ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], SDL_BLENDMODE_BLEND);
|
||||
ItemRegistry[itemRegistryIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT], renderer);
|
||||
ItemRegistry[itemRegistryIndex].atlasRectsOnBelt[ORIENT_LEFT] = allocate_16x16(ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], renderer);
|
||||
ItemRegistry[itemRegistryIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(
|
||||
ItemRegistry[itemRegistryIndex].texture[ORIENT_LEFT], renderer);
|
||||
ItemRegistry[itemRegistryIndex].atlasRectsOnBelt[ORIENT_LEFT] = allocate_16x16(
|
||||
ItemRegistry[itemRegistryIndex].textureOnBelt[ORIENT_LEFT], renderer);
|
||||
ItemRegistry[itemRegistryIndex].type = itemRegistryIndex;
|
||||
ItemRegistry[itemRegistryIndex].isTile = false;
|
||||
ItemRegistry[itemRegistryIndex].miscVal = 60;
|
||||
@@ -294,7 +296,9 @@ void renderItem(ItemOnBelt item, SDL_Renderer *renderer, int lane, SDL_Rect play
|
||||
adjustRect(&rectA, playerRect);
|
||||
SDL_RenderFillRect(renderer, &rectA);
|
||||
}
|
||||
SDL_RenderCopy(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &rect);
|
||||
//SDL_RenderCopyx(renderer, ItemRegistry[item.type].textureOnBelt[ORIENT_LEFT], NULL, &rect);
|
||||
SDL_RenderCopy(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], &rect);
|
||||
|
||||
if (debugMode) {
|
||||
renderText(renderer, fonts[3], tempStr, rectA.x, rectA.y);
|
||||
}
|
||||
|
84
main.c
84
main.c
@@ -14,7 +14,8 @@ typedef struct GameState {
|
||||
Player player;
|
||||
Tile tileMap[MAP_HEIGHT][MAP_WIDTH];
|
||||
BackgroundTile backgroundTileMap[MAP_HEIGHT][MAP_WIDTH];
|
||||
SynthVoice voices[NUM_SYNTH_VOICES];
|
||||
AudioData audioData;
|
||||
TileArray neededUpdates;
|
||||
} GameState;
|
||||
GameState gameState;
|
||||
|
||||
@@ -34,7 +35,10 @@ int loadGameState(char *filename, Player *plr) {
|
||||
memcpy(plr, &gameState.player, sizeof(gameState.player));
|
||||
memcpy(tileMap, gameState.tileMap, sizeof(tileMap));
|
||||
memcpy(backgroundMap, gameState.backgroundTileMap, sizeof(backgroundMap));
|
||||
memcpy(audioData.synthVoices, gameState.voices, sizeof(gameState.voices));
|
||||
SDL_Rect *tmp = audioData.playerRect;
|
||||
memcpy(&audioData, &gameState.audioData, sizeof(gameState.audioData));
|
||||
audioData.playerRect = tmp;
|
||||
memcpy(&neededUpdates, &gameState.neededUpdates, sizeof(gameState.neededUpdates));
|
||||
plr->cursor.targetTile = NULL;
|
||||
plr->cursor.prevTargetTile = NULL;
|
||||
return 0;
|
||||
@@ -46,7 +50,8 @@ void saveGameState(char *filename, Player *plr) {
|
||||
memcpy(&gameState.player, plr, sizeof(gameState.player));
|
||||
memcpy(gameState.tileMap, tileMap, sizeof(gameState.tileMap));
|
||||
memcpy(gameState.backgroundTileMap, backgroundMap, sizeof(gameState.backgroundTileMap));
|
||||
memcpy(gameState.voices, audioData.synthVoices, sizeof(gameState.voices));
|
||||
memcpy(&gameState.audioData, &audioData, sizeof(gameState.audioData));
|
||||
memcpy(&gameState.neededUpdates, &neededUpdates, sizeof(neededUpdates));
|
||||
|
||||
FILE *gameSave = fopen(filename, "wb");
|
||||
if (!gameSave) {
|
||||
@@ -98,9 +103,11 @@ int init() {
|
||||
srand(time(NULL));
|
||||
|
||||
memset(tileMap, 0, sizeof(tileMap));
|
||||
memset(&neededUpdates, 0, sizeof(neededUpdates));
|
||||
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, NULL);
|
||||
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1");
|
||||
SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1");
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
|
||||
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1");
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
|
||||
@@ -161,13 +168,16 @@ int init() {
|
||||
spec.callback = audio_callback;
|
||||
spec.userdata = &audioData;
|
||||
|
||||
// SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
|
||||
// if (dev == 0) {
|
||||
// printf("Failed to open audio: %s\n", SDL_GetError());
|
||||
// SDL_Quit();
|
||||
// }
|
||||
//
|
||||
// SDL_PauseAudioDevice(dev, 1);
|
||||
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
|
||||
if (dev == 0) {
|
||||
printf("Failed to open audio: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
load_midi_file("assets/audio/testaid.mid");
|
||||
|
||||
SDL_PauseAudioDevice(dev, 0);
|
||||
|
||||
|
||||
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(mainRenderer);
|
||||
@@ -203,19 +213,23 @@ int render() {
|
||||
SDL_Rect rect2;
|
||||
rect2.x = 0;
|
||||
rect2.y = 0;
|
||||
rect2.w = 0;
|
||||
rect2.h = 0;
|
||||
rect2.w = ATLAS_SIZE;
|
||||
rect2.h = ATLAS_SIZE;
|
||||
|
||||
renderAllTiles(mainRenderer, player.rect);
|
||||
renderPlayer(&player);
|
||||
|
||||
// SDL_RenderCopy(mainRenderer, backgroundTexture, &screenRect, &screenRect);
|
||||
// SDL_RenderCopy(mainRenderer, tilesTexture, &screenRect, &screenRect);
|
||||
// SDL_RenderCopy(mainRenderer, itemsTexture, &screenRect, &screenRect);
|
||||
// SDL_RenderCopy(mainRenderer, entityTexture, &screenRect, &screenRect);
|
||||
// SDL_RenderCopy(mainRenderer, hudTexture, &screenRect, &screenRect);
|
||||
SDL_QueryTexture(atlasTexture, NULL, NULL, &rect2.w, &rect2.h);
|
||||
SDL_RenderCopy(mainRenderer, atlasTexture, &rect2, &rect2);
|
||||
|
||||
if (!renderAtlas) {
|
||||
SDL_RenderCopy(mainRenderer, backgroundTexture, &screenRect, &screenRect);
|
||||
SDL_RenderCopy(mainRenderer, tilesTexture, &screenRect, &screenRect);
|
||||
SDL_RenderCopy(mainRenderer, itemsTexture, &screenRect, &screenRect);
|
||||
SDL_RenderCopy(mainRenderer, entityTexture, &screenRect, &screenRect);
|
||||
SDL_RenderCopy(mainRenderer, hudTexture, &screenRect, &screenRect);
|
||||
|
||||
} else {
|
||||
SDL_RenderCopy(mainRenderer, atlasTexture, &rect2, &rect2);
|
||||
}
|
||||
|
||||
|
||||
SDL_RenderPresent(mainRenderer);
|
||||
@@ -246,11 +260,12 @@ int processEvent(SDL_Event e) {
|
||||
speed = speed == 0 ? 0.004f : 0;
|
||||
break;
|
||||
case SDLK_r:
|
||||
if (player.cursor.canReach && player.cursor.targetTile->type != TYPE_AIR) {
|
||||
if (player.inventory.activeSlotIndex == 0 && player.cursor.canReach &&
|
||||
player.cursor.targetTile->type != TYPE_AIR) {
|
||||
player.cursor.direction = player.cursor.targetTile->direction;
|
||||
}
|
||||
player.cursor.direction = (player.cursor.direction + 2) % ORIENT_DIRECTION_COUNT;
|
||||
if (player.cursor.canReach) {
|
||||
if (player.inventory.activeSlotIndex == 0 && player.cursor.canReach) {
|
||||
player.cursor.targetTile->direction = player.cursor.direction;
|
||||
}
|
||||
break;
|
||||
@@ -260,6 +275,9 @@ int processEvent(SDL_Event e) {
|
||||
case SDLK_F3:
|
||||
debugMode = !debugMode;
|
||||
break;
|
||||
case SDLK_F10:
|
||||
renderAtlas = !renderAtlas;
|
||||
break;
|
||||
case SDLK_F4:
|
||||
Tile *tile = &tileMap[playerTileY][playerTileX];
|
||||
break;
|
||||
@@ -306,6 +324,12 @@ void processMousePosition() {
|
||||
if (player.inventory.slotCounts[player.inventory.activeSlotIndex] > 0) {
|
||||
player.inventory.slotCounts[player.inventory.activeSlotIndex]--;
|
||||
player.cursor.targetTile->type = player.inventory.activeSlotIndex;
|
||||
player.cursor.targetTile->rect.x = player.cursor.tileX;
|
||||
player.cursor.targetTile->rect.y = player.cursor.tileY;
|
||||
if (TileRegistry[player.inventory.activeSlotIndex].needsTicks) {
|
||||
player.cursor.targetTile->neededUpdateIndex = add_tile(&neededUpdates,
|
||||
player.cursor.targetTile->rect);
|
||||
}
|
||||
player.cursor.targetTile->direction = player.cursor.direction;
|
||||
}
|
||||
} else if (player.cursor.targetTile->type == player.inventory.activeSlotIndex) {
|
||||
@@ -329,6 +353,12 @@ void processMousePosition() {
|
||||
player.cursor.targetTile->items[lane].type = 0;
|
||||
}
|
||||
}
|
||||
int neededIndex = player.cursor.targetTile->neededUpdateIndex;
|
||||
if (TileRegistry[player.cursor.targetTile->type].needsTicks &&
|
||||
neededUpdates.tiles[neededIndex].x == player.cursor.targetTile->rect.x &&
|
||||
neededUpdates.tiles[neededIndex].y == player.cursor.targetTile->rect.y) {
|
||||
remove_tile(&neededUpdates, neededIndex);
|
||||
}
|
||||
player.cursor.targetTile->type = TYPE_AIR;
|
||||
player.cursor.breakingProgress = 0;
|
||||
} else {
|
||||
@@ -461,8 +491,10 @@ void processKeyboardHeld() {
|
||||
}
|
||||
|
||||
if (keyboardState[SDL_SCANCODE_Q]) {
|
||||
if (player.cursor.targetTile->type > 0) {
|
||||
if (player.cursor.targetTile->type > 0 && player.inventory.activeSlotIndex == 0) {
|
||||
setActivePlayerSlot(&player, player.cursor.targetTile->type);
|
||||
} else {
|
||||
setActivePlayerSlot(&player, 0);
|
||||
}
|
||||
}
|
||||
if (keyboardState[SDL_SCANCODE_Y]) {
|
||||
@@ -519,6 +551,14 @@ int main(__attribute__((unused)) int argc, __attribute__((unused)) char *args[])
|
||||
genInitMap();
|
||||
}
|
||||
|
||||
// audioData.synthVoices[0].frequency = 1000;
|
||||
// audioData.synthVoices[0].phase = 0;
|
||||
// audioData.synthVoices[0].sourceRect.w = TILE_SIZE;
|
||||
// audioData.synthVoices[0].sourceRect.h = TILE_SIZE;
|
||||
// audioData.synthVoices[0].sourceRect.x = 100 * TILE_SIZE;
|
||||
// audioData.synthVoices[0].sourceRect.y = 100 * TILE_SIZE;
|
||||
// audioData.synthVoices[0].volume = 255;
|
||||
// audioData.synthVoices[0].waveform = WAVE_SINE;
|
||||
|
||||
//Hack to get window to stay up
|
||||
SDL_Event e;
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "player.h"
|
||||
#include "../tiles/tile.h"
|
||||
#include "../util/font.h"
|
||||
#include "../util/atlas.h"
|
||||
|
||||
#define HEALTH_MARGIN 4
|
||||
|
||||
@@ -18,6 +19,7 @@ SDL_Texture *hudTexture;
|
||||
|
||||
SDL_Texture *PlayerTexture;
|
||||
SDL_Rect PlayerRect;
|
||||
SDL_Rect playerTextureRect;
|
||||
|
||||
SDL_Rect targetItemBGRect;
|
||||
|
||||
@@ -28,9 +30,6 @@ SDL_Color breakingBarColor = {128, 128, 0, 255};
|
||||
|
||||
void setActivePlayerSlot(Player *plr, ItemType activeSlotIndex) {
|
||||
activeSlotIndex = activeSlotIndex % itemRegistryIndex;
|
||||
if (activeSlotIndex <= 0) {
|
||||
activeSlotIndex = 1;
|
||||
}
|
||||
plr->inventory.activeSlotIndex = activeSlotIndex;
|
||||
}
|
||||
|
||||
@@ -46,11 +45,11 @@ void moveActivePlayerSlot(Player *plr, bool up, bool seek) {
|
||||
|
||||
// Stop if we found a slot with count > 0
|
||||
if (!strlen(ItemRegistry[newSlot].name)) continue;
|
||||
if (newSlot == 0) continue;
|
||||
if (newSlot == 0) break;
|
||||
if (seek) {
|
||||
if (plr->inventory.slotCounts[newSlot] > 0) break;
|
||||
} else {
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
@@ -104,6 +103,7 @@ void initPlayer(Player *plr) {
|
||||
SDL_QueryTexture(PlayerTexture, NULL, NULL, &PlayerRect.w, &PlayerRect.h);
|
||||
PlayerRect.x = (DISPLAY_WIDTH / 2) - (PlayerRect.w / 2);
|
||||
PlayerRect.y = (DISPLAY_HEIGHT / 2) - (PlayerRect.h / 2);
|
||||
playerTextureRect = allocate_32x32(PlayerTexture, mainRenderer);
|
||||
plr->health = 64;
|
||||
plr->healthIdle = 0;
|
||||
|
||||
@@ -112,12 +112,12 @@ void initPlayer(Player *plr) {
|
||||
plr->rect.w = TILE_SIZE;
|
||||
plr->rect.h = TILE_SIZE;
|
||||
|
||||
for (ItemType ui = 0; ui < tileTypeIndex; ui++) {
|
||||
plr->inventory.slotCounts[ui] = 64;
|
||||
for (ItemType ui = 1; ui < tileTypeIndex; ui++) {
|
||||
plr->inventory.slotCounts[ui] = 65535;
|
||||
}
|
||||
|
||||
for (ItemType ui = ITEMREGISTRY_SIZE / 2; ui < itemRegistryIndex; ui++) {
|
||||
plr->inventory.slotCounts[ui] = 64;
|
||||
plr->inventory.slotCounts[ui] = 65535;
|
||||
}
|
||||
|
||||
hudTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, screenRect.w,
|
||||
@@ -135,12 +135,12 @@ void initPlayer(Player *plr) {
|
||||
plr->cursor.targetTileRect.h = TILE_SIZE;
|
||||
|
||||
targetItemBGRect.w = DISPLAY_WIDTH;
|
||||
targetItemBGRect.h = TILE_SIZE;
|
||||
targetItemBGRect.h = TILE_SIZE + fonts[2].size * 2;
|
||||
targetItemBGRect.x = 0;
|
||||
targetItemBGRect.y = DISPLAY_HEIGHT - 32;
|
||||
targetItemBGRect.y = DISPLAY_HEIGHT - TILE_SIZE - fonts[2].size * 2;
|
||||
|
||||
targetItemRect.w = TILE_SIZE / 2;
|
||||
targetItemRect.h = TILE_SIZE / 2;
|
||||
targetItemRect.w = TILE_SIZE;
|
||||
targetItemRect.h = TILE_SIZE;
|
||||
|
||||
plr->cursor.heldItemRect.w = TILE_SIZE;
|
||||
plr->cursor.heldItemRect.h = TILE_SIZE;
|
||||
@@ -164,7 +164,8 @@ void renderPlayer(Player *plr) {
|
||||
|
||||
SDL_SetRenderTarget(mainRenderer, entityTexture);
|
||||
SDL_RenderClear(mainRenderer);
|
||||
SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect);
|
||||
SDL_RenderCopy(mainRenderer, atlasTexture, &playerTextureRect, &PlayerRect);
|
||||
//SDL_RenderCopy(mainRenderer, PlayerTexture, NULL, &PlayerRect);
|
||||
SDL_SetRenderTarget(mainRenderer, hudTexture);
|
||||
SDL_RenderClear(mainRenderer);
|
||||
|
||||
@@ -172,29 +173,31 @@ void renderPlayer(Player *plr) {
|
||||
DrawThickRect(mainRenderer, plr->cursor.targetTileRect, 4);
|
||||
|
||||
ItemType itemIndex = plr->inventory.activeSlotIndex;
|
||||
SDL_Texture *itemTex;
|
||||
//SDL_Texture *itemTex;
|
||||
char itemStringCount[6];
|
||||
if (itemIndex < itemRegistryIndex) {
|
||||
if (itemIndex < itemRegistryIndex && itemIndex > 0) {
|
||||
plr->cursor.heldItemRect.x = plr->cursor.windowX;
|
||||
plr->cursor.heldItemRect.y = plr->cursor.windowY;
|
||||
itemTex = ItemRegistry[itemIndex].textureOnBelt[plr->cursor.direction];
|
||||
if (itemTex == NULL) {
|
||||
itemTex = ItemRegistry[itemIndex].textureOnBelt[ORIENT_LEFT];
|
||||
//itemTex = ItemRegistry[itemIndex].textureOnBelt[plr->cursor.direction];
|
||||
SDL_Rect itemAtlasRect = ItemRegistry[itemIndex].atlasRects[plr->cursor.direction];
|
||||
if (itemAtlasRect.w == 0 || itemAtlasRect.h == 0) {
|
||||
itemAtlasRect = ItemRegistry[itemIndex].atlasRects[ORIENT_LEFT];
|
||||
}
|
||||
if (itemTex != NULL) {
|
||||
if (itemAtlasRect.w != 0 && itemAtlasRect.h != 0) {
|
||||
|
||||
if (plr->inventory.slotCounts[itemIndex] <= 0) {
|
||||
// Set a red tint (255, 0, 0)
|
||||
SDL_SetTextureColorMod(itemTex, 128, 128, 255);
|
||||
|
||||
SDL_SetTextureAlphaMod(itemTex, 192);
|
||||
} else {
|
||||
SDL_SetTextureColorMod(itemTex, 255, 255, 255);
|
||||
|
||||
SDL_SetTextureAlphaMod(itemTex, 255);
|
||||
}
|
||||
SDL_RenderCopy(mainRenderer, itemTex, NULL,
|
||||
&plr->cursor.heldItemRect);
|
||||
// if (plr->inventory.slotCounts[itemIndex] <= 0) {
|
||||
// // Set a red tint (255, 0, 0)
|
||||
// SDL_SetTextureColorMod(itemTex, 128, 128, 255);
|
||||
//
|
||||
// SDL_SetTextureAlphaMod(itemTex, 192);
|
||||
// } else {
|
||||
// SDL_SetTextureColorMod(itemTex, 255, 255, 255);
|
||||
//
|
||||
// SDL_SetTextureAlphaMod(itemTex, 255);
|
||||
// }
|
||||
// SDL_RenderCopy(mainRenderer, itemTex, NULL,
|
||||
// &plr->cursor.heldItemRect);
|
||||
SDL_RenderCopy(mainRenderer, atlasTexture, &itemAtlasRect, &plr->cursor.heldItemRect);
|
||||
renderText(mainRenderer, fonts[2], ItemRegistry[itemIndex].name, plr->cursor.heldItemRect.x,
|
||||
plr->cursor.heldItemRect.y - (fonts[2].size * 3 / 2));
|
||||
}
|
||||
@@ -215,21 +218,22 @@ void renderPlayer(Player *plr) {
|
||||
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
|
||||
SDL_RenderFillRect(mainRenderer, &targetItemBGRect);
|
||||
|
||||
targetItemRect.y = DISPLAY_HEIGHT - 30 + TILE_SIZE / 4 + 3;
|
||||
targetItemRect.y = DISPLAY_HEIGHT - TILE_SIZE;
|
||||
targetItemRect.x = TILE_SIZE / 4;
|
||||
|
||||
for (ItemType i = 1; i < ITEMREGISTRY_SIZE; i++) {
|
||||
itemTex = ItemRegistry[i].textureOnBelt[plr->cursor.direction];
|
||||
if (itemTex == NULL) {
|
||||
itemTex = ItemRegistry[i].textureOnBelt[ORIENT_LEFT];
|
||||
SDL_SetTextureBlendMode(atlasTexture, SDL_BLENDMODE_ADD);
|
||||
for (ItemType i = 0; i < ITEMREGISTRY_SIZE; i++) {
|
||||
SDL_Rect itemAtlasRectd = ItemRegistry[i].atlasRects[plr->cursor.direction];
|
||||
if (itemAtlasRectd.w == 0 || itemAtlasRectd.h == 0) {
|
||||
itemAtlasRectd = ItemRegistry[i].atlasRects[ORIENT_LEFT];
|
||||
}
|
||||
if (itemTex != NULL) {
|
||||
if (plr->inventory.slotCounts[i] <= 0) {
|
||||
// Set a red tint (255, 0, 0)
|
||||
SDL_SetTextureColorMod(itemTex, 128, 128, 255);
|
||||
}
|
||||
SDL_RenderCopy(mainRenderer, itemTex, NULL, &targetItemRect);
|
||||
SDL_SetTextureColorMod(itemTex, 255, 255, 255);
|
||||
if (itemAtlasRectd.w != 0 && itemAtlasRectd.h != 0) {
|
||||
// if (plr->inventory.slotCounts[i] <= 0) {
|
||||
// // Set a red tint (255, 0, 0)
|
||||
// SDL_SetTextureColorMod(itemTex, 128, 128, 255);
|
||||
// }
|
||||
SDL_RenderCopy(mainRenderer, atlasTexture, &itemAtlasRectd, &targetItemRect);
|
||||
//SDL_SetTextureColorMod(itemTex, 255, 255, 255);
|
||||
|
||||
if (plr->inventory.activeSlotIndex == i) {
|
||||
SDL_SetRenderDrawColor(mainRenderer, 16, plr->inventory.slotCounts[i] > 0 ? 128 : 16,
|
||||
@@ -240,9 +244,10 @@ void renderPlayer(Player *plr) {
|
||||
renderText(mainRenderer, fonts[2], itemStringCount, targetItemRect.x - 3,
|
||||
targetItemRect.y - (fonts[2].size * 3 / 2));
|
||||
//targetItemRect.x += (TILE_SIZE / 2) + (TILE_SIZE / 4);
|
||||
targetItemRect.x += TILE_SIZE;
|
||||
targetItemRect.x += TILE_SIZE / 2 * 3;
|
||||
}
|
||||
}
|
||||
SDL_SetTextureBlendMode(atlasTexture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
SDL_SetRenderTarget(mainRenderer, originalTarget);
|
||||
}
|
10
tiles/belt.c
10
tiles/belt.c
@@ -8,6 +8,7 @@
|
||||
#include "tile.h"
|
||||
#include "../player/player.h"
|
||||
#include "../items/item.h"
|
||||
#include "../util/atlas.h"
|
||||
|
||||
|
||||
void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect playerRect, SDL_Renderer *renderer) {
|
||||
@@ -36,8 +37,10 @@ void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect player
|
||||
adjustRect(&dst1, playerRect);
|
||||
adjustRect(&dst2, playerRect);
|
||||
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1);
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2);
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1); //TODO CONVERT TO ATLAS
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2); //TODO CONVERT TO ATLAS
|
||||
// SDL_RenderCopyx(renderer, atlasTexture, &TileRegistry[tileType].atlasRects[dir], NULL);
|
||||
// SDL_RenderCopyx(renderer, atlasTexture, &TileRegistry[tileType].atlasRects[dir], NULL);
|
||||
} else {
|
||||
int offset = scrollFrame % TILE_SIZE;
|
||||
|
||||
@@ -56,6 +59,9 @@ void renderBelt(int x, int y, int w, int h, OrientDirection dir, SDL_Rect player
|
||||
|
||||
|
||||
// Rotate to make the belt vertical
|
||||
// SDL_RenderCopyx(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], NULL);
|
||||
// SDL_RenderCopyx(renderer, atlasTexture, &ItemRegistry[item.type].atlasRectsOnBelt[ORIENT_LEFT], NULL);
|
||||
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src1, &dst1);
|
||||
SDL_RenderCopy(renderer, TileRegistry[tileType].textures[dir], &src2, &dst2);
|
||||
}
|
||||
|
@@ -27,16 +27,19 @@ void updateFurnace(Tile *tile) {
|
||||
if (tile->miscVal == 0) {
|
||||
tile->audioCh = getAvailableChannel();
|
||||
if (tile->audioCh < NUM_SYNTH_VOICES) {
|
||||
audioData.synthVoices[tile->audioCh].volume = 1;
|
||||
audioData.synthVoices[tile->audioCh].volume = 255;
|
||||
audioData.synthVoices[tile->audioCh].phase = 0;
|
||||
audioData.synthVoices[tile->audioCh].sourceRect.x = TILE_SIZE * tile->x;
|
||||
audioData.synthVoices[tile->audioCh].sourceRect.y = TILE_SIZE * tile->y;
|
||||
audioData.synthVoices[tile->audioCh].waveform = WAVE_TRIANGLE;
|
||||
audioData.synthVoices[tile->audioCh].frequency = 99;
|
||||
audioData.synthVoices[tile->audioCh].sourceRect.x = TILE_SIZE * tile->rect.x;
|
||||
audioData.synthVoices[tile->audioCh].sourceRect.y = TILE_SIZE * tile->rect.y;
|
||||
audioData.synthVoices[tile->audioCh].waveform = WAVE_SINE;
|
||||
audioData.synthVoices[tile->audioCh].frequency = 200;
|
||||
}
|
||||
}
|
||||
if (tile->audioCh < NUM_SYNTH_VOICES) {
|
||||
audioData.synthVoices[tile->audioCh].frequency++;
|
||||
printf("frq: %d\n", ++audioData.synthVoices[tile->audioCh].frequency);
|
||||
}
|
||||
if (audioData.synthVoices[tile->audioCh].volume < 255) {
|
||||
audioData.synthVoices[tile->audioCh].volume++;
|
||||
}
|
||||
if (outItem->type == 0 && ++tile->miscVal >= targetOutItem.miscVal) {
|
||||
if (tile->audioCh < NUM_SYNTH_VOICES) {
|
||||
|
76
tiles/tile.c
76
tiles/tile.c
@@ -2,15 +2,31 @@
|
||||
// Created by bruno on 4/24/25.
|
||||
//
|
||||
|
||||
#include <dirent.h>
|
||||
#include "tile.h"
|
||||
#include "../player/player.h"
|
||||
#include "furnace.h"
|
||||
#include "../util/atlas.h"
|
||||
#include "../util/font.h"
|
||||
|
||||
int scrollFrame = 0;
|
||||
unsigned long beltFrames = 0;
|
||||
|
||||
TileArray neededUpdates;
|
||||
|
||||
int add_tile(TileArray *arr, MiniRect t) {
|
||||
if (arr->activeCount >= MAX_TILES) return 0;
|
||||
arr->tiles[arr->activeCount] = t;
|
||||
arr->activeCount++;
|
||||
return arr->activeCount - 1;
|
||||
}
|
||||
|
||||
void remove_tile(TileArray *arr, int index) {
|
||||
if (index < 0 || index >= arr->activeCount) return;
|
||||
arr->activeCount--;
|
||||
arr->tiles[index] = arr->tiles[arr->activeCount]; // swap with last active
|
||||
}
|
||||
|
||||
|
||||
SDL_Texture *backgroundTexture;
|
||||
SDL_Texture *tilesTexture;
|
||||
SDL_Texture *itemsTexture;
|
||||
@@ -30,8 +46,8 @@ void generateTestMap() {
|
||||
for (int y = 0; y < DISPLAY_MAP_HEIGHT; y++) {
|
||||
for (int x = 0; x < DISPLAY_MAP_WIDTH; x++) {
|
||||
Tile tile = {0};
|
||||
tile.x = x;
|
||||
tile.y = y;
|
||||
tile.rect.x = x;
|
||||
tile.rect.y = y;
|
||||
tileMap[y][x] = tile;
|
||||
}
|
||||
}
|
||||
@@ -66,21 +82,19 @@ void registerTile(char name[20], SDL_Renderer *renderer) {
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT] = createFlippedTexture(renderer, texture, SDL_FLIP_HORIZONTAL);
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_UP] = createRotatedTexture(renderer, texture, 90);
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_DOWN] = createRotatedTexture(renderer, texture, 270);
|
||||
if (tileTypeIndex == 0) {
|
||||
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_LEFT], 64);
|
||||
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_RIGHT], 64);
|
||||
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_UP], 64);
|
||||
SDL_SetTextureAlphaMod(TileRegistry[0].textures[ORIENT_DOWN], 64);
|
||||
}
|
||||
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_UP], SDL_BLENDMODE_BLEND);
|
||||
SDL_SetTextureBlendMode(TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], SDL_BLENDMODE_BLEND);
|
||||
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_RIGHT] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_UP] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_UP], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_DOWN] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_LEFT] = allocate_32x32(
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_LEFT], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_RIGHT] = allocate_32x32(
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_RIGHT], renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_UP] = allocate_32x32(TileRegistry[tileTypeIndex].textures[ORIENT_UP],
|
||||
renderer);
|
||||
TileRegistry[tileTypeIndex].atlasRects[ORIENT_DOWN] = allocate_32x32(
|
||||
TileRegistry[tileTypeIndex].textures[ORIENT_DOWN], renderer);
|
||||
|
||||
TileRegistry[tileTypeIndex].type = tileTypeIndex;
|
||||
TileRegistry[tileTypeIndex].breakTime = 15;
|
||||
@@ -96,7 +110,8 @@ void registerBackgroundTile(char name[20], SDL_Renderer *renderer) {
|
||||
SDL_Texture *texture = IMG_LoadTexture(renderer, texturePath);
|
||||
BackgroundTileRegistry[backgroundTileTypeIndex].texture = texture;
|
||||
SDL_SetTextureBlendMode(BackgroundTileRegistry[backgroundTileTypeIndex].texture, SDL_BLENDMODE_NONE);
|
||||
BackgroundTileRegistry[backgroundTileTypeIndex].atlasRect = allocate_32x32(BackgroundTileRegistry[backgroundTileTypeIndex].texture, renderer);
|
||||
BackgroundTileRegistry[backgroundTileTypeIndex].atlasRect = allocate_32x32(
|
||||
BackgroundTileRegistry[backgroundTileTypeIndex].texture, renderer);
|
||||
|
||||
BackgroundTileRegistry[backgroundTileTypeIndex].type = backgroundTileTypeIndex;
|
||||
|
||||
@@ -128,6 +143,8 @@ void setupTiles() {
|
||||
}
|
||||
}
|
||||
TileRegistry[TYPE_FURNACE].outputLane[FURNACE_OUTPUT_SLOT] = 1;
|
||||
TileRegistry[TYPE_FURNACE].needsTicks = true;
|
||||
TileRegistry[TYPE_BELT].needsTicks = true;
|
||||
}
|
||||
|
||||
uint16_t getBreakTime(int type) {
|
||||
@@ -186,8 +203,10 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
|
||||
|
||||
BackgroundTile bt = backgroundMap[y][x];
|
||||
SDL_Texture *tex = BackgroundTileRegistry[bt.type].texture;
|
||||
if (tex != NULL) {
|
||||
SDL_RenderCopy(renderer, tex, NULL, &dstRect);
|
||||
SDL_Rect atlRect = BackgroundTileRegistry[bt.type].atlasRect;
|
||||
if (atlRect.w != 0 && atlRect.h != 0) {
|
||||
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect);
|
||||
//SDL_RenderCopy(renderer, tex, NULL, &dstRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,14 +228,21 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
|
||||
|
||||
Tile t = tileMap[y][x];
|
||||
switch (t.type) {
|
||||
case TYPE_AIR:
|
||||
break;
|
||||
case TYPE_BELT:
|
||||
renderBelt(x, y, tileSize, tileSize, t.direction, playerRect, renderer);
|
||||
break;
|
||||
default: {
|
||||
SDL_Rect atlRect = TileRegistry[t.type].atlasRects[t.direction];
|
||||
SDL_Texture *tex = TileRegistry[t.type].textures[t.direction];
|
||||
if (tex == NULL) tex = TileRegistry[t.type].textures[ORIENT_LEFT];
|
||||
if (tex != NULL) {
|
||||
SDL_RenderCopy(renderer, tex, NULL, &dstRect);
|
||||
if (atlRect.w == 0 || atlRect.h == 0) {
|
||||
tex = TileRegistry[t.type].textures[ORIENT_LEFT];
|
||||
atlRect = TileRegistry[t.type].atlasRects[ORIENT_LEFT];
|
||||
}
|
||||
if (atlRect.w != 0 && atlRect.h != 0) {
|
||||
//SDL_RenderCopy(renderer, tex, NULL, &dstRect);
|
||||
SDL_RenderCopy(renderer, atlasTexture, &atlRect, &dstRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,6 +257,18 @@ void renderAllTiles(SDL_Renderer *renderer, SDL_Rect playerRect) {
|
||||
if (x < 0 || x >= MAP_WIDTH) continue;
|
||||
|
||||
Tile t = tileMap[y][x];
|
||||
if (debugMode) {
|
||||
char locChar[20];
|
||||
sprintf(locChar, "X:%d\nY:%d", x, y);
|
||||
SDL_Rect dstRect = {
|
||||
.x = x * TILE_SIZE,
|
||||
.y = y * TILE_SIZE,
|
||||
.w = TILE_SIZE,
|
||||
.h = TILE_SIZE
|
||||
};
|
||||
adjustRect(&dstRect, playerRect);
|
||||
renderText(renderer, fonts[3], locChar, dstRect.x, dstRect.y);
|
||||
}
|
||||
if (t.type == TYPE_BELT || itemViewing) {
|
||||
for (uint8_t lane = 0; lane < ItemSlotCount; lane++) {
|
||||
if (t.items[lane].type != 0) {
|
||||
|
29
tiles/tile.h
29
tiles/tile.h
@@ -7,10 +7,9 @@
|
||||
|
||||
#include "../util/util.h"
|
||||
#include "../items/item.h"
|
||||
//#include "../items/item.h"
|
||||
|
||||
#define MAP_WIDTH 100
|
||||
#define MAP_HEIGHT 100
|
||||
#define MAP_WIDTH 1000
|
||||
#define MAP_HEIGHT 1000
|
||||
|
||||
#define DISPLAY_MAP_WIDTH 60
|
||||
#define DISPLAY_MAP_HEIGHT 31
|
||||
@@ -20,6 +19,25 @@
|
||||
#define DISPLAY_WIDTH DISPLAY_MAP_WIDTH * TILE_SIZE
|
||||
#define DISPLAY_HEIGHT DISPLAY_MAP_HEIGHT * TILE_SIZE
|
||||
|
||||
typedef struct MiniRect {
|
||||
int x;
|
||||
int y;
|
||||
} MiniRect;
|
||||
|
||||
#define MAX_TILES MAP_WIDTH * MAP_HEIGHT
|
||||
|
||||
|
||||
typedef struct {
|
||||
MiniRect tiles[MAX_TILES];
|
||||
int activeCount;
|
||||
} TileArray;
|
||||
|
||||
extern TileArray neededUpdates;
|
||||
|
||||
void remove_tile(TileArray *arr, int index);
|
||||
|
||||
int add_tile(TileArray *arr, MiniRect t);
|
||||
|
||||
extern SDL_Texture *tilesTexture;
|
||||
extern SDL_Texture *itemsTexture;
|
||||
extern SDL_Texture *backgroundTexture;
|
||||
@@ -77,6 +95,7 @@ typedef struct TileTypeReg {
|
||||
bool itemMoves;
|
||||
bool allowedInItems[ItemSlotCount][ITEMREGISTRY_SIZE];
|
||||
bool outputLane[ItemSlotCount];
|
||||
bool needsTicks;
|
||||
} TileTypeReg;
|
||||
|
||||
typedef struct BackgroundTileType {
|
||||
@@ -106,8 +125,8 @@ typedef struct Tile {
|
||||
int miscVal;
|
||||
ItemOnBelt items[ItemSlotCount];
|
||||
uint16_t audioCh;
|
||||
int x;
|
||||
int y;
|
||||
MiniRect rect;
|
||||
int neededUpdateIndex;
|
||||
} Tile;
|
||||
|
||||
|
||||
|
112
util/atlas.c
112
util/atlas.c
@@ -7,50 +7,97 @@
|
||||
|
||||
SDL_Texture *atlasTexture;
|
||||
|
||||
int atlasX = 0, atlasY = 0;
|
||||
int tileIndex16 = 0, quadrantIndex16 = 0;
|
||||
int tileIndex32 = 0;
|
||||
|
||||
int tileIndex = 0; // Which 32x32 tile we're on
|
||||
int quadrantIndex = 0; // Which 16x16 slot inside that tile
|
||||
#define MAX_RECTS 256
|
||||
int allocatedRectCount = 0;
|
||||
SDL_Rect allocatedRects[MAX_RECTS];
|
||||
|
||||
|
||||
bool isIntersecting(SDL_Rect a) {
|
||||
for (int i = 0; i < allocatedRectCount; i++) {
|
||||
SDL_Rect b = allocatedRects[i];
|
||||
if (SDL_HasIntersection(&a, &b)) {
|
||||
printf("Rect intersection %d - X:%d, Y: %d, W: %d, H: %d with X:%d, Y: %d, W: %d, H: %d\n",
|
||||
allocatedRectCount, a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void storeRect(SDL_Rect rect) {
|
||||
if (isIntersecting(rect)) {
|
||||
printf("PROBLEM\n");
|
||||
}
|
||||
if (allocatedRectCount < MAX_RECTS) {
|
||||
allocatedRects[allocatedRectCount++] = rect;
|
||||
} else {
|
||||
fprintf(stderr, "Error: atlas rect limit reached!\n");
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect allocate_16x16(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
SDL_Texture * oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
int tileX = tileIndex % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex / ATLAS_TILES_PER_ROW;
|
||||
|
||||
int dx = (quadrantIndex % 2) * QUADRANT_SIZE;
|
||||
int dy = (quadrantIndex / 2) * QUADRANT_SIZE;
|
||||
|
||||
SDL_Rect destRect = {
|
||||
tileX * TILE_SIZE + dx,
|
||||
tileY * TILE_SIZE + dy,
|
||||
SDL_Rect sourceRect = {
|
||||
0,
|
||||
0,
|
||||
QUADRANT_SIZE,
|
||||
QUADRANT_SIZE
|
||||
};
|
||||
SDL_RenderCopy(renderer, srcTexture, NULL, &destRect);
|
||||
|
||||
quadrantIndex++;
|
||||
if (quadrantIndex >= 4) {
|
||||
tileIndex++;
|
||||
quadrantIndex = 0;
|
||||
SDL_Rect destRect;
|
||||
while (1) {
|
||||
int tileX = tileIndex16 % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex16 / ATLAS_TILES_PER_ROW;
|
||||
|
||||
int dx = (quadrantIndex16 % 2) * QUADRANT_SIZE;
|
||||
int dy = (quadrantIndex16 / 2) * QUADRANT_SIZE;
|
||||
destRect.x = tileX * TILE_SIZE + dx;
|
||||
destRect.y = tileY * TILE_SIZE + dy;
|
||||
destRect.w = QUADRANT_SIZE;
|
||||
destRect.h = QUADRANT_SIZE;
|
||||
if (isIntersecting(destRect)) {
|
||||
tileIndex16 = tileIndex32;
|
||||
tileIndex32++;
|
||||
quadrantIndex16 = 0;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SDL_RenderCopy(renderer, srcTexture, &sourceRect, &destRect);
|
||||
|
||||
quadrantIndex16++;
|
||||
if (quadrantIndex16 >= 4) {
|
||||
tileIndex16 = tileIndex32;
|
||||
tileIndex32 += 1;
|
||||
quadrantIndex16 = 0;
|
||||
|
||||
// Ensure 32x32 allocator skips this tile
|
||||
if (tileIndex32 <= tileIndex16) {
|
||||
tileIndex32 = tileIndex16 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
storeRect(destRect);
|
||||
printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h);
|
||||
return destRect;
|
||||
}
|
||||
|
||||
SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
SDL_Texture * oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
// If we’re not at the start of a tile, skip to the next clean one
|
||||
if (quadrantIndex != 0) {
|
||||
tileIndex++;
|
||||
quadrantIndex = 0;
|
||||
}
|
||||
|
||||
int tileX = tileIndex % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex / ATLAS_TILES_PER_ROW;
|
||||
int tileX = tileIndex32 % ATLAS_TILES_PER_ROW;
|
||||
int tileY = tileIndex32 / ATLAS_TILES_PER_ROW;
|
||||
|
||||
SDL_Rect destRect = {
|
||||
tileX * TILE_SIZE,
|
||||
@@ -58,23 +105,30 @@ SDL_Rect allocate_32x32(SDL_Texture *srcTexture, SDL_Renderer *renderer) {
|
||||
TILE_SIZE,
|
||||
TILE_SIZE
|
||||
};
|
||||
|
||||
SDL_RenderCopy(renderer, srcTexture, NULL, &destRect);
|
||||
|
||||
tileIndex++; // Move to next tile
|
||||
// quadrantIndex stays 0 — new tile is fresh
|
||||
tileIndex32++;
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
storeRect(destRect);
|
||||
printf("Rect X:%d, Y: %d, W: %d, H: %d\n", destRect.x, destRect.y, destRect.w, destRect.h);
|
||||
return destRect;
|
||||
}
|
||||
|
||||
void initAtlas(SDL_Renderer *renderer) {
|
||||
|
||||
SDL_Texture *oldTarget = SDL_GetRenderTarget(renderer);
|
||||
|
||||
atlasTexture = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
|
||||
ATLAS_SIZE, ATLAS_SIZE);
|
||||
// Clear atlas with transparent
|
||||
SDL_SetRenderTarget(renderer, atlasTexture);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
SDL_SetRenderTarget(renderer, oldTarget);
|
||||
|
||||
|
||||
atlasTexture = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
|
||||
ATLAS_SIZE, ATLAS_SIZE);
|
||||
}
|
211
util/audio.c
211
util/audio.c
@@ -6,6 +6,11 @@
|
||||
|
||||
AudioData audioData;
|
||||
|
||||
#define MAX_MIDI_EVENTS 1024
|
||||
MidiEvent midiEvents[MAX_MIDI_EVENTS];
|
||||
int midiEventCount = 0;
|
||||
int nextMidiEvent = 0;
|
||||
|
||||
uint16_t getAvailableChannel() {
|
||||
for (uint16_t i = 0; i < NUM_SYNTH_VOICES; i++) {
|
||||
if (audioData.synthVoices[i].volume == 0) {
|
||||
@@ -39,93 +44,209 @@ static void compute_stereo_gains(float pan, float *outL, float *outR) {
|
||||
// e.g. *outL *= 0.7071f; *outR *= 0.7071f;
|
||||
}
|
||||
|
||||
// This callback now writes stereo frames: interleaved L/R floats.
|
||||
// Improved audio callback with anti-clipping and smooth fade-out
|
||||
void audio_callback(void *userdata, Uint8 *stream, int len) {
|
||||
AudioData *audio = (AudioData *) userdata;
|
||||
|
||||
// 'len' is total bytes; each sample‐frame is 2 floats (L+R), i.e. 2 * sizeof(float).
|
||||
int frames = len / (2 * sizeof(float));
|
||||
int frames = len / (2 * sizeof(float)); // Stereo frame count
|
||||
|
||||
float elapsedSec = audio->totalSamples / SAMPLE_RATE;
|
||||
audio->totalSamples += frames;
|
||||
|
||||
while (nextMidiEvent < midiEventCount &&
|
||||
midiEvents[nextMidiEvent].timeSec <= elapsedSec) {
|
||||
|
||||
MidiEvent *ev = &midiEvents[nextMidiEvent];
|
||||
|
||||
if (ev->type == 0 && ev->velocity > 0) {
|
||||
// Note On
|
||||
for (int i = NUM_SYNTH_VOICES - 4; i < NUM_SYNTH_VOICES; ++i) {
|
||||
SynthVoice *v = &audio->synthVoices[i];
|
||||
if (v->volume == 0) {
|
||||
float freq = 440.0f * powf(2.0f, (ev->note - 69) / 12.0f);
|
||||
v->frequency = (uint16_t) freq;
|
||||
v->volume = ev->velocity * 2;
|
||||
v->waveform = WAVE_SQUARE;
|
||||
v->smoothedAmp = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Note Off
|
||||
for (int i = NUM_SYNTH_VOICES - 4; i < NUM_SYNTH_VOICES; ++i) {
|
||||
SynthVoice *v = &audio->synthVoices[i];
|
||||
float freq = 440.0f * powf(2.0f, (ev->note - 69) / 12.0f);
|
||||
if ((uint16_t)freq == v->frequency) {
|
||||
v->volume = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextMidiEvent++;
|
||||
}
|
||||
|
||||
// Zero out the entire output buffer (silence)
|
||||
// We’ll accumulate into it.
|
||||
// Each float is 4 bytes, so total floats = 2 * frames.
|
||||
float *outBuf = (float *) stream;
|
||||
for (int i = 0; i < 2 * frames; ++i) {
|
||||
outBuf[i] = 0.0f;
|
||||
}
|
||||
|
||||
// Precompute the listener center
|
||||
float listenerCx = audio->playerRect->x + audio->playerRect->w * 0.5f;
|
||||
|
||||
// For each synth voice, mix into the stereo buffer
|
||||
int *voiceCounts = calloc(frames, sizeof(int));
|
||||
|
||||
for (int v = 0; v < NUM_SYNTH_VOICES; v++) {
|
||||
SynthVoice *voice = &audio->synthVoices[v];
|
||||
if (voice->volume == 0 || voice->frequency == 0) {
|
||||
continue; // skip silent or inactive voices
|
||||
}
|
||||
|
||||
// Compute source center X
|
||||
float sourceCx = voice->sourceRect.x + voice->sourceRect.w * 0.5f;
|
||||
if ((voice->volume == 0 && voice->smoothedAmp < 0.001f) || voice->frequency == 0)
|
||||
continue;
|
||||
|
||||
float sourceCx = voice->sourceRect.x + TILE_SIZE * 0.5f;
|
||||
float dx = sourceCx - listenerCx;
|
||||
|
||||
// Normalize for pan. If |dx| >= maxPanDistance → full left or full right.
|
||||
float pan = dx / audio->maxPanDistance;
|
||||
if (pan < -1.0f) pan = -1.0f;
|
||||
if (pan > +1.0f) pan = +1.0f;
|
||||
float pan = fmaxf(-1.0f, fminf(+1.0f, dx / audio->maxPanDistance));
|
||||
|
||||
float gainL, gainR;
|
||||
compute_stereo_gains(pan, &gainL, &gainR);
|
||||
gainL *= 0.7071f;
|
||||
gainR *= 0.7071f;
|
||||
|
||||
// Optional: You could also attenuate overall volume with distance
|
||||
// float dist = fabsf(dx);
|
||||
// float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f);
|
||||
// float finalVolume = (voice->volume / 255.0f) * distanceAtten;
|
||||
// But for now, we’ll just use voice->volume for amplitude.
|
||||
float dist = fabsf(dx);
|
||||
float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f);
|
||||
float targetAmp = (voice->volume / 255.0f) * distanceAtten;
|
||||
|
||||
float amp = (voice->volume / 255.0f);
|
||||
double phaseInc = ((double) voice->frequency * 256.0) / (double) SAMPLE_RATE;
|
||||
|
||||
// Phase increment per sample‐frame:
|
||||
// (freq * 256) / SAMPLE_RATE tells how many phase steps per mono-sample.
|
||||
// Because we’re writing stereo, we still advance phase once per frame.
|
||||
uint8_t phaseInc = (uint8_t)((voice->frequency * 256) / SAMPLE_RATE);
|
||||
|
||||
// Mix into each frame
|
||||
for (int i = 0; i < frames; i++) {
|
||||
float t = (float) voice->phase / 255.0f * 2.0f - 1.0f;
|
||||
voice->smoothedAmp += (targetAmp - voice->smoothedAmp) * SMOOTHING_FACTOR;
|
||||
float amp = voice->smoothedAmp;
|
||||
|
||||
double norm = voice->phase / 256.0;
|
||||
double t = norm * 2.0 - 1.0;
|
||||
float sample;
|
||||
|
||||
switch (voice->waveform) {
|
||||
default:
|
||||
case WAVE_SINE:
|
||||
sample = sinf(voice->phase * 2.0f * M_PI / 256.0f);
|
||||
break;
|
||||
case WAVE_SQUARE:
|
||||
sample = (t >= 0.0f) ? 1.0f : -1.0f;
|
||||
sample = (t >= 0.0) ? 1.0f : -1.0f;
|
||||
break;
|
||||
case WAVE_SAWTOOTH:
|
||||
sample = t;
|
||||
sample = (float) t;
|
||||
break;
|
||||
case WAVE_TRIANGLE:
|
||||
sample = (t < 0.0f) ? -t : t;
|
||||
sample = (float) ((t < 0.0) ? -t : t);
|
||||
break;
|
||||
case WAVE_NOISE:
|
||||
sample = ((float) rand() / RAND_MAX) * 2.0f - 1.0f;
|
||||
sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f;
|
||||
break;
|
||||
default:
|
||||
sample = (float) sin(norm * 2.0 * M_PI);
|
||||
break;
|
||||
}
|
||||
|
||||
voice->phase += phaseInc;
|
||||
if (voice->phase >= 256.0) voice->phase -= 256.0;
|
||||
else if (voice->phase < 0.0) voice->phase += 256.0;
|
||||
|
||||
// Interleaved index: left = 2*i, right = 2*i + 1
|
||||
int idxL = 2 * i;
|
||||
int idxR = 2 * i + 1;
|
||||
|
||||
// Accumulate into buffer
|
||||
outBuf[idxL] += sample * amp * gainL;
|
||||
outBuf[idxR] += sample * amp * gainR;
|
||||
voiceCounts[i]++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < frames; ++i) {
|
||||
int count = voiceCounts[i];
|
||||
if (count > 0) {
|
||||
outBuf[2 * i + 0] /= count;
|
||||
outBuf[2 * i + 1] /= count;
|
||||
}
|
||||
}
|
||||
free(voiceCounts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static uint32_t read_be_uint32(const uint8_t *data) {
|
||||
return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | data[3];
|
||||
}
|
||||
|
||||
static uint16_t read_be_uint16(const uint8_t *data) {
|
||||
return (data[0]<<8) | data[1];
|
||||
}
|
||||
|
||||
static uint32_t read_vlq(const uint8_t **ptr) {
|
||||
uint32_t value = 0;
|
||||
const uint8_t *p = *ptr;
|
||||
while (*p & 0x80) {
|
||||
value = (value << 7) | (*p++ & 0x7F);
|
||||
}
|
||||
value = (value << 7) | (*p++ & 0x7F);
|
||||
*ptr = p;
|
||||
return value;
|
||||
}
|
||||
|
||||
void load_midi_file(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
uint8_t *data = malloc(size);
|
||||
fread(data, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
const uint8_t *ptr = data;
|
||||
if (memcmp(ptr, "MThd", 4) != 0) return;
|
||||
ptr += 8; // skip header length
|
||||
uint16_t format = read_be_uint16(ptr); ptr += 2;
|
||||
uint16_t nTracks = read_be_uint16(ptr); ptr += 2;
|
||||
uint16_t ppqn = read_be_uint16(ptr); ptr += 2;
|
||||
|
||||
if (format != 0 || nTracks != 1) {
|
||||
printf("Only Type 0 MIDI supported\n");
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(ptr, "MTrk", 4) != 0) return;
|
||||
uint32_t trackLen = read_be_uint32(ptr+4);
|
||||
ptr += 8;
|
||||
const uint8_t *trackEnd = ptr + trackLen;
|
||||
|
||||
float curTime = 0.0f;
|
||||
uint32_t tempo = 500000; // default: 120 BPM
|
||||
uint8_t lastStatus = 0;
|
||||
|
||||
while (ptr < trackEnd && midiEventCount < MAX_MIDI_EVENTS) {
|
||||
uint32_t delta = read_vlq(&ptr);
|
||||
curTime += (delta * (tempo / 1000000.0f)) / ppqn;
|
||||
|
||||
uint8_t status = *ptr;
|
||||
if (status < 0x80) status = lastStatus;
|
||||
else ptr++;
|
||||
|
||||
lastStatus = status;
|
||||
|
||||
if (status == 0xFF) {
|
||||
uint8_t metaType = *ptr++;
|
||||
uint32_t len = read_vlq(&ptr);
|
||||
if (metaType == 0x51 && len == 3) {
|
||||
tempo = (ptr[0]<<16 | ptr[1]<<8 | ptr[2]);
|
||||
}
|
||||
ptr += len;
|
||||
} else if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80) {
|
||||
uint8_t note = *ptr++;
|
||||
uint8_t vel = *ptr++;
|
||||
midiEvents[midiEventCount++] = (MidiEvent){
|
||||
.timeSec = curTime,
|
||||
.type = (status & 0xF0) == 0x90 ? 0 : 1,
|
||||
.note = note,
|
||||
.velocity = vel
|
||||
};
|
||||
} else {
|
||||
ptr += 2; // skip unknown
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We did not normalize by active voices here, because each voice already
|
||||
// uses its own volume. If you still want an automatic “divide by N active voices”,
|
||||
// you would need to track active voices per‐frame, which is relatively expensive.
|
||||
// In practice, you manage the volume per voice so clipping doesn’t occur.
|
||||
free(data);
|
||||
}
|
17
util/audio.h
17
util/audio.h
@@ -9,9 +9,18 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../tiles/tile.h"
|
||||
|
||||
#define SAMPLE_RATE 44100
|
||||
#define NUM_SYNTH_VOICES 256
|
||||
#define SMOOTHING_FACTOR 0.001f
|
||||
|
||||
typedef struct {
|
||||
float timeSec; // When to trigger this event
|
||||
uint8_t type; // 0 = Note On, 1 = Note Off
|
||||
uint8_t note;
|
||||
uint8_t velocity;
|
||||
} MidiEvent;
|
||||
|
||||
typedef enum Waveform {
|
||||
WAVE_SINE,
|
||||
@@ -23,16 +32,18 @@ typedef enum Waveform {
|
||||
|
||||
typedef struct SynthVoice {
|
||||
Waveform waveform;
|
||||
uint8_t phase;
|
||||
double phase;
|
||||
uint16_t frequency;
|
||||
uint8_t volume;
|
||||
SDL_Rect sourceRect;
|
||||
MiniRect sourceRect;
|
||||
float smoothedAmp; // a float that holds the exponentially smoothed amplitude
|
||||
} SynthVoice;
|
||||
|
||||
typedef struct AudioData {
|
||||
SynthVoice synthVoices[NUM_SYNTH_VOICES];
|
||||
SDL_Rect *playerRect;
|
||||
float maxPanDistance;
|
||||
uint64_t totalSamples;
|
||||
} AudioData;
|
||||
|
||||
extern AudioData audioData;
|
||||
@@ -41,4 +52,6 @@ void audio_callback(void *userdata, Uint8 *stream, int len);
|
||||
|
||||
uint16_t getAvailableChannel();
|
||||
|
||||
void load_midi_file(const char *path);
|
||||
|
||||
#endif //RISCB_AUDIO_H
|
||||
|
@@ -46,7 +46,7 @@ void renderText(SDL_Renderer *renderer, BitmapFont font, char *string, uint16_t
|
||||
string++;
|
||||
continue;
|
||||
}
|
||||
SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect);
|
||||
SDL_RenderCopy(renderer, font.texture[*string], &charRect, &outRect); //TODO CONSIDER FONTS IN ONE ATLAS
|
||||
outRect.x += charRect.w + 1;
|
||||
string++;
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ volatile bool running = true;
|
||||
|
||||
bool debugMode = false;
|
||||
bool itemViewing = false;
|
||||
bool renderAtlas = false;
|
||||
|
||||
//The surface contained by the window
|
||||
SDL_Renderer *mainRenderer = NULL;
|
||||
|
@@ -30,6 +30,7 @@ typedef enum OrientDirection{
|
||||
|
||||
extern bool debugMode;
|
||||
extern bool itemViewing;
|
||||
extern bool renderAtlas;
|
||||
|
||||
SDL_Texture *createRotatedTexture(SDL_Renderer *renderer, SDL_Texture *src, double angle);
|
||||
|
||||
|
Reference in New Issue
Block a user