anarch/main_csfml.c

380 lines
9.5 KiB
C
Raw Normal View History

2020-10-20 11:51:09 -04:00
/**
@file main_csfml.c
2020-10-26 10:51:35 -04:00
This is a csfml (C binding for SFML) implementation of the game front end.
2020-10-28 11:41:23 -04:00
This is another alternative to the SDL for the PC. This front end is maybe a
little simpler than the SDL, so it's better as a learning resource.
2020-10-20 11:51:09 -04:00
by Miloslav Ciz (drummyfish), 2020
Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
plus a waiver of all other intellectual property. The goal of this work is
be and remain completely in the public domain forever, available for any use
whatsoever.
*/
#include <SFML/Audio.h>
#include <SFML/Graphics.h>
#include <SFML/System.h>
#include <stdint.h>
#include <stdio.h>
2020-10-22 10:36:48 -04:00
#include <SFML/Audio/Types.h>
2020-10-20 11:51:09 -04:00
#define SFG_SCREEN_RESOLUTION_X 640
#define SFG_SCREEN_RESOLUTION_Y 480
#define SFG_DITHERED_SHADOW 1
#define SFG_DIMINISH_SPRITES 1
#define SFG_RESOLUTION_SCALEDOWN 1
2020-10-28 11:41:23 -04:00
#define SFG_BACKGROUND_BLUR 1
#define SFG_LOG(s) printf("game: %s\n",s);
2020-10-20 11:51:09 -04:00
2020-10-25 14:45:57 -04:00
#define MUSIC_VOLUME 16
2020-10-20 11:51:09 -04:00
#define WINDOW_SIZE (SFG_SCREEN_RESOLUTION_X * SFG_SCREEN_RESOLUTION_Y)
#include "game.h"
2020-10-22 10:36:48 -04:00
#include "sounds.h"
2020-10-20 11:51:09 -04:00
uint32_t windowPixels[WINDOW_SIZE];
uint32_t paletteRGB32[256]; // SFML can't do 565, so precompute RGB here
sfClock *clock;
sfRenderWindow* window;
2020-10-26 10:51:35 -04:00
uint8_t musicOn = 0;
2020-10-26 11:17:03 -04:00
int8_t mouseWheelState = 0;
2020-10-26 10:51:35 -04:00
2020-10-20 11:51:09 -04:00
int8_t SFG_keyPressed(uint8_t key)
{
2020-10-22 10:36:48 -04:00
#define k(x) sfKeyboard_isKeyPressed(sfKey ## x)
2020-10-20 11:51:09 -04:00
switch (key)
{
2020-10-22 10:36:48 -04:00
case SFG_KEY_UP: return k(W) || k(Up) || k(Num8); break;
case SFG_KEY_RIGHT: return k(E) || k(Right) || k(Num6);
2020-10-20 11:51:09 -04:00
break;
case SFG_KEY_DOWN:
2020-10-22 10:36:48 -04:00
return k(S) || k(Down) || k(Num5) || k (Num2); break;
case SFG_KEY_LEFT: return k(Q) || k(Left) || k(Num4); break;
2020-10-20 11:51:09 -04:00
case SFG_KEY_A:
2020-10-28 11:41:23 -04:00
return k(J) || k(Return) || k(LControl) || k(RControl) ||
2020-10-23 07:06:18 -04:00
sfMouse_isButtonPressed(sfMouseLeft); break;
2020-10-28 11:41:23 -04:00
case SFG_KEY_B: return k(K) || k(LShift); break;
2020-10-22 10:36:48 -04:00
case SFG_KEY_C: return k(L); break;
case SFG_KEY_JUMP: return k(Space); break;
case SFG_KEY_STRAFE_LEFT: return k(A) || k(Num7); break;
case SFG_KEY_STRAFE_RIGHT: return k(D) || k(Num9); break;
case SFG_KEY_MAP: return k(Tab); break;
case SFG_KEY_CYCLE_WEAPON: return k(F);
2020-10-20 11:51:09 -04:00
case SFG_KEY_TOGGLE_FREELOOK:
return sfMouse_isButtonPressed(sfMouseRight);
break;
case SFG_KEY_NEXT_WEAPON:
2020-10-26 11:17:03 -04:00
if (k(P) || k(X))
2020-10-20 11:51:09 -04:00
return 1;
2020-10-28 11:41:23 -04:00
#define checkMouse(cmp)\
if (mouseWheelState cmp 0) { mouseWheelState = 0; return 1; }
checkMouse(>)
2020-10-26 11:17:03 -04:00
2020-10-20 11:51:09 -04:00
return 0;
break;
case SFG_KEY_PREVIOUS_WEAPON:
2020-10-26 11:17:03 -04:00
if (k(O) || k(Y) || k(Z))
2020-10-20 11:51:09 -04:00
return 1;
2020-10-28 11:41:23 -04:00
checkMouse(<)
#undef checkMouse
2020-10-20 11:51:09 -04:00
return 0;
break;
2020-10-22 10:36:48 -04:00
case SFG_KEY_MENU: return sfKeyboard_isKeyPressed(sfKeyEscape); break;
2020-10-20 11:51:09 -04:00
default: return 0; break;
#undef k
}
2020-10-22 10:36:48 -04:00
return 0;
2020-10-20 11:51:09 -04:00
}
void SFG_getMouseOffset(int16_t *x, int16_t *y)
{
2020-10-22 10:36:48 -04:00
sfVector2u s = sfWindow_getSize((const sfWindow *) window);
sfVector2i p = sfMouse_getPosition((const sfWindow *) window);
2020-10-20 11:51:09 -04:00
s.x /= 2;
s.y /= 2;
*x = p.x - s.x;
*y = p.y - s.y;
p.x = s.x;
p.y = s.y;
2020-10-22 10:36:48 -04:00
sfMouse_setPosition(p,(const sfWindow *) window);
2020-10-20 11:51:09 -04:00
}
uint32_t SFG_getTimeMs()
{
return sfClock_getElapsedTime(clock).microseconds / 1000 ;
}
void SFG_sleepMs(uint16_t timeMs)
{
sfTime t;
t.microseconds = timeMs * 1000;
sfSleep(t);
}
void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex)
{
windowPixels[y * SFG_SCREEN_RESOLUTION_X + x] = paletteRGB32[colorIndex];
}
2020-10-23 06:34:26 -04:00
void SFG_setMusic(uint8_t value)
2020-10-20 11:51:09 -04:00
{
2020-10-26 10:51:35 -04:00
switch (value)
{
case SFG_MUSIC_TURN_ON: musicOn = 1; break;
case SFG_MUSIC_TURN_OFF: musicOn = 0; break;
case SFG_MUSIC_NEXT: SFG_nextMusicTrack(); break;
default: break;
}
2020-10-20 11:51:09 -04:00
}
void SFG_processEvent(uint8_t event, uint8_t data)
{
}
void SFG_save(uint8_t data[SFG_SAVE_SIZE])
{
2020-10-22 10:36:48 -04:00
FILE *f = fopen("anarch.sav","wb");
if (f == NULL)
return;
fwrite(data,1,SFG_SAVE_SIZE,f);
fclose(f);
2020-10-20 11:51:09 -04:00
}
uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE])
{
2020-10-22 10:36:48 -04:00
FILE *f = fopen("anarch.sav","rb");
if (f != NULL)
{
fread(data,1,SFG_SAVE_SIZE,f);
fclose(f);
}
return 1;
}
2020-10-26 10:51:35 -04:00
/* Because of the csfml sound API we won't use circular audio buffer, but a
linear buffer that is at each audio update shifted to the left. */
2020-10-25 14:45:57 -04:00
sfSoundStream *sound;
2020-10-22 10:36:48 -04:00
2020-10-26 10:51:35 -04:00
#define AUDIO_BUFFER_SIZE (SFG_SFX_SAMPLE_COUNT * 2) // total size of the buffer
#define AUDIO_BUFFER_OFFSET 400 /* size of the beginning portion of the buffer
that's being played, while the other part
is being filled with audio */
#if AUDIO_BUFFER_OFFSET * 2 > AUDIO_BUFFER_SIZE
#error "AUDIO_BUFFER_OFFSET must be at most half of AUDIO_BUFFER_SIZE"
#endif
2020-10-22 10:36:48 -04:00
2020-10-25 14:45:57 -04:00
int16_t audioBuffer[AUDIO_BUFFER_SIZE];
uint32_t audioUpdateFrame = 0; // game frame at which audio buffer fill happened
2020-10-22 10:36:48 -04:00
2020-10-25 14:45:57 -04:00
static inline int16_t mixSamples(int16_t sample1, int16_t sample2)
{
return sample1 + sample2;
2020-10-22 10:36:48 -04:00
}
2020-10-25 14:45:57 -04:00
void SFG_playSound(uint8_t soundIndex, uint8_t volume)
{
uint16_t volumeScale = 1 << (volume / 37);
2020-10-22 10:36:48 -04:00
2020-10-25 14:45:57 -04:00
uint32_t pos = AUDIO_BUFFER_OFFSET +
((SFG_game.frame - audioUpdateFrame) * SFG_MS_PER_FRAME * 8);
2020-10-22 10:36:48 -04:00
2020-10-25 14:45:57 -04:00
for (int i = 0; i < SFG_SFX_SAMPLE_COUNT; ++i)
{
2020-10-26 10:51:35 -04:00
if (pos >= AUDIO_BUFFER_SIZE)
break;
2020-10-25 14:45:57 -04:00
audioBuffer[pos] = mixSamples(audioBuffer[pos],
(128 - SFG_GET_SFX_SAMPLE(soundIndex,i)) * volumeScale);
2020-10-25 14:05:57 -04:00
2020-10-25 14:45:57 -04:00
pos++;
}
}
2020-10-25 14:05:57 -04:00
sfBool soundFill(sfSoundStreamChunk *data, void *userdata)
2020-10-22 10:36:48 -04:00
{
2020-10-25 14:45:57 -04:00
for (uint32_t i = 0; i < AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET; ++i)
audioBuffer[i] = audioBuffer[i + AUDIO_BUFFER_OFFSET];
2020-10-26 10:51:35 -04:00
for (uint32_t i = AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET;
i < AUDIO_BUFFER_SIZE; ++i)
2020-10-25 14:45:57 -04:00
audioBuffer[i] = 0;
2020-10-26 10:51:35 -04:00
if (musicOn)
for (uint32_t i = 0; i < AUDIO_BUFFER_OFFSET; ++i) // mix in the music
{
audioBuffer[i] = mixSamples((SFG_getNextMusicSample() -
SFG_musicTrackAverages[SFG_MusicState.track]) * MUSIC_VOLUME,
audioBuffer[i]);
}
2020-10-25 14:05:57 -04:00
data->samples = audioBuffer;
2020-10-25 14:45:57 -04:00
data->sampleCount = AUDIO_BUFFER_OFFSET;
audioUpdateFrame = SFG_game.frame;
2020-10-25 14:05:57 -04:00
return sfTrue;
2020-10-22 10:36:48 -04:00
}
2020-10-25 14:05:57 -04:00
void soundSeek(sfTime t, void *userData)
2020-10-22 10:36:48 -04:00
{
2020-10-20 11:51:09 -04:00
}
2020-10-28 11:41:23 -04:00
uint32_t screenshotNumber = 0;
/**
Saves a screenshot using the simple uncompressed PPM file format.
*/
void screenshot()
2020-10-20 11:51:09 -04:00
{
2020-10-28 11:41:23 -04:00
char fileName[64];
sprintf(fileName,"screenshot_%05d.ppm",screenshotNumber);
FILE *f = fopen(fileName,"w");
if (!f)
{
puts("error: could not take screenshot");
return;
}
fprintf(f,"P6 %d %d 255\n",SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y);
for (uint32_t i = 0; i < WINDOW_SIZE; ++i)
fwrite(&windowPixels[i],1,3,f);
puts("screenshot taken");
screenshotNumber++;
fclose(f);
}
int main(int argc, char *argv[])
{
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'h' && argv[1][2] == 0)
{
puts("Anarch (CSFML), version " SFG_VERSION_STRING "\n");
puts("Anarch is a unique suckless FPS game. Collect weapons and items and destroy");
puts("robot enemies in your way in order to get to the level finish. Some door are");
puts("locked and require access cards. Good luck!\n");
puts("created by Miloslav \"drummyfish\" Ciz, 2020, released under CC0 1.0 (public domain)\n");
puts("controls:\n");
2020-11-20 12:00:12 -05:00
puts("- arrows, numpad, [W] [S] [A] [D] [Q] [E]: movement");
2020-10-28 11:41:23 -04:00
puts("- mouse: rotation, [LMB] shoot, [RMB] toggle free look");
puts("- [SPACE]: jump");
puts("- [J] [RETURN] [CTRL] [LMB]: game A button (shoot, confirm)");
puts("- [K] [SHIFT]: game B button (cancel, strafe)");
puts("- [L]: game C button (+ down = menu, + up = jump, ...)");
puts("- [F]: cycle next/previous weapon");
2020-11-20 12:00:12 -05:00
puts("- [O] [P] [X] [Y] [Z] [mouse wheel] [mouse middle]: change weapons");
2020-10-28 11:41:23 -04:00
puts("- [TAB]: map");
puts("- [F12]: screenshot");
puts("- [ESCAPE]: menu");
return 0;
}
SFG_init();
2020-10-20 11:51:09 -04:00
sfVideoMode mode = {SFG_SCREEN_RESOLUTION_X, SFG_SCREEN_RESOLUTION_Y, 32};
sfEvent event;
clock = sfClock_create();
sfClock_restart(clock);
2020-10-28 11:41:23 -04:00
puts("initializing");
2020-10-22 10:36:48 -04:00
2020-10-26 10:51:35 -04:00
for (int i = 0; i < AUDIO_BUFFER_SIZE; ++i)
audioBuffer[i] = 0;
2020-10-22 10:36:48 -04:00
2020-10-26 10:51:35 -04:00
sound = sfSoundStream_create(soundFill,soundSeek,1,8000,0);
2020-10-22 10:36:48 -04:00
2020-10-20 11:51:09 -04:00
for (int i = 0; i < 256; ++i) // precompute RGB palette
{
uint16_t col565 = paletteRGB565[i];
paletteRGB32[i] = 0xff000000 | ((col565 << 19) & 0xf80000) |
((col565 << 5) & 0xfc00) | ((col565 >> 8) & 0xf8);
}
2020-10-26 10:51:35 -04:00
sfTexture* windowTexture =
sfTexture_create(SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y);
2020-10-20 11:51:09 -04:00
sfTexture_setSmooth(windowTexture,sfTrue);
sfSprite* windowSprite = sfSprite_create();
window = sfRenderWindow_create(mode, "Anarch", sfResize | sfClose, NULL);
sfSprite_setTexture(windowSprite, windowTexture, sfTrue);
2020-10-22 10:36:48 -04:00
sfWindow_setMouseCursorVisible((sfWindow *) window,sfFalse);
sfWindow_setVerticalSyncEnabled((sfWindow *) window,sfFalse);
2020-10-26 10:51:35 -04:00
sfSoundStream_play(sound);
2020-10-20 11:51:09 -04:00
2020-10-28 11:41:23 -04:00
puts("starting");
2020-10-20 11:51:09 -04:00
while (sfRenderWindow_isOpen(window))
{
while (sfRenderWindow_pollEvent(window,&event))
if (event.type == sfEvtClosed)
sfRenderWindow_close(window);
2020-10-26 11:17:03 -04:00
else if (event.type == sfEvtMouseWheelMoved)
mouseWheelState = event.mouseWheel.delta;
2020-10-28 11:41:23 -04:00
else if (event.type == sfEvtKeyPressed && event.key.code == sfKeyF12)
screenshot();
2020-10-20 11:51:09 -04:00
if (!SFG_mainLoopBody())
break;
2020-10-26 10:51:35 -04:00
sfTexture_updateFromPixels(windowTexture,(const sfUint8 *) windowPixels,
SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y,0,0);
2020-10-20 11:51:09 -04:00
sfRenderWindow_clear(window, sfBlack);
2020-10-26 10:51:35 -04:00
sfRenderWindow_drawSprite(window,windowSprite,NULL);
2020-10-20 11:51:09 -04:00
sfRenderWindow_display(window);
}
2020-10-28 11:41:23 -04:00
puts("ending");
2020-10-26 10:51:35 -04:00
sfSoundStream_stop(sound);
sfSoundStream_destroy(sound);
2020-10-20 11:51:09 -04:00
sfSprite_destroy(windowSprite);
sfTexture_destroy(windowTexture);
sfRenderWindow_destroy(window);
sfClock_destroy(clock);
return 0;
}