20130320
[gdash.git] / src / framework / replaysaveractivity.cpp
blob406d15eef9dc93a7f0afe23be0fa637ccc8f4a08
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "config.h"
19 /* the replay saver thing only works in the sdl version */
20 #ifdef HAVE_SDL
22 #include <SDL/SDL_mixer.h>
23 #include <glib/gi18n.h>
25 #include "framework/replaysaveractivity.hpp"
26 #include "framework/commands.hpp"
27 #include "sdl/IMG_savepng.h"
28 #include "cave/gamecontrol.hpp"
29 #include "cave/titleanimation.hpp"
30 #include "cave/caveset.hpp"
31 #include "misc/logger.hpp"
32 #include "misc/printf.hpp"
33 #include "settings.hpp"
34 #include "sound/sound.hpp"
37 void ReplaySaverActivity::SDLInmemoryScreen::set_title(char const *) {
41 void ReplaySaverActivity::SDLInmemoryScreen::configure_size() {
42 if (surface)
43 SDL_FreeSurface(surface);
44 surface = SDL_CreateRGBSurface(0, w, h, 32, 0, 0, 0, 0);
48 ReplaySaverActivity::SDLInmemoryScreen::~SDLInmemoryScreen() {
49 SDL_FreeSurface(surface);
53 void ReplaySaverActivity::SDLInmemoryScreen::save(char const *filename) {
54 IMG_SavePNG(filename, surface, 2); // 2 = not too much compression, but a bit faster than the default
58 ReplaySaverActivity::ReplaySaverActivity(App *app, CaveStored *cave, CaveReplay *replay, std::string const &filename_prefix)
60 Activity(app),
61 filename_prefix(filename_prefix),
62 game(GameControl::new_replay(app->caveset, cave, replay)),
63 pf(),
64 fm(pf, ""),
65 pm(),
66 cellrenderer(pf, gd_theme),
67 gamerenderer(pm, cellrenderer, fm, *game) {
68 int cell_size = cellrenderer.get_cell_size();
69 pm.set_size(cell_size*GAME_RENDERER_SCREEN_SIZE_X, cell_size*GAME_RENDERER_SCREEN_SIZE_Y);
70 gamerenderer.set_show_replay_sign(false);
71 std::string wav_filename = filename_prefix + ".wav";
72 wavfile = fopen(wav_filename.c_str(), "wb");
73 if (!wavfile) {
74 gd_critical(CPrintf("Cannot open %s for sound output") % wav_filename);
75 app->enqueue_command(new PopActivityCommand(app));
76 return;
78 fseek(wavfile, 44, SEEK_SET); /* 44bytes offset: start of data in a wav file */
79 wavlen = 0;
80 frame = 0;
84 void ReplaySaverActivity::shown_event() {
85 std::vector<Pixmap *> animation = get_title_animation_pixmap(app->caveset->title_screen, app->caveset->title_screen_scroll, true, pf);
86 pm.blit(*animation[0], 0, 0);
87 delete animation[0];
88 gd_music_stop();
90 /* enable own timer and sound saver */
91 install_own_mixer();
95 ReplaySaverActivity::~ReplaySaverActivity() {
96 Mix_SetPostMix(NULL, NULL); /* remove wav file saver */
97 uninstall_own_mixer();
98 gd_sound_off();
100 /* write wav header, as now we now its final size. */
101 fseek(wavfile, 0, SEEK_SET);
102 Uint32 out32;
103 Uint16 out16;
105 int i=0;
106 i+=fwrite("RIFF", 1, 4, wavfile); /* "RIFF" */
107 out32=GUINT32_TO_LE(wavlen+36);
108 i+=fwrite(&out32, 1, 4, wavfile); /* 4 + 8+subchunk1size + 8+subchunk2size */
109 i+=fwrite("WAVE", 1, 4, wavfile); /* "WAVE" */
111 i+=fwrite("fmt ", 1, 4, wavfile); /* "fmt " */
112 out32=GUINT32_TO_LE(16);
113 i+=fwrite(&out32, 1, 4, wavfile); /* fmt chunk size=16 bytes */
114 out16=GUINT16_TO_LE(1);
115 i+=fwrite(&out16, 1, 2, wavfile); /* 1=pcm */
116 out16=GUINT16_TO_LE(channels);
117 i+=fwrite(&out16, 1, 2, wavfile);
118 out32=GUINT32_TO_LE(frequency);
119 i+=fwrite(&out32, 1, 4, wavfile);
120 out32=GUINT32_TO_LE(frequency*bits/8*channels);
121 i+=fwrite(&out32, 1, 4, wavfile); /* byterate */
122 out16=GUINT16_TO_LE(bits/8*channels);
123 i+=fwrite(&out16, 1, 2, wavfile); /* blockalign */
124 out16=GUINT16_TO_LE(bits);
125 i+=fwrite(&out16, 1, 2, wavfile); /* bitspersample */
127 i+=fwrite("data", 1, 4, wavfile); /* "data" */
128 out32=GUINT32_TO_LE(wavlen);
129 i+=fwrite(&out32, 1, 4, wavfile); /* actual data length */
130 fclose(wavfile);
132 if (i!=44)
133 gd_critical("Could not write wav header to file!");
135 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;
136 app->show_message(_("Replay Saved"), message);
138 // restore settings
139 gd_show_name_of_game=saved_gd_show_name_of_game;
140 gd_sound_set_music_volume();
141 gd_sound_set_chunk_volumes();
142 gd_music_play_random();
143 delete game;
147 void ReplaySaverActivity::install_own_mixer() {
148 gd_sound_close();
150 /* we setup mixing and other parameters for our own needs. */
151 saved_gd_sdl_sound=gd_sound_enabled;
152 saved_gd_sdl_44khz_mixing=gd_sound_44khz_mixing;
153 saved_gd_sdl_16bit_mixing=gd_sound_16bit_mixing;
154 saved_gd_sound_stereo=gd_sound_stereo;
155 const char *driver=g_getenv("SDL_AUDIODRIVER");
156 if (driver)
157 saved_driver=driver;
159 /* and after tweaking settings to our own need, we can init sdl. */
160 gd_sound_enabled=true;
161 gd_sound_44khz_mixing=true;
162 gd_sound_16bit_mixing=true;
163 gd_sound_stereo=true;
164 // select the dummy driver, as it accepts any format, and we have control
165 g_setenv("SDL_AUDIODRIVER", "dummy", TRUE);
166 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. */
168 /* query audio format from sdl */
169 Uint16 format;
170 Mix_QuerySpec(&frequency, &format, &channels);
171 if (frequency!=44100) /* something must be really going wrong. */
172 gd_critical("Cannot initialize mixer to 44100Hz mixing. The replay saver will not work correctly!");
174 switch (format) {
175 case AUDIO_U8:
176 bits=8;
177 break;
178 case AUDIO_S8:
179 bits=8;
180 break;
181 case AUDIO_U16LSB:
182 bits=16;
183 break;
184 case AUDIO_S16LSB:
185 bits=16;
186 break;
187 case AUDIO_U16MSB:
188 bits=16;
189 break;
190 case AUDIO_S16MSB:
191 bits=16;
192 break;
193 default:
194 g_assert_not_reached();
196 Mix_SetPostMix(mixfunc, this);
200 void ReplaySaverActivity::uninstall_own_mixer() {
201 gd_sound_close();
203 // restore settings
204 gd_sound_enabled=saved_gd_sdl_sound;
205 gd_sound_44khz_mixing=saved_gd_sdl_44khz_mixing;
206 gd_sound_16bit_mixing=saved_gd_sdl_16bit_mixing;
207 gd_sound_stereo=saved_gd_sound_stereo;
209 if (saved_driver!="")
210 g_setenv("SDL_AUDIODRIVER", saved_driver.c_str(), TRUE);
211 else
212 g_unsetenv("SDL_AUDIODRIVER");
214 gd_sound_init();
218 /* this function saves the wav file, and also does the timing! */
219 void ReplaySaverActivity::mixfunc(void *udata, Uint8 *stream, int len) {
220 ReplaySaverActivity *rs=static_cast<ReplaySaverActivity *>(udata);
222 if (!rs->wavfile)
223 return;
224 if (fwrite(stream, 1, len, rs->wavfile)!=size_t(len))
225 gd_critical("Cannot write to wav file!");
227 // push a user event -> to do the timing
228 SDL_Event ev;
229 ev.type=SDL_USEREVENT+1;
230 SDL_PushEvent(&ev);
232 rs->wavlen+=len;
236 void ReplaySaverActivity::redraw_event() {
237 app->clear_screen();
239 app->title_line(_("Saving Replay"));
240 app->status_line(_("Please wait"));
242 // show it to the user.
243 // it is not in displayformat, but a small image - not that slow to draw.
244 // center coordinates
245 int x=(app->screen->get_width()-pm.get_width())/2;
246 int y=(app->screen->get_height()-pm.get_height())/2;
247 // HACK
248 SDL_Rect destr;
249 destr.x = x;
250 destr.y = y;
251 destr.w = 0;
252 destr.h = 0;
253 SDL_BlitSurface(pm.get_surface(), 0, static_cast<SDLScreen *>(app->screen)->get_surface(), &destr);
255 app->screen->flip();
259 void ReplaySaverActivity::timer2_event() {
260 /* iterate and see what happened */
261 /* no movement, no fire, no suicide, no restart, no pause, no fast movement.
262 * the gamerenderer takes the moves from the replay. if we would add a move or a fire
263 * press, the gamerenderer would switch to "continue replay" mode. */
264 bool suicide=false;
265 bool restart=false;
266 GameRenderer::State state = gamerenderer.main_int(40, MV_STILL, false, suicide, restart, false, false, false);
267 switch (state) {
268 case GameRenderer::CaveLoaded:
269 case GameRenderer::Iterated:
270 case GameRenderer::Nothing:
271 break;
273 case GameRenderer::Stop: /* game stopped, this could be a replay or a snapshot */
274 case GameRenderer::GameOver: /* game over should not happen for a replay, but no problem */
275 app->enqueue_command(new PopActivityCommand(app));
276 break;
279 /* before incrementing frame number, check if to save the frame to disk. */
280 char *filename=g_strdup_printf("%s_%08d.png", filename_prefix.c_str(), frame);
281 pm.save(filename);
282 g_free(filename);
284 redraw_event();
285 frame++;
288 #endif /* IFDEF HAVE_SDL */