/** @file main_csfml.c This is a csfml (C binding for SFML) implementation of the game front end. 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. 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 #include #include #include #include #include #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 #define SFG_BACKGROUND_BLUR 1 #define SFG_LOG(s) printf("game: %s\n",s); #define MUSIC_VOLUME 16 #define WINDOW_SIZE (SFG_SCREEN_RESOLUTION_X * SFG_SCREEN_RESOLUTION_Y) #include "game.h" #include "sounds.h" uint32_t windowPixels[WINDOW_SIZE]; uint32_t paletteRGB32[256]; // SFML can't do 565, so precompute RGB here sfClock *clock; sfRenderWindow* window; uint8_t musicOn = 0; int8_t mouseWheelState = 0; int8_t SFG_keyPressed(uint8_t key) { #define k(x) sfKeyboard_isKeyPressed(sfKey ## x) switch (key) { case SFG_KEY_UP: return k(W) || k(Up) || k(Num8); break; case SFG_KEY_RIGHT: return k(E) || k(Right) || k(Num6); break; case SFG_KEY_DOWN: return k(S) || k(Down) || k(Num5) || k (Num2); break; case SFG_KEY_LEFT: return k(Q) || k(Left) || k(Num4); break; case SFG_KEY_A: return k(J) || k(Return) || k(LControl) || k(RControl) || sfMouse_isButtonPressed(sfMouseLeft); break; case SFG_KEY_B: return k(K) || k(LShift); break; 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); case SFG_KEY_TOGGLE_FREELOOK: return sfMouse_isButtonPressed(sfMouseRight); break; case SFG_KEY_NEXT_WEAPON: if (k(P) || k(X)) return 1; #define checkMouse(cmp)\ if (mouseWheelState cmp 0) { mouseWheelState = 0; return 1; } checkMouse(>) return 0; break; case SFG_KEY_PREVIOUS_WEAPON: if (k(O) || k(Y) || k(Z)) return 1; checkMouse(<) #undef checkMouse return 0; break; case SFG_KEY_MENU: return sfKeyboard_isKeyPressed(sfKeyEscape); break; default: return 0; break; #undef k } return 0; } void SFG_getMouseOffset(int16_t *x, int16_t *y) { sfVector2u s = sfWindow_getSize((const sfWindow *) window); sfVector2i p = sfMouse_getPosition((const sfWindow *) window); s.x /= 2; s.y /= 2; *x = p.x - s.x; *y = p.y - s.y; p.x = s.x; p.y = s.y; sfMouse_setPosition(p,(const sfWindow *) window); } 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]; } void SFG_setMusic(uint8_t value) { 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; } } void SFG_processEvent(uint8_t event, uint8_t data) { } void SFG_save(uint8_t data[SFG_SAVE_SIZE]) { FILE *f = fopen("anarch.sav","wb"); if (f == NULL) return; fwrite(data,1,SFG_SAVE_SIZE,f); fclose(f); } uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE]) { FILE *f = fopen("anarch.sav","rb"); if (f != NULL) { fread(data,1,SFG_SAVE_SIZE,f); fclose(f); } return 1; } /* 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. */ sfSoundStream *sound; #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 int16_t audioBuffer[AUDIO_BUFFER_SIZE]; uint32_t audioUpdateFrame = 0; // game frame at which audio buffer fill happened static inline int16_t mixSamples(int16_t sample1, int16_t sample2) { return sample1 + sample2; } void SFG_playSound(uint8_t soundIndex, uint8_t volume) { uint16_t volumeScale = 1 << (volume / 37); uint32_t pos = AUDIO_BUFFER_OFFSET + ((SFG_game.frame - audioUpdateFrame) * SFG_MS_PER_FRAME * 8); for (int i = 0; i < SFG_SFX_SAMPLE_COUNT; ++i) { if (pos >= AUDIO_BUFFER_SIZE) break; audioBuffer[pos] = mixSamples(audioBuffer[pos], (128 - SFG_GET_SFX_SAMPLE(soundIndex,i)) * volumeScale); pos++; } } sfBool soundFill(sfSoundStreamChunk *data, void *userdata) { for (uint32_t i = 0; i < AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET; ++i) audioBuffer[i] = audioBuffer[i + AUDIO_BUFFER_OFFSET]; for (uint32_t i = AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET; i < AUDIO_BUFFER_SIZE; ++i) audioBuffer[i] = 0; 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]); } data->samples = audioBuffer; data->sampleCount = AUDIO_BUFFER_OFFSET; audioUpdateFrame = SFG_game.frame; return sfTrue; } void soundSeek(sfTime t, void *userData) { } uint32_t screenshotNumber = 0; /** Saves a screenshot using the simple uncompressed PPM file format. */ void screenshot() { 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[]) { uint8_t fullScreen = 1; for (uint8_t i = 1; i < argc; ++i) { if (argv[i][0] == '-' && argv[i][1] == 'h' && argv[i][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"); puts("- arrows, numpad, [W] [S] [A] [D] [Q] [E]: movement"); 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"); puts("- [O] [P] [X] [Y] [Z] [mouse wheel] [mouse middle]: change weapons"); puts("- [TAB]: map"); puts("- [F12]: screenshot"); puts("- [ESCAPE]: menu"); return 0; } else if (argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0) fullScreen = 0; else if (argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0) fullScreen = 1; else puts("SDL: unknown argument"); } SFG_init(); sfVideoMode mode = {SFG_SCREEN_RESOLUTION_X, SFG_SCREEN_RESOLUTION_Y, 32}; sfEvent event; clock = sfClock_create(); sfClock_restart(clock); puts("initializing"); for (int i = 0; i < AUDIO_BUFFER_SIZE; ++i) audioBuffer[i] = 0; sound = sfSoundStream_create(soundFill,soundSeek,1,8000,0); 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); } sfTexture* windowTexture = sfTexture_create(SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y); sfTexture_setSmooth(windowTexture,sfTrue); sfSprite* windowSprite = sfSprite_create(); window = sfRenderWindow_create(mode, "Anarch", fullScreen ? sfFullscreen : (sfResize | sfClose ), NULL); sfSprite_setTexture(windowSprite, windowTexture, sfTrue); sfWindow_setMouseCursorVisible((sfWindow *) window,sfFalse); sfWindow_setVerticalSyncEnabled((sfWindow *) window,sfFalse); sfSoundStream_play(sound); puts("starting"); while (sfRenderWindow_isOpen(window)) { while (sfRenderWindow_pollEvent(window,&event)) if (event.type == sfEvtClosed) sfRenderWindow_close(window); else if (event.type == sfEvtMouseWheelMoved) mouseWheelState = event.mouseWheel.delta; else if (event.type == sfEvtKeyPressed && event.key.code == sfKeyF12) screenshot(); if (!SFG_mainLoopBody()) break; sfTexture_updateFromPixels(windowTexture,(const sfUint8 *) windowPixels, SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y,0,0); sfRenderWindow_clear(window, sfBlack); sfRenderWindow_drawSprite(window,windowSprite,NULL); sfRenderWindow_display(window); } puts("ending"); sfSoundStream_stop(sound); sfSoundStream_destroy(sound); sfSprite_destroy(windowSprite); sfTexture_destroy(windowTexture); sfRenderWindow_destroy(window); sfClock_destroy(clock); return 0; }