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(&copyOfList[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(&copyOfList[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