1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14
15 #define IS_RECORD_UNIT
16 #include "ac/common.h"
17 #include "media/audio/audiodefines.h"
18 #include "ac/game.h"
19 #include "ac/gamesetupstruct.h"
20 #include "ac/gamestate.h"
21 #include "ac/global_display.h"
22 #include "ac/global_game.h"
23 #include "ac/keycode.h"
24 #include "ac/mouse.h"
25 #include "ac/record.h"
26 #include "game/savegame.h"
27 #include "main/main.h"
28 #include "media/audio/soundclip.h"
29 #include "util/string_utils.h"
30 #include "gfx/gfxfilter.h"
31 #include "device/mousew32.h"
32 #include "util/filestream.h"
33
34 using namespace AGS::Common;
35 using namespace AGS::Engine;
36
37 extern GameSetupStruct game;
38 extern GameState play;
39 extern int disable_mgetgraphpos;
40 extern int mousex,mousey;
41 extern unsigned int loopcounter,lastcounter;
42 extern volatile unsigned long globalTimerCounter;
43 extern SOUNDCLIP *channels[MAX_SOUND_CHANNELS+1];
44 extern int pluginSimulatedClick;
45 extern int displayed_room;
46 extern char check_dynamic_sprites_at_exit;
47
48 char replayfile[MAX_PATH] = "record.dat";
49 int replay_time = 0;
50 unsigned long replay_last_second = 0;
51 int replay_start_this_time = 0;
52
53 short *recordbuffer = NULL;
54 int recbuffersize = 0, recsize = 0;
55
56 const char *replayTempFile = "~replay.tmp";
57
58 int mouse_z_was = 0;
59
write_record_event(int evnt,int dlen,short * dbuf)60 void write_record_event (int evnt, int dlen, short *dbuf) {
61
62 recordbuffer[recsize] = play.gamestep;
63 recordbuffer[recsize+1] = evnt;
64
65 for (int i = 0; i < dlen; i++)
66 recordbuffer[recsize + i + 2] = dbuf[i];
67 recsize += dlen + 2;
68
69 if (recsize >= recbuffersize - 100) {
70 recbuffersize += 10000;
71 recordbuffer = (short*)realloc (recordbuffer, recbuffersize * sizeof(short));
72 }
73
74 play.gamestep++;
75 }
disable_replay_playback()76 void disable_replay_playback () {
77 play.playback = 0;
78 if (recordbuffer)
79 free (recordbuffer);
80 recordbuffer = NULL;
81 disable_mgetgraphpos = 0;
82 }
83
done_playback_event(int size)84 void done_playback_event (int size) {
85 recsize += size;
86 play.gamestep++;
87 if ((recsize >= recbuffersize) || (recordbuffer[recsize+1] == REC_ENDOFFILE))
88 disable_replay_playback();
89 }
90
rec_getch()91 int rec_getch () {
92 if (play.playback) {
93 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_GETCH)) {
94 int toret = recordbuffer[recsize + 2];
95 done_playback_event (3);
96 return toret;
97 }
98 // Since getch() waits for a key to be pressed, if we have no
99 // record for it we're out of sync
100 quit("out of sync in playback in getch");
101 }
102 int result = my_readkey();
103 if (play.recording) {
104 short buff[1] = {static_cast<short>(result)};
105 write_record_event (REC_GETCH, 1, buff);
106 }
107
108 return result;
109 }
110
rec_kbhit()111 int rec_kbhit () {
112 if ((play.playback) && (recordbuffer != NULL)) {
113 // check for real keypresses to abort the replay
114 if (keypressed()) {
115 if (my_readkey() == 27) {
116 disable_replay_playback();
117 return 0;
118 }
119 }
120 // now simulate the keypresses
121 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_KBHIT)) {
122 done_playback_event (2);
123 return 1;
124 }
125 return 0;
126 }
127 int result = keypressed();
128 if ((result) && (globalTimerCounter < play.ignore_user_input_until_time))
129 {
130 // ignoring user input
131 my_readkey();
132 result = 0;
133 }
134 if ((result) && (play.recording)) {
135 write_record_event (REC_KBHIT, 0, NULL);
136 }
137 return result;
138 }
139
140 char playback_keystate[KEY_MAX];
141
rec_iskeypressed(int keycode)142 int rec_iskeypressed (int keycode) {
143
144 if (play.playback) {
145 if ((recordbuffer[recsize] == play.gamestep)
146 && (recordbuffer[recsize + 1] == REC_KEYDOWN)
147 && (recordbuffer[recsize + 2] == keycode)) {
148 playback_keystate[keycode] = recordbuffer[recsize + 3];
149 done_playback_event (4);
150 }
151 return playback_keystate[keycode];
152 }
153
154 int toret = key[keycode];
155
156 if (play.recording) {
157 if (toret != playback_keystate[keycode]) {
158 short buff[2] = {static_cast<short>(keycode), static_cast<short>(toret)};
159 write_record_event (REC_KEYDOWN, 2, buff);
160 playback_keystate[keycode] = toret;
161 }
162 }
163
164 return toret;
165 }
166
rec_isSpeechFinished()167 int rec_isSpeechFinished () {
168 if (play.playback) {
169 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_SPEECHFINISHED)) {
170 done_playback_event (2);
171 return 1;
172 }
173 return 0;
174 }
175
176 if (!channels[SCHAN_SPEECH]->done) {
177 return 0;
178 }
179 if (play.recording)
180 write_record_event (REC_SPEECHFINISHED, 0, NULL);
181 return 1;
182 }
183
184 int recbutstate[4] = {-1, -1, -1, -1};
rec_misbuttondown(int but)185 int rec_misbuttondown (int but) {
186 if (play.playback) {
187 if ((recordbuffer[recsize] == play.gamestep)
188 && (recordbuffer[recsize + 1] == REC_MOUSEDOWN)
189 && (recordbuffer[recsize + 2] == but)) {
190 recbutstate[but] = recordbuffer[recsize + 3];
191 done_playback_event (4);
192 }
193 return recbutstate[but];
194 }
195 int result = misbuttondown (but);
196 if (play.recording) {
197 if (result != recbutstate[but]) {
198 short buff[2] = {static_cast<short>(but), static_cast<short>(result)};
199 write_record_event (REC_MOUSEDOWN, 2, buff);
200 recbutstate[but] = result;
201 }
202 }
203 return result;
204 }
205
rec_mgetbutton()206 int rec_mgetbutton() {
207
208 if ((play.playback) && (recordbuffer != NULL)) {
209 if ((recordbuffer[recsize] < play.gamestep) && (play.gamestep < 32766))
210 quit("Playback error: out of sync");
211 if (loopcounter >= replay_last_second + 40) {
212 replay_time ++;
213 replay_last_second += 40;
214 }
215 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_MOUSECLICK)) {
216 Mouse::SetPosition(Point(recordbuffer[recsize+3], recordbuffer[recsize+4]));
217 disable_mgetgraphpos = 0;
218 mgetgraphpos ();
219 disable_mgetgraphpos = 1;
220 int toret = recordbuffer[recsize + 2];
221 done_playback_event (5);
222 return toret;
223 }
224 return NONE;
225 }
226
227 int result;
228
229 if (pluginSimulatedClick > NONE) {
230 result = pluginSimulatedClick;
231 pluginSimulatedClick = NONE;
232 }
233 else {
234 result = mgetbutton();
235 }
236
237 if ((result >= 0) && (globalTimerCounter < play.ignore_user_input_until_time))
238 {
239 // ignoring user input
240 result = NONE;
241 }
242
243 if (play.recording) {
244 if (result >= 0) {
245 short buff[3] = {static_cast<short>(result), static_cast<short>(mousex), static_cast<short>(mousey)};
246 write_record_event (REC_MOUSECLICK, 3, buff);
247 }
248 if (loopcounter >= replay_last_second + 40) {
249 replay_time ++;
250 replay_last_second += 40;
251 }
252 }
253 return result;
254 }
255
rec_domouse(int what)256 void rec_domouse (int what) {
257
258 if (play.recording) {
259 int mxwas = mousex, mywas = mousey;
260 if (what == DOMOUSE_NOCURSOR)
261 mgetgraphpos();
262 else
263 domouse(what);
264
265 if ((mxwas != mousex) || (mywas != mousey)) {
266 // don't divide down the co-ordinates, because we lose
267 // the precision, and it might click the wrong thing
268 // if eg. hi-res 71 -> 35 in record file -> 70 in playback
269 short buff[2] = {static_cast<short>(mousex), static_cast<short>(mousey)};
270 write_record_event (REC_MOUSEMOVE, 2, buff);
271 }
272 return;
273 }
274 else if ((play.playback) && (recordbuffer != NULL)) {
275 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_MOUSEMOVE)) {
276 Mouse::SetPosition(Point(recordbuffer[recsize+2], recordbuffer[recsize+3]));
277 disable_mgetgraphpos = 0;
278 if (what == DOMOUSE_NOCURSOR)
279 mgetgraphpos();
280 else
281 domouse(what);
282 disable_mgetgraphpos = 1;
283 done_playback_event (4);
284 return;
285 }
286 }
287 if (what == DOMOUSE_NOCURSOR)
288 mgetgraphpos();
289 else
290 domouse(what);
291 }
292
check_mouse_wheel()293 int check_mouse_wheel () {
294 if ((play.playback) && (recordbuffer != NULL)) {
295 if ((recordbuffer[recsize] == play.gamestep) && (recordbuffer[recsize + 1] == REC_MOUSEWHEEL)) {
296 int toret = recordbuffer[recsize+2];
297 done_playback_event (3);
298 return toret;
299 }
300 return 0;
301 }
302
303 int result = 0;
304 if ((mouse_z != mouse_z_was) && (game.options[OPT_MOUSEWHEEL] != 0)) {
305 if (mouse_z > mouse_z_was)
306 result = 1;
307 else
308 result = -1;
309 mouse_z_was = mouse_z;
310 }
311
312 if ((play.recording) && (result)) {
313 short buff[1] = {static_cast<short>(result)};
314 write_record_event (REC_MOUSEWHEEL, 1, buff);
315 }
316
317 return result;
318 }
319
start_recording()320 void start_recording() {
321 if (play.playback) {
322 play.recording = 0; // stop quit() crashing
323 play.playback = 0;
324 quit("!playback and recording of replay selected simultaneously");
325 }
326
327 srand (play.randseed);
328 play.gamestep = 0;
329
330 recbuffersize = 10000;
331 recordbuffer = (short*)malloc (recbuffersize * sizeof(short));
332 recsize = 0;
333 memset (playback_keystate, -1, KEY_MAX);
334 replay_last_second = loopcounter;
335 replay_time = 0;
336 strcpy (replayfile, "New.agr");
337 }
338
start_replay_record()339 void start_replay_record () {
340 Stream *replay_s = Common::File::CreateFile(replayTempFile);
341 SaveGameState(replay_s);
342 delete replay_s;
343 start_recording();
344 play.recording = 1;
345 }
346
stop_recording()347 void stop_recording() {
348 if (!play.recording)
349 return;
350
351 write_record_event (REC_ENDOFFILE, 0, NULL);
352
353 play.recording = 0;
354 char replaydesc[100] = "";
355 sc_inputbox ("Enter replay description:", replaydesc);
356 sc_inputbox ("Enter replay filename:", replayfile);
357 if (replayfile[0] == 0)
358 strcpy (replayfile, "Untitled");
359 if (strchr (replayfile, '.') != NULL)
360 strchr (replayfile, '.')[0] = 0;
361 strcat (replayfile, ".agr");
362
363 Stream *replay_out = Common::File::CreateFile(replayfile);
364 replay_out->Write ("AGSRecording", 12);
365 fputstring (EngineVersion.LongString, replay_out);
366 int write_version = 2;
367 Stream *replay_temp_in = Common::File::OpenFileRead(replayTempFile);
368 if (replay_temp_in) {
369 // There was a save file created
370 write_version = 3;
371 }
372 replay_out->WriteInt32 (write_version);
373
374 fputstring (game.gamename, replay_out);
375 replay_out->WriteInt32 (game.uniqueid);
376 replay_out->WriteInt32 (replay_time);
377 fputstring (replaydesc, replay_out); // replay description, maybe we'll use this later
378 replay_out->WriteInt32 (play.randseed);
379 if (write_version >= 3)
380 replay_out->WriteInt32 (recsize);
381 replay_out->WriteArrayOfInt16 (recordbuffer, recsize);
382 if (replay_temp_in) {
383 replay_out->WriteInt32 (1); // yes there is a save present
384 int lenno = replay_temp_in->GetLength();
385 char *tbufr = (char*)malloc (lenno);
386 replay_temp_in->Read (tbufr, lenno);
387 replay_out->Write (tbufr, lenno);
388 free (tbufr);
389 delete replay_temp_in;
390 unlink (replayTempFile);
391 }
392 else if (write_version >= 3) {
393 replay_out->WriteInt32 (0);
394 }
395 delete replay_out;
396
397 free (recordbuffer);
398 recordbuffer = NULL;
399 }
400
start_playback()401 void start_playback()
402 {
403 Stream *in = Common::File::OpenFileRead(replayfile);
404 if (in != NULL) {
405 char buffer [100];
406 in->Read(buffer, 12);
407 buffer[12] = 0;
408 if (strcmp (buffer, "AGSRecording") != 0) {
409 Display("ERROR: Invalid recorded data file");
410 play.playback = 0;
411 }
412 else {
413 String version_string = String::FromStream(in, 12);
414 AGS::Engine::Version requested_engine_version(version_string);
415 if (requested_engine_version.Major != '2')
416 quit("!Replay file is from an old version of AGS");
417 if (requested_engine_version < AGS::Engine::Version(2, 55, 553))
418 quit("!Replay file was recorded with an older incompatible version");
419
420 if (requested_engine_version != EngineVersion) {
421 // Disable text as speech while displaying the warning message
422 // This happens if the user's graphics card does BGR order 16-bit colour
423 int oldalways = game.options[OPT_ALWAYSSPCH];
424 game.options[OPT_ALWAYSSPCH] = 0;
425 play.playback = 0;
426 Display("Warning! replay is from a different version of AGS (%s) - it may not work properly.", buffer);
427 play.playback = 1;
428 srand (play.randseed);
429 play.gamestep = 0;
430 game.options[OPT_ALWAYSSPCH] = oldalways;
431 }
432
433 int replayver = in->ReadInt32();
434
435 if ((replayver < 1) || (replayver > 3))
436 quit("!Unsupported Replay file version");
437
438 if (replayver >= 2) {
439 fgetstring_limit (buffer, in, 99);
440 int uid = in->ReadInt32 ();
441 if ((strcmp (buffer, game.gamename) != 0) || (uid != game.uniqueid)) {
442 char msg[150];
443 sprintf (msg, "!This replay is meant for the game '%s' and will not work correctly with this game.", buffer);
444 delete in;
445 quit (msg);
446 }
447 // skip the total time
448 in->ReadInt32 ();
449 // replay description, maybe we'll use this later
450 fgetstring_limit (buffer, in, 99);
451 }
452
453 play.randseed = in->ReadInt32();
454 int flen = in->GetLength() - in->GetPosition ();
455 if (replayver >= 3) {
456 flen = in->ReadInt32() * sizeof(short);
457 }
458 recordbuffer = (short*)malloc (flen);
459 in->Read(recordbuffer, flen);
460 srand (play.randseed);
461 recbuffersize = flen / sizeof(short);
462 recsize = 0;
463 disable_mgetgraphpos = 1;
464 replay_time = 0;
465 replay_last_second = loopcounter;
466 if (replayver >= 3) {
467 int issave = in->ReadInt32();
468 if (issave) {
469 if (RestoreGameState(in, kSvgVersion_321) != kSvgErr_NoError)
470 quit("!Error running replay... could be incorrect game version");
471 replay_last_second = loopcounter;
472 }
473 }
474 delete in;
475 }
476 }
477 else // file not found
478 play.playback = 0;
479 }
480
my_readkey()481 int my_readkey() {
482 int gott=readkey();
483 int scancode = ((gott >> 8) & 0x00ff);
484
485 if (gott == READKEY_CODE_ALT_TAB)
486 {
487 // Alt+Tab, it gets stuck down unless we do this
488 return AGS_KEYCODE_ALT_TAB;
489 }
490
491 /* char message[200];
492 sprintf(message, "Scancode: %04X", gott);
493 Debug::Printf(message);*/
494
495 /*if ((scancode >= KEY_0_PAD) && (scancode <= KEY_9_PAD)) {
496 // fix numeric pad keys if numlock is off (allegro 4.2 changed this behaviour)
497 if ((key_shifts & KB_NUMLOCK_FLAG) == 0)
498 gott = (gott & 0xff00) | EXTENDED_KEY_CODE;
499 }*/
500
501 if ((gott & 0x00ff) == EXTENDED_KEY_CODE) {
502 gott = scancode + AGS_EXT_KEY_SHIFT;
503
504 // convert Allegro KEY_* numbers to scan codes
505 // (for backwards compatibility we can't just use the
506 // KEY_* constants now, it's too late)
507 if ((gott>=347) & (gott<=356)) gott+=12;
508 // F11-F12
509 else if ((gott==357) || (gott==358)) gott+=76;
510 // insert / numpad insert
511 else if ((scancode == KEY_0_PAD) || (scancode == KEY_INSERT))
512 gott = AGS_KEYCODE_INSERT;
513 // delete / numpad delete
514 else if ((scancode == KEY_DEL_PAD) || (scancode == KEY_DEL))
515 gott = AGS_KEYCODE_DELETE;
516 // Home
517 else if (gott == 378) gott = 371;
518 // End
519 else if (gott == 379) gott = 379;
520 // PgUp
521 else if (gott == 380) gott = 373;
522 // PgDn
523 else if (gott == 381) gott = 381;
524 // left arrow
525 else if (gott==382) gott=375;
526 // right arrow
527 else if (gott==383) gott=377;
528 // up arrow
529 else if (gott==384) gott=372;
530 // down arrow
531 else if (gott==385) gott=380;
532 // numeric keypad
533 else if (gott==338) gott=379;
534 else if (gott==339) gott=380;
535 else if (gott==340) gott=381;
536 else if (gott==341) gott=375;
537 else if (gott==342) gott=376;
538 else if (gott==343) gott=377;
539 else if (gott==344) gott=371;
540 else if (gott==345) gott=372;
541 else if (gott==346) gott=373;
542 }
543 else
544 {
545 gott = gott & 0x00ff;
546 #if defined(MAC_VERSION)
547 if (scancode==KEY_BACKSPACE) {
548 gott = 8; //j backspace on mac
549 }
550 #endif
551 }
552
553 // Alt+X, abort (but only once game is loaded)
554 if ((gott == play.abort_key) && (displayed_room >= 0)) {
555 check_dynamic_sprites_at_exit = 0;
556 quit("!|");
557 }
558
559 //sprintf(message, "Keypress: %d", gott);
560 //Debug::Printf(message);
561
562 return gott;
563 }
564
clear_input_buffer()565 void clear_input_buffer()
566 {
567 while (rec_kbhit()) rec_getch();
568 while (mgetbutton() != NONE);
569 }
570
wait_until_keypress()571 void wait_until_keypress()
572 {
573 while (!rec_kbhit());
574 rec_getch();
575 }
576