20130313
[gdash.git] / src / framework / replaysaveractivity.cpp
blob1780a721de878ab83f81fff833c23a133bf04bc3
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)
69 int cell_size = cellrenderer.get_cell_size();
70 pm.set_size(cell_size*GAME_RENDERER_SCREEN_SIZE_X, cell_size*GAME_RENDERER_SCREEN_SIZE_Y);
71 gamerenderer.set_show_replay_sign(false);
72 std::string wav_filename = filename_prefix + ".wav";
73 wavfile = fopen(wav_filename.c_str(), "wb");
74 if (!wavfile) {
75 gd_critical(CPrintf("Cannot open %s for sound output") % wav_filename);
76 app->enqueue_command(new PopActivityCommand(app));
77 return;
79 fseek(wavfile, 44, SEEK_SET); /* 44bytes offset: start of data in a wav file */
80 wavlen = 0;
81 frame = 0;
85 void ReplaySaverActivity::shown_event() {
86 std::vector<Pixmap *> animation = get_title_animation_pixmap(app->caveset->title_screen, app->caveset->title_screen_scroll, true, pf);
87 pm.blit(*animation[0], 0, 0);
88 delete animation[0];
89 gd_music_stop();
91 /* enable own timer and sound saver */
92 install_own_mixer();
96 ReplaySaverActivity::~ReplaySaverActivity() {
97 Mix_SetPostMix(NULL, NULL); /* remove wav file saver */
98 uninstall_own_mixer();
99 gd_sound_off();
101 /* write wav header, as now we now its final size. */
102 fseek(wavfile, 0, SEEK_SET);
103 Uint32 out32;
104 Uint16 out16;
106 int i=0;
107 i+=fwrite("RIFF", 1, 4, wavfile); /* "RIFF" */
108 out32=GUINT32_TO_LE(wavlen+36); 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); i+=fwrite(&out32, 1, 4, wavfile); /* fmt chunk size=16 bytes */
113 out16=GUINT16_TO_LE(1); i+=fwrite(&out16, 1, 2, wavfile); /* 1=pcm */
114 out16=GUINT16_TO_LE(channels); i+=fwrite(&out16, 1, 2, wavfile);
115 out32=GUINT32_TO_LE(frequency); i+=fwrite(&out32, 1, 4, wavfile);
116 out32=GUINT32_TO_LE(frequency*bits/8*channels); i+=fwrite(&out32, 1, 4, wavfile); /* byterate */
117 out16=GUINT16_TO_LE(bits/8*channels); i+=fwrite(&out16, 1, 2, wavfile); /* blockalign */
118 out16=GUINT16_TO_LE(bits); i+=fwrite(&out16, 1, 2, wavfile); /* bitspersample */
120 i+=fwrite("data", 1, 4, wavfile); /* "data" */
121 out32=GUINT32_TO_LE(wavlen); i+=fwrite(&out32, 1, 4, wavfile); /* actual data length */
122 fclose(wavfile);
124 if (i!=44)
125 gd_critical("Could not write wav header to file!");
127 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;
128 app->show_message(_("Replay Saved"), message);
130 // restore settings
131 gd_show_name_of_game=saved_gd_show_name_of_game;
132 gd_sound_set_music_volume();
133 gd_sound_set_chunk_volumes();
134 gd_music_play_random();
135 delete game;
139 void ReplaySaverActivity::install_own_mixer() {
140 gd_sound_close();
142 /* we setup mixing and other parameters for our own needs. */
143 saved_gd_sdl_sound=gd_sound_enabled;
144 saved_gd_sdl_44khz_mixing=gd_sound_44khz_mixing;
145 saved_gd_sdl_16bit_mixing=gd_sound_16bit_mixing;
146 saved_gd_sound_stereo=gd_sound_stereo;
147 const char *driver=g_getenv("SDL_AUDIODRIVER");
148 if (driver)
149 saved_driver=driver;
151 /* and after tweaking settings to our own need, we can init sdl. */
152 gd_sound_enabled=true;
153 gd_sound_44khz_mixing=true;
154 gd_sound_16bit_mixing=true;
155 gd_sound_stereo=true;
156 // select the dummy driver, as it accepts any format, and we have control
157 g_setenv("SDL_AUDIODRIVER", "dummy", TRUE);
158 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. */
160 /* query audio format from sdl */
161 Uint16 format;
162 Mix_QuerySpec(&frequency, &format, &channels);
163 if (frequency!=44100) /* something must be really going wrong. */
164 gd_critical("Cannot initialize mixer to 44100Hz mixing. The replay saver will not work correctly!");
166 switch(format) {
167 case AUDIO_U8: bits=8; break;
168 case AUDIO_S8: bits=8; break;
169 case AUDIO_U16LSB: bits=16; break;
170 case AUDIO_S16LSB: bits=16; break;
171 case AUDIO_U16MSB: bits=16; break;
172 case AUDIO_S16MSB: bits=16; break;
173 default:
174 g_assert_not_reached();
176 Mix_SetPostMix(mixfunc, this);
180 void ReplaySaverActivity::uninstall_own_mixer()
182 gd_sound_close();
184 // restore settings
185 gd_sound_enabled=saved_gd_sdl_sound;
186 gd_sound_44khz_mixing=saved_gd_sdl_44khz_mixing;
187 gd_sound_16bit_mixing=saved_gd_sdl_16bit_mixing;
188 gd_sound_stereo=saved_gd_sound_stereo;
190 if (saved_driver!="")
191 g_setenv("SDL_AUDIODRIVER", saved_driver.c_str(), TRUE);
192 else
193 g_unsetenv("SDL_AUDIODRIVER");
195 gd_sound_init();
199 /* this function saves the wav file, and also does the timing! */
200 void ReplaySaverActivity::mixfunc(void *udata, Uint8 *stream, int len) {
201 ReplaySaverActivity *rs=static_cast<ReplaySaverActivity *>(udata);
203 if (!rs->wavfile)
204 return;
205 if (fwrite(stream, 1, len, rs->wavfile)!=size_t(len))
206 gd_critical("Cannot write to wav file!");
208 // push a user event -> to do the timing
209 SDL_Event ev;
210 ev.type=SDL_USEREVENT+1;
211 SDL_PushEvent(&ev);
213 rs->wavlen+=len;
217 void ReplaySaverActivity::redraw_event() {
218 app->clear_screen();
220 app->title_line("SAVING REPLAY");
221 app->status_line("PLEASE WAIT");
223 // show it to the user.
224 // it is not in displayformat, but a small image - not that slow to draw.
225 // center coordinates
226 int x=(app->screen->get_width()-pm.get_width())/2;
227 int y=(app->screen->get_height()-pm.get_height())/2;
228 // HACK
229 SDL_Rect destr;
230 destr.x = x;
231 destr.y = y;
232 destr.w = 0;
233 destr.h = 0;
234 SDL_BlitSurface(pm.get_surface(), 0, static_cast<SDLScreen*>(app->screen)->get_surface(), &destr);
236 app->screen->flip();
240 void ReplaySaverActivity::timer2_event() {
241 /* iterate and see what happened */
242 /* no movement, no fire, no suicide, no restart, no pause, no fast movement.
243 * the gamerenderer takes the moves from the replay. if we would add a move or a fire
244 * press, the gamerenderer would switch to "continue replay" mode. */
245 bool suicide=false;
246 bool restart=false;
247 GameRenderer::State state = gamerenderer.main_int(40, MV_STILL, false, suicide, restart, false, false, false);
248 switch (state) {
249 case GameRenderer::CaveLoaded:
250 case GameRenderer::Iterated:
251 case GameRenderer::Nothing:
252 break;
254 case GameRenderer::Stop: /* game stopped, this could be a replay or a snapshot */
255 case GameRenderer::GameOver: /* game over should not happen for a replay, but no problem */
256 app->enqueue_command(new PopActivityCommand(app));
257 break;
260 /* before incrementing frame number, check if to save the frame to disk. */
261 char *filename=g_strdup_printf("%s_%08d.png", filename_prefix.c_str(), frame);
262 pm.save(filename);
263 g_free(filename);
265 redraw_event();
266 frame++;
269 #endif /* IFDEF HAVE_SDL */