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