Update
[anarch.git] / main_csfml.c
blob14ec59017896d1d37734ff03f965c6deaaa43656
1 /**
2 @file main_csfml.c
4 This is a csfml (C binding for SFML) implementation of the game front end.
5 This is another alternative to the SDL for the PC. This front end is maybe a
6 little simpler than the SDL, so it's better as a learning resource.
8 by Miloslav Ciz (drummyfish), 2020
10 Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
11 plus a waiver of all other intellectual property. The goal of this work is
12 be and remain completely in the public domain forever, available for any use
13 whatsoever.
16 #include <SFML/Audio.h>
17 #include <SFML/Graphics.h>
18 #include <SFML/System.h>
19 #include <stdint.h>
20 #include <stdio.h>
22 #include <SFML/Audio/Types.h>
24 #define SFG_SCREEN_RESOLUTION_X 640
25 #define SFG_SCREEN_RESOLUTION_Y 480
27 #define SFG_DITHERED_SHADOW 1
28 #define SFG_DIMINISH_SPRITES 1
29 #define SFG_RESOLUTION_SCALEDOWN 1
30 #define SFG_BACKGROUND_BLUR 1
32 #define SFG_LOG(s) printf("game: %s\n",s);
34 #define MUSIC_VOLUME 16
36 #define WINDOW_SIZE (SFG_SCREEN_RESOLUTION_X * SFG_SCREEN_RESOLUTION_Y)
38 #include "game.h"
39 #include "sounds.h"
41 uint32_t windowPixels[WINDOW_SIZE];
43 uint32_t paletteRGB32[256]; // SFML can't do 565, so precompute RGB here
45 sfClock *clock;
46 sfRenderWindow* window;
48 uint8_t musicOn = 0;
49 int8_t mouseWheelState = 0;
51 int8_t SFG_keyPressed(uint8_t key)
53 #define k(x) sfKeyboard_isKeyPressed(sfKey ## x)
55 switch (key)
57 case SFG_KEY_UP: return k(W) || k(Up) || k(Num8); break;
58 case SFG_KEY_RIGHT: return k(E) || k(Right) || k(Num6);
59 break;
60 case SFG_KEY_DOWN:
61 return k(S) || k(Down) || k(Num5) || k (Num2); break;
62 case SFG_KEY_LEFT: return k(Q) || k(Left) || k(Num4); break;
63 case SFG_KEY_A:
64 return k(J) || k(Return) || k(LControl) || k(RControl) ||
65 sfMouse_isButtonPressed(sfMouseLeft); break;
66 case SFG_KEY_B: return k(K) || k(LShift); break;
67 case SFG_KEY_C: return k(L); break;
68 case SFG_KEY_JUMP: return k(Space); break;
69 case SFG_KEY_STRAFE_LEFT: return k(A) || k(Num7); break;
70 case SFG_KEY_STRAFE_RIGHT: return k(D) || k(Num9); break;
71 case SFG_KEY_MAP: return k(Tab); break;
72 case SFG_KEY_CYCLE_WEAPON: return k(F);
73 case SFG_KEY_TOGGLE_FREELOOK:
74 return sfMouse_isButtonPressed(sfMouseRight);
75 break;
77 case SFG_KEY_NEXT_WEAPON:
78 if (k(P) || k(X))
79 return 1;
81 #define checkMouse(cmp)\
82 if (mouseWheelState cmp 0) { mouseWheelState = 0; return 1; }
84 checkMouse(>)
86 return 0;
87 break;
89 case SFG_KEY_PREVIOUS_WEAPON:
90 if (k(O) || k(Y) || k(Z))
91 return 1;
93 checkMouse(<)
95 #undef checkMouse
97 return 0;
98 break;
100 case SFG_KEY_MENU: return sfKeyboard_isKeyPressed(sfKeyEscape); break;
101 default: return 0; break;
103 #undef k
106 return 0;
109 void SFG_getMouseOffset(int16_t *x, int16_t *y)
111 sfVector2u s = sfWindow_getSize((const sfWindow *) window);
112 sfVector2i p = sfMouse_getPosition((const sfWindow *) window);
114 s.x /= 2;
115 s.y /= 2;
117 *x = p.x - s.x;
118 *y = p.y - s.y;
120 p.x = s.x;
121 p.y = s.y;
123 sfMouse_setPosition(p,(const sfWindow *) window);
126 uint32_t SFG_getTimeMs()
128 return sfClock_getElapsedTime(clock).microseconds / 1000 ;
131 void SFG_sleepMs(uint16_t timeMs)
133 sfTime t;
134 t.microseconds = timeMs * 1000;
135 sfSleep(t);
138 void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex)
140 windowPixels[y * SFG_SCREEN_RESOLUTION_X + x] = paletteRGB32[colorIndex];
143 void SFG_setMusic(uint8_t value)
145 switch (value)
147 case SFG_MUSIC_TURN_ON: musicOn = 1; break;
148 case SFG_MUSIC_TURN_OFF: musicOn = 0; break;
149 case SFG_MUSIC_NEXT: SFG_nextMusicTrack(); break;
150 default: break;
154 void SFG_processEvent(uint8_t event, uint8_t data)
158 void SFG_save(uint8_t data[SFG_SAVE_SIZE])
160 FILE *f = fopen(SFG_SAVE_FILE_PATH,"wb");
162 if (f == NULL)
163 return;
165 fwrite(data,1,SFG_SAVE_SIZE,f);
167 fclose(f);
170 uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE])
172 FILE *f = fopen(SFG_SAVE_FILE_PATH,"rb");
174 if (f != NULL)
176 fread(data,1,SFG_SAVE_SIZE,f);
177 fclose(f);
180 return 1;
183 /* Because of the csfml sound API we won't use circular audio buffer, but a
184 linear buffer that is at each audio update shifted to the left. */
186 sfSoundStream *sound;
188 #define AUDIO_BUFFER_SIZE (SFG_SFX_SAMPLE_COUNT * 2) // total size of the buffer
189 #define AUDIO_BUFFER_OFFSET 400 /* size of the beginning portion of the buffer
190 that's being played, while the other part
191 is being filled with audio */
193 #if AUDIO_BUFFER_OFFSET * 2 > AUDIO_BUFFER_SIZE
194 #error "AUDIO_BUFFER_OFFSET must be at most half of AUDIO_BUFFER_SIZE"
195 #endif
197 int16_t audioBuffer[AUDIO_BUFFER_SIZE];
198 uint32_t audioUpdateFrame = 0; // game frame at which audio buffer fill happened
200 static inline int16_t mixSamples(int16_t sample1, int16_t sample2)
202 return sample1 + sample2;
205 void SFG_playSound(uint8_t soundIndex, uint8_t volume)
207 uint16_t volumeScale = 1 << (volume / 37);
209 uint32_t pos = AUDIO_BUFFER_OFFSET +
210 ((SFG_game.frame - audioUpdateFrame) * SFG_MS_PER_FRAME * 8);
212 for (int i = 0; i < SFG_SFX_SAMPLE_COUNT; ++i)
214 if (pos >= AUDIO_BUFFER_SIZE)
215 break;
217 audioBuffer[pos] = mixSamples(audioBuffer[pos],
218 (128 - SFG_GET_SFX_SAMPLE(soundIndex,i)) * volumeScale);
220 pos++;
224 sfBool soundFill(sfSoundStreamChunk *data, void *userdata)
226 for (uint32_t i = 0; i < AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET; ++i)
227 audioBuffer[i] = audioBuffer[i + AUDIO_BUFFER_OFFSET];
229 for (uint32_t i = AUDIO_BUFFER_SIZE - AUDIO_BUFFER_OFFSET;
230 i < AUDIO_BUFFER_SIZE; ++i)
231 audioBuffer[i] = 0;
233 if (musicOn)
234 for (uint32_t i = 0; i < AUDIO_BUFFER_OFFSET; ++i) // mix in the music
236 audioBuffer[i] = mixSamples((SFG_getNextMusicSample() -
237 SFG_musicTrackAverages[SFG_MusicState.track]) * MUSIC_VOLUME,
238 audioBuffer[i]);
241 data->samples = audioBuffer;
242 data->sampleCount = AUDIO_BUFFER_OFFSET;
244 audioUpdateFrame = SFG_game.frame;
246 return sfTrue;
249 void soundSeek(sfTime t, void *userData)
253 uint32_t screenshotNumber = 0;
256 Saves a screenshot using the simple uncompressed PPM file format.
258 void screenshot()
260 char fileName[64];
262 sprintf(fileName,"screenshot_%05d.ppm",screenshotNumber);
264 FILE *f = fopen(fileName,"w");
266 if (!f)
268 puts("error: could not take screenshot");
269 return;
272 fprintf(f,"P6 %d %d 255\n",SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y);
274 for (uint32_t i = 0; i < WINDOW_SIZE; ++i)
275 fwrite(&windowPixels[i],1,3,f);
277 puts("screenshot taken");
279 screenshotNumber++;
281 fclose(f);
284 int main(int argc, char *argv[])
286 uint8_t fullScreen = 1;
288 for (uint8_t i = 1; i < argc; ++i)
290 if (argv[i][0] == '-' && argv[i][1] == 'h' && argv[i][2] == 0)
292 puts("Anarch (CSFML), version " SFG_VERSION_STRING "\n");
293 puts("Anarch is a unique suckless FPS game. Collect weapons and items and destroy");
294 puts("robot enemies in your way in order to get to the level finish. Some door are");
295 puts("locked and require access cards. Good luck!\n");
296 puts("created by Miloslav \"drummyfish\" Ciz, 2020, released under CC0 1.0 (public domain)\n");
297 puts("controls:\n");
298 puts("- arrows, numpad, [W] [S] [A] [D] [Q] [E]: movement");
299 puts("- mouse: rotation, [LMB] shoot, [RMB] toggle free look");
300 puts("- [SPACE]: jump");
301 puts("- [J] [RETURN] [CTRL] [LMB]: game A button (shoot, confirm)");
302 puts("- [K] [SHIFT]: game B button (cancel, strafe)");
303 puts("- [L]: game C button (+ down = menu, + up = jump, ...)");
304 puts("- [F]: cycle next/previous weapon");
305 puts("- [O] [P] [X] [Y] [Z] [mouse wheel] [mouse middle]: change weapons");
306 puts("- [TAB]: map");
307 puts("- [F12]: screenshot");
308 puts("- [ESCAPE]: menu");
309 return 0;
311 else if (argv[i][0] == '-' && argv[i][1] == 'w' && argv[i][2] == 0)
312 fullScreen = 0;
313 else if (argv[i][0] == '-' && argv[i][1] == 'f' && argv[i][2] == 0)
314 fullScreen = 1;
315 else
316 puts("SDL: unknown argument");
319 SFG_init();
321 sfVideoMode mode = {SFG_SCREEN_RESOLUTION_X, SFG_SCREEN_RESOLUTION_Y, 32};
322 sfEvent event;
323 clock = sfClock_create();
324 sfClock_restart(clock);
326 puts("initializing");
328 for (int i = 0; i < AUDIO_BUFFER_SIZE; ++i)
329 audioBuffer[i] = 0;
331 sound = sfSoundStream_create(soundFill,soundSeek,1,8000,0);
333 for (int i = 0; i < 256; ++i) // precompute RGB palette
335 uint16_t col565 = paletteRGB565[i];
337 paletteRGB32[i] = 0xff000000 | ((col565 << 19) & 0xf80000) |
338 ((col565 << 5) & 0xfc00) | ((col565 >> 8) & 0xf8);
341 sfTexture* windowTexture =
342 sfTexture_create(SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y);
344 sfTexture_setSmooth(windowTexture,sfTrue);
346 sfSprite* windowSprite = sfSprite_create();
348 window = sfRenderWindow_create(mode, "Anarch",
349 fullScreen ? sfFullscreen : (sfResize | sfClose ), NULL);
351 sfSprite_setTexture(windowSprite, windowTexture, sfTrue);
353 sfWindow_setMouseCursorVisible((sfWindow *) window,sfFalse);
354 sfWindow_setVerticalSyncEnabled((sfWindow *) window,sfFalse);
356 sfSoundStream_play(sound);
358 puts("starting");
360 while (sfRenderWindow_isOpen(window))
362 while (sfRenderWindow_pollEvent(window,&event))
363 if (event.type == sfEvtClosed)
364 sfRenderWindow_close(window);
365 else if (event.type == sfEvtMouseWheelMoved)
366 mouseWheelState = event.mouseWheel.delta;
367 else if (event.type == sfEvtKeyPressed && event.key.code == sfKeyF12)
368 screenshot();
370 if (!SFG_mainLoopBody())
371 break;
373 sfTexture_updateFromPixels(windowTexture,(const sfUint8 *) windowPixels,
374 SFG_SCREEN_RESOLUTION_X,SFG_SCREEN_RESOLUTION_Y,0,0);
375 sfRenderWindow_clear(window, sfBlack);
376 sfRenderWindow_drawSprite(window,windowSprite,NULL);
377 sfRenderWindow_display(window);
380 puts("ending");
382 sfSoundStream_stop(sound);
383 sfSoundStream_destroy(sound);
384 sfSprite_destroy(windowSprite);
385 sfTexture_destroy(windowTexture);
386 sfRenderWindow_destroy(window);
387 sfClock_destroy(clock);
389 return 0;