1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "ags/engine/ac/event.h"
24 #include "ags/shared/ac/common.h"
25 #include "ags/engine/ac/draw.h"
26 #include "ags/shared/ac/game_setup_struct.h"
27 #include "ags/engine/ac/game_state.h"
28 #include "ags/engine/ac/global_game.h"
29 #include "ags/engine/ac/global_room.h"
30 #include "ags/engine/ac/global_screen.h"
31 #include "ags/engine/ac/gui.h"
32 #include "ags/engine/ac/room_status.h"
33 #include "ags/engine/ac/screen.h"
34 #include "ags/shared/script/cc_error.h"
35 #include "ags/engine/platform/base/ags_platform_driver.h"
36 #include "ags/plugins/ags_plugin.h"
37 #include "ags/plugins/plugin_engine.h"
38 #include "ags/engine/script/script.h"
39 #include "ags/shared/gfx/bitmap.h"
40 #include "ags/engine/gfx/ddb.h"
41 #include "ags/engine/gfx/graphics_driver.h"
42 #include "ags/engine/media/audio/audio_system.h"
43 #include "ags/engine/ac/timer.h"
44 #include "ags/globals.h"
45
46 namespace AGS3 {
47
48 using namespace AGS::Shared;
49 using namespace AGS::Engine;
50
run_claimable_event(const char * tsname,bool includeRoom,int numParams,const RuntimeScriptValue * params,bool * eventWasClaimed)51 int run_claimable_event(const char *tsname, bool includeRoom, int numParams, const RuntimeScriptValue *params, bool *eventWasClaimed) {
52 *eventWasClaimed = true;
53 // Run the room script function, and if it is not claimed,
54 // then run the main one
55 // We need to remember the eventClaimed variable's state, in case
56 // this is a nested event
57 int eventClaimedOldValue = _G(eventClaimed);
58 _G(eventClaimed) = EVENT_INPROGRESS;
59 int toret;
60
61 if (includeRoom && _G(roominst)) {
62 toret = RunScriptFunctionIfExists(_G(roominst), tsname, numParams, params);
63 if (_G(abort_engine))
64 return -1;
65
66 if (_G(eventClaimed) == EVENT_CLAIMED) {
67 _G(eventClaimed) = eventClaimedOldValue;
68 return toret;
69 }
70 }
71
72 // run script modules
73 for (int kk = 0; kk < _G(numScriptModules); kk++) {
74 toret = RunScriptFunctionIfExists(_GP(moduleInst)[kk], tsname, numParams, params);
75
76 if (_G(eventClaimed) == EVENT_CLAIMED) {
77 _G(eventClaimed) = eventClaimedOldValue;
78 return toret;
79 }
80 }
81
82 _G(eventClaimed) = eventClaimedOldValue;
83 *eventWasClaimed = false;
84 return 0;
85 }
86
87 // runs the global script on_event function
run_on_event(int evtype,RuntimeScriptValue & wparam)88 void run_on_event(int evtype, RuntimeScriptValue &wparam) {
89 QueueScriptFunction(kScInstGame, "on_event", 2, RuntimeScriptValue().SetInt32(evtype), wparam);
90 }
91
run_room_event(int id)92 void run_room_event(int id) {
93 _G(evblockbasename) = "room";
94
95 if (_GP(thisroom).EventHandlers != nullptr) {
96 run_interaction_script(_GP(thisroom).EventHandlers.get(), id);
97 } else {
98 run_interaction_event(&_G(croom)->intrRoom, id);
99 }
100 }
101
run_event_block_inv(int invNum,int event)102 void run_event_block_inv(int invNum, int event) {
103 _G(evblockbasename) = "inventory%d";
104 if (_G(loaded_game_file_version) > kGameVersion_272) {
105 run_interaction_script(_GP(game).invScripts[invNum].get(), event);
106 } else {
107 run_interaction_event(_GP(game).intrInv[invNum].get(), event);
108 }
109
110 }
111
112 // event list functions
setevent(int evtyp,int ev1,int ev2,int ev3)113 void setevent(int evtyp, int ev1, int ev2, int ev3) {
114 _G(event)[_G(numevents)].type = evtyp;
115 _G(event)[_G(numevents)].data1 = ev1;
116 _G(event)[_G(numevents)].data2 = ev2;
117 _G(event)[_G(numevents)].data3 = ev3;
118 _G(event)[_G(numevents)].player = _GP(game).playercharacter;
119 _G(numevents)++;
120 if (_G(numevents) >= MAXEVENTS) quit("too many events posted");
121 }
122
123 // TODO: this is kind of a hack, which forces event to be processed even if
124 // it was fired from insides of other event processing.
125 // The proper solution would be to do the event processing overhaul in AGS.
force_event(int evtyp,int ev1,int ev2,int ev3)126 void force_event(int evtyp, int ev1, int ev2, int ev3) {
127 if (_G(inside_processevent))
128 runevent_now(evtyp, ev1, ev2, ev3);
129 else
130 setevent(evtyp, ev1, ev2, ev3);
131 }
132
process_event(EventHappened * evp)133 void process_event(EventHappened *evp) {
134 RuntimeScriptValue rval_null;
135 if (evp->type == EV_TEXTSCRIPT) {
136 _G(ccError) = 0;
137 if (evp->data2 > -1000) {
138 QueueScriptFunction(kScInstGame, _G(tsnames)[evp->data1], 1, RuntimeScriptValue().SetInt32(evp->data2));
139 } else {
140 QueueScriptFunction(kScInstGame, _G(tsnames)[evp->data1]);
141 }
142 } else if (evp->type == EV_NEWROOM) {
143 NewRoom(evp->data1);
144 } else if (evp->type == EV_RUNEVBLOCK) {
145 Interaction *evpt = nullptr;
146 PInteractionScripts scriptPtr = nullptr;
147 const char *oldbasename = _G(evblockbasename);
148 int oldblocknum = _G(evblocknum);
149
150 if (evp->data1 == EVB_HOTSPOT) {
151
152 if (_GP(thisroom).Hotspots[evp->data2].EventHandlers != nullptr)
153 scriptPtr = _GP(thisroom).Hotspots[evp->data2].EventHandlers;
154 else
155 evpt = &_G(croom)->intrHotspot[evp->data2];
156
157 _G(evblockbasename) = "hotspot%d";
158 _G(evblocknum) = evp->data2;
159 //Debug::Printf("Running hotspot interaction for hotspot %d, event %d", evp->data2, evp->data3);
160 } else if (evp->data1 == EVB_ROOM) {
161
162 if (_GP(thisroom).EventHandlers != nullptr)
163 scriptPtr = _GP(thisroom).EventHandlers;
164 else
165 evpt = &_G(croom)->intrRoom;
166
167 _G(evblockbasename) = "room";
168 if (evp->data3 == 5) {
169 _G(in_enters_screen)++;
170 run_on_event(GE_ENTER_ROOM, RuntimeScriptValue().SetInt32(_G(displayed_room)));
171
172 }
173 //Debug::Printf("Running room interaction, event %d", evp->data3);
174 }
175
176 if (scriptPtr != nullptr) {
177 run_interaction_script(scriptPtr.get(), evp->data3);
178 } else if (evpt != nullptr) {
179 run_interaction_event(evpt, evp->data3);
180 } else
181 quit("process_event: RunEvBlock: unknown evb type");
182
183 if (_G(abort_engine))
184 return;
185
186 _G(evblockbasename) = oldbasename;
187 _G(evblocknum) = oldblocknum;
188
189 if ((evp->data3 == 5) && (evp->data1 == EVB_ROOM))
190 _G(in_enters_screen)--;
191 } else if (evp->type == EV_FADEIN) {
192 // if they change the transition type before the fadein, make
193 // sure the screen doesn't freeze up
194 _GP(play).screen_is_faded_out = 0;
195
196 // determine the transition style
197 int theTransition = _GP(play).fade_effect;
198
199 if (_GP(play).next_screen_transition >= 0) {
200 // a one-off transition was selected, so use it
201 theTransition = _GP(play).next_screen_transition;
202 _GP(play).next_screen_transition = -1;
203 }
204
205 if (pl_run_plugin_hooks(AGSE_TRANSITIONIN, 0))
206 return;
207
208 if (_GP(play).fast_forward)
209 return;
210
211 const bool ignore_transition = (_GP(play).screen_tint > 0);
212 if (((theTransition == FADE_CROSSFADE) || (theTransition == FADE_DISSOLVE)) &&
213 (_G(saved_viewport_bitmap) == nullptr) && !ignore_transition) {
214 // transition type was not crossfade/dissolve when the screen faded out,
215 // but it is now when the screen fades in (Eg. a save game was restored
216 // with a different setting). Therefore just fade normally.
217 my_fade_out(5);
218 theTransition = FADE_NORMAL;
219 }
220
221 // TODO: use normal coordinates instead of "native_size" and multiply_up_*?
222 const Rect &viewport = _GP(play).GetMainViewport();
223
224 if ((theTransition == FADE_INSTANT) || ignore_transition)
225 set_palette_range(_G(palette), 0, 255, 0);
226 else if (theTransition == FADE_NORMAL) {
227 my_fade_in(_G(palette), 5);
228 } else if (theTransition == FADE_BOXOUT) {
229 if (!_G(gfxDriver)->UsesMemoryBackBuffer()) {
230 _G(gfxDriver)->BoxOutEffect(false, get_fixed_pixel_size(16), 1000 / GetGameSpeed());
231 } else {
232 // First of all we render the game once again and save backbuffer from further editing.
233 // We put temporary bitmap as a new backbuffer for the transition period, and
234 // will be drawing saved image of the game over to that backbuffer, simulating "box-out".
235 set_palette_range(_G(palette), 0, 255, 0);
236 construct_game_scene(true);
237 construct_game_screen_overlay(false);
238 _G(gfxDriver)->RenderToBackBuffer();
239 Bitmap *saved_backbuf = _G(gfxDriver)->GetMemoryBackBuffer();
240 Bitmap *temp_scr = new Bitmap(saved_backbuf->GetWidth(), saved_backbuf->GetHeight(), saved_backbuf->GetColorDepth());
241 _G(gfxDriver)->SetMemoryBackBuffer(temp_scr);
242 temp_scr->Clear();
243 render_to_screen();
244
245 const int speed = get_fixed_pixel_size(16);
246 const int yspeed = viewport.GetHeight() / (viewport.GetWidth() / speed);
247 int boxwid = speed, boxhit = yspeed;
248 while (boxwid < temp_scr->GetWidth()) {
249 boxwid += speed;
250 boxhit += yspeed;
251 boxwid = Math::Clamp(boxwid, 0, viewport.GetWidth());
252 boxhit = Math::Clamp(boxhit, 0, viewport.GetHeight());
253 int lxp = viewport.GetWidth() / 2 - boxwid / 2;
254 int lyp = viewport.GetHeight() / 2 - boxhit / 2;
255 _G(gfxDriver)->Vsync();
256 temp_scr->Blit(saved_backbuf, lxp, lyp, lxp, lyp,
257 boxwid, boxhit);
258 render_to_screen();
259 WaitForNextFrame();
260 }
261 _G(gfxDriver)->SetMemoryBackBuffer(saved_backbuf);
262 }
263 _GP(play).screen_is_faded_out = 0;
264 } else if (theTransition == FADE_CROSSFADE) {
265 if (_GP(game).color_depth == 1)
266 quit("!Cannot use crossfade screen transition in 256-colour games");
267
268 IDriverDependantBitmap *ddb = prepare_screen_for_transition_in();
269
270 int transparency = 254;
271
272 while (transparency > 0) {
273 // do the crossfade
274 ddb->SetTransparency(transparency);
275 invalidate_screen();
276 construct_game_scene(true);
277 construct_game_screen_overlay(false);
278
279 if (transparency > 16) {
280 // on last frame of fade (where transparency < 16), don't
281 // draw the old screen on top
282 _G(gfxDriver)->DrawSprite(0, 0, ddb);
283 }
284 render_to_screen();
285 update_polled_stuff_if_runtime();
286 WaitForNextFrame();
287 transparency -= 16;
288 }
289
290 delete _G(saved_viewport_bitmap);
291 _G(saved_viewport_bitmap) = nullptr;
292 set_palette_range(_G(palette), 0, 255, 0);
293 _G(gfxDriver)->DestroyDDB(ddb);
294 } else if (theTransition == FADE_DISSOLVE) {
295 int pattern[16] = { 0, 4, 14, 9, 5, 11, 2, 8, 10, 3, 12, 7, 15, 6, 13, 1 };
296 int aa, bb, cc;
297 RGB interpal[256];
298
299 IDriverDependantBitmap *ddb = prepare_screen_for_transition_in();
300 for (aa = 0; aa < 16; aa++) {
301 // merge the palette while dithering
302 if (_GP(game).color_depth == 1) {
303 fade_interpolate(_G(old_palette), _G(palette), interpal, aa * 4, 0, 255);
304 set_palette_range(interpal, 0, 255, 0);
305 }
306 // do the dissolving
307 int maskCol = _G(saved_viewport_bitmap)->GetMaskColor();
308 for (bb = 0; bb < viewport.GetWidth(); bb += 4) {
309 for (cc = 0; cc < viewport.GetHeight(); cc += 4) {
310 _G(saved_viewport_bitmap)->PutPixel(bb + pattern[aa] / 4, cc + pattern[aa] % 4, maskCol);
311 }
312 }
313 _G(gfxDriver)->UpdateDDBFromBitmap(ddb, _G(saved_viewport_bitmap), false);
314 construct_game_scene(true);
315 construct_game_screen_overlay(false);
316 _G(gfxDriver)->DrawSprite(0, 0, ddb);
317 render_to_screen();
318 update_polled_stuff_if_runtime();
319 WaitForNextFrame();
320 }
321
322 delete _G(saved_viewport_bitmap);
323 _G(saved_viewport_bitmap) = nullptr;
324 set_palette_range(_G(palette), 0, 255, 0);
325 _G(gfxDriver)->DestroyDDB(ddb);
326 }
327
328 } else if (evp->type == EV_IFACECLICK)
329 process_interface_click(evp->data1, evp->data2, evp->data3);
330 else quit("process_event: unknown event to process");
331 }
332
333
runevent_now(int evtyp,int ev1,int ev2,int ev3)334 void runevent_now(int evtyp, int ev1, int ev2, int ev3) {
335 EventHappened evh;
336 evh.type = evtyp;
337 evh.data1 = ev1;
338 evh.data2 = ev2;
339 evh.data3 = ev3;
340 evh.player = _GP(game).playercharacter;
341 process_event(&evh);
342 }
343
processallevents(int numev,EventHappened * evlist)344 void processallevents(int numev, EventHappened *evlist) {
345 int dd;
346
347 if (_G(inside_processevent))
348 return;
349
350 // make a copy of the events - if processing an event includes
351 // a blocking function it will continue to the next game loop
352 // and wipe out the event pointer we were passed
353 EventHappened copyOfList[MAXEVENTS];
354 memcpy(©OfList[0], &evlist[0], sizeof(EventHappened) * numev);
355
356 int room_was = _GP(play).room_changes;
357
358 _G(inside_processevent)++;
359
360 for (dd = 0; dd < numev; dd++) {
361
362 process_event(©OfList[dd]);
363
364 if (room_was != _GP(play).room_changes || _G(abort_engine))
365 break; // changed room, so discard other events
366 }
367
368 _G(inside_processevent)--;
369 }
370
update_events()371 void update_events() {
372 processallevents(_G(numevents), &_G(event)[0]);
373 _G(numevents) = 0;
374 }
375
376 // end event list functions
377
378
ClaimEvent()379 void ClaimEvent() {
380 if (_G(eventClaimed) == EVENT_NONE)
381 quit("!ClaimEvent: no event to claim");
382
383 _G(eventClaimed) = EVENT_CLAIMED;
384 }
385
386 } // namespace AGS3
387