README.md edited online with Bitbucket
[gdash.git] / src / framework / replaysaveractivity.cpp
blob80453fdd2f3e5fed00485aea0e869a811b9616b3
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
19 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "config.h"
26 /* the replay saver thing only works in the sdl version */
27 #ifdef HAVE_SDL
29 #include <SDL/SDL_mixer.h>
30 #include <glib/gi18n.h>
32 #include "framework/replaysaveractivity.hpp"
33 #include "framework/app.hpp"
34 #include "framework/commands.hpp"
35 #include "sdl/IMG_savepng.h"
36 #include "sound/sound.hpp"
37 #include "cave/gamerender.hpp"
38 #include "cave/gamecontrol.hpp"
39 #include "cave/titleanimation.hpp"
40 #include "cave/caveset.hpp"
41 #include "misc/logger.hpp"
42 #include "settings.hpp"
43 #include "sdl/sdlpixbuf.hpp"
45 class SDLInmemoryPixmap: public Pixmap {
46 protected:
47 SDL_Surface *surface;
49 SDLInmemoryPixmap(const SDLInmemoryPixmap &); // copy ctor not implemented
50 SDLInmemoryPixmap &operator=(const SDLInmemoryPixmap &); // operator= not implemented
52 public:
53 SDLInmemoryPixmap(SDL_Surface *surface_) : surface(surface_) {}
54 ~SDLInmemoryPixmap() {
55 SDL_FreeSurface(surface);
58 virtual int get_width() const {
59 return surface->w;
61 virtual int get_height() const {
62 return surface->h;
67 Pixmap *SDLInmemoryScreen::create_pixmap_from_pixbuf(Pixbuf const &pb, bool keep_alpha) const {
68 SDL_Surface *to_copy = static_cast<SDLPixbuf const &>(pb).get_surface();
69 SDL_Surface *newsurface = SDL_CreateRGBSurface(keep_alpha ? SDL_SRCALPHA : 0, to_copy->w, to_copy->h, 32,
70 surface->format->Rmask, surface->format->Gmask, surface->format->Bmask, surface->format->Amask);
71 SDL_SetAlpha(to_copy, 0, SDL_ALPHA_OPAQUE);
72 SDL_BlitSurface(to_copy, NULL, newsurface, NULL);
73 return new SDLInmemoryPixmap(newsurface);
77 void SDLInmemoryScreen::set_title(char const *) {
78 /* do nothing, not a real window */
82 void SDLInmemoryScreen::configure_size() {
83 if (surface)
84 SDL_FreeSurface(surface);
85 surface = SDL_CreateRGBSurface(SDL_SRCALPHA, w, h, 32, Pixbuf::rmask, Pixbuf::gmask, Pixbuf::bmask, Pixbuf::amask);
86 Uint32 col = SDL_MapRGBA(surface->format, 0, 0, 0, SDL_ALPHA_OPAQUE);
87 SDL_FillRect(surface, NULL, col);
91 SDLInmemoryScreen::~SDLInmemoryScreen() {
92 SDL_FreeSurface(surface);
96 void SDLInmemoryScreen::save(char const *filename) {
97 IMG_SavePNG(filename, surface, 2); // 2 = not too much compression, but a bit faster than the default
101 Pixbuf const *SDLInmemoryScreen::create_pixbuf_screenshot() const {
102 SDL_Surface *sub = SDL_CreateRGBSurfaceFrom(surface->pixels,
103 w, h, 32, surface->pitch, surface->format->Rmask, surface->format->Gmask, surface->format->Bmask, 0);
104 return new SDLPixbuf(sub);
108 ReplaySaverActivity::ReplaySaverActivity(App *app, CaveStored *cave, CaveReplay *replay, std::string const &filename_prefix)
110 Activity(app),
111 filename_prefix(filename_prefix),
112 game(GameControl::new_replay(app->caveset, cave, replay)),
113 pf(),
114 pm(pf),
115 fm(pm, ""),
116 cellrenderer(pm, gd_theme),
117 gamerenderer(pm, cellrenderer, fm, *game) {
118 int cell_size = cellrenderer.get_cell_size();
119 pm.set_size(cell_size * GAME_RENDERER_SCREEN_SIZE_X, cell_size * GAME_RENDERER_SCREEN_SIZE_Y, false);
120 gamerenderer.screen_initialized();
121 gamerenderer.set_show_replay_sign(false);
122 std::string wav_filename = filename_prefix + ".wav";
123 wavfile = fopen(wav_filename.c_str(), "wb");
124 if (!wavfile) {
125 gd_critical(CPrintf("Cannot open %s for sound output") % wav_filename);
126 app->enqueue_command(new PopActivityCommand(app));
127 return;
129 fseek(wavfile, 44, SEEK_SET); /* 44bytes offset: start of data in a wav file */
130 wavlen = 0;
131 frame = 0;
132 /* save settings and install own settings */
133 saved_gd_show_name_of_game = gd_show_name_of_game;
134 gd_show_name_of_game = true;
138 void ReplaySaverActivity::shown_event() {
139 std::vector<Pixmap *> animation = get_title_animation_pixmap(app->caveset->title_screen, app->caveset->title_screen_scroll, true, pm, pf);
140 pm.blit(*animation[0], 0, 0);
141 delete animation[0];
142 gd_music_stop();
144 /* enable own timer and sound saver */
145 install_own_mixer();
149 ReplaySaverActivity::~ReplaySaverActivity() {
150 Mix_SetPostMix(NULL, NULL); /* remove wav file saver */
151 uninstall_own_mixer();
152 gd_sound_off();
154 /* write wav header, as now we now its final size. */
155 fseek(wavfile, 0, SEEK_SET);
156 Uint32 out32;
157 Uint16 out16;
159 int i = 0;
160 i += fwrite("RIFF", 1, 4, wavfile); /* "RIFF" */
161 out32 = GUINT32_TO_LE(wavlen + 36);
162 i += fwrite(&out32, 1, 4, wavfile); /* 4 + 8+subchunk1size + 8+subchunk2size */
163 i += fwrite("WAVE", 1, 4, wavfile); /* "WAVE" */
165 i += fwrite("fmt ", 1, 4, wavfile); /* "fmt " */
166 out32 = GUINT32_TO_LE(16);
167 i += fwrite(&out32, 1, 4, wavfile); /* fmt chunk size=16 bytes */
168 out16 = GUINT16_TO_LE(1);
169 i += fwrite(&out16, 1, 2, wavfile); /* 1=pcm */
170 out16 = GUINT16_TO_LE(channels);
171 i += fwrite(&out16, 1, 2, wavfile);
172 out32 = GUINT32_TO_LE(frequency);
173 i += fwrite(&out32, 1, 4, wavfile);
174 out32 = GUINT32_TO_LE(frequency * bits / 8 * channels);
175 i += fwrite(&out32, 1, 4, wavfile); /* byterate */
176 out16 = GUINT16_TO_LE(bits / 8 * channels);
177 i += fwrite(&out16, 1, 2, wavfile); /* blockalign */
178 out16 = GUINT16_TO_LE(bits);
179 i += fwrite(&out16, 1, 2, wavfile); /* bitspersample */
181 i += fwrite("data", 1, 4, wavfile); /* "data" */
182 out32 = GUINT32_TO_LE(wavlen);
183 i += fwrite(&out32, 1, 4, wavfile); /* actual data length */
184 fclose(wavfile);
186 if (i != 44)
187 gd_critical("Could not write wav header to file!");
189 std::string message = SPrintf(_("Saved %d video frames and %dMiB of audio data to %s_*.png and %s.wav.")) % (frame + 1) % (wavlen / 1048576) % filename_prefix % filename_prefix;
190 app->show_message(_("Replay Saved"), message);
192 // restore settings
193 gd_show_name_of_game = saved_gd_show_name_of_game;
194 gd_sound_set_music_volume();
195 gd_sound_set_chunk_volumes();
196 gd_music_play_random();
197 delete game;
201 void ReplaySaverActivity::install_own_mixer() {
202 gd_sound_close();
204 /* we setup mixing and other parameters for our own needs. */
205 saved_gd_sdl_sound = gd_sound_enabled;
206 saved_gd_sdl_44khz_mixing = gd_sound_44khz_mixing;
207 saved_gd_sdl_16bit_mixing = gd_sound_16bit_mixing;
208 saved_gd_sound_stereo = gd_sound_stereo;
209 const char *driver = g_getenv("SDL_AUDIODRIVER");
210 if (driver)
211 saved_driver = driver;
213 /* and after tweaking settings to our own need, we can init sdl. */
214 gd_sound_enabled = true;
215 gd_sound_44khz_mixing = true;
216 gd_sound_16bit_mixing = true;
217 gd_sound_stereo = true;
218 // select the dummy driver, as it accepts any format, and we have control
219 g_setenv("SDL_AUDIODRIVER", "dummy", TRUE);
220 gd_sound_init(44100 / 25); /* so the buffer will be 1/25th of a second. and we get our favourite 25hz interrupt from the mixer. */
222 /* query audio format from sdl */
223 Uint16 format;
224 Mix_QuerySpec(&frequency, &format, &channels);
225 if (frequency != 44100) /* something must be really going wrong. */
226 gd_critical("Cannot initialize mixer to 44100Hz mixing. The replay saver will not work correctly!");
228 switch (format) {
229 case AUDIO_U8:
230 case AUDIO_S8:
231 bits = 8;
232 break;
233 case AUDIO_U16LSB:
234 case AUDIO_S16LSB:
235 case AUDIO_U16MSB:
236 case AUDIO_S16MSB:
237 bits = 16;
238 break;
239 default:
240 g_assert_not_reached();
242 Mix_SetPostMix(mixfunc, this);
246 void ReplaySaverActivity::uninstall_own_mixer() {
247 gd_sound_close();
249 // restore settings
250 gd_sound_enabled = saved_gd_sdl_sound;
251 gd_sound_44khz_mixing = saved_gd_sdl_44khz_mixing;
252 gd_sound_16bit_mixing = saved_gd_sdl_16bit_mixing;
253 gd_sound_stereo = saved_gd_sound_stereo;
255 if (saved_driver != "")
256 g_setenv("SDL_AUDIODRIVER", saved_driver.c_str(), TRUE);
257 else
258 g_unsetenv("SDL_AUDIODRIVER");
260 gd_sound_init();
264 /* this function saves the wav file, and also does the timing! */
265 void ReplaySaverActivity::mixfunc(void *udata, Uint8 *stream, int len) {
266 ReplaySaverActivity *rs = static_cast<ReplaySaverActivity *>(udata);
268 if (!rs->wavfile)
269 return;
270 if (fwrite(stream, 1, len, rs->wavfile) != size_t(len))
271 gd_critical("Cannot write to wav file!");
273 // push a user event -> to do the timing
274 SDL_Event ev;
275 ev.type = SDL_USEREVENT + 1;
276 SDL_PushEvent(&ev);
278 rs->wavlen += len;
281 #include <typeinfo>
283 void ReplaySaverActivity::redraw_event(bool full) const {
284 app->clear_screen();
286 // TRANSLATORS: Title line capitalization in English
287 app->title_line(_("Saving Replay"));
288 app->status_line(_("Please wait"));
290 // show it to the user.
291 // it is not in displayformat, but a small image - not that slow to draw.
292 // center coordinates
293 int x = (app->screen->get_width() - pm.get_width()) / 2;
294 int y = (app->screen->get_height() - pm.get_height()) / 2;
295 Pixbuf const *pb = pm.create_pixbuf_screenshot();
296 app->screen->blit_pixbuf(*pb, x, y, false);
297 delete pb;
299 app->screen->drawing_finished();
303 void ReplaySaverActivity::timer2_event() {
304 /* iterate and see what happened */
305 /* give no gameinputhandler to the renderer */
306 GameRenderer::State state = gamerenderer.main_int(40, false, NULL);
307 gamerenderer.draw(pm.must_redraw_all_before_flip());
308 pm.do_the_flip();
309 switch (state) {
310 case GameRenderer::Nothing:
311 break;
313 case GameRenderer::Stop: /* game stopped, this could be a replay or a snapshot */
314 case GameRenderer::GameOver: /* game over should not happen for a replay, but no problem */
315 app->enqueue_command(new PopActivityCommand(app));
316 break;
319 /* before incrementing frame number, check if to save the frame to disk. */
320 pm.save(CPrintf("%s_%08d.png") % filename_prefix.c_str() % frame);
322 queue_redraw();
323 frame++;
326 #endif /* IFDEF HAVE_SDL */