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/lib/allegro.h"
24 #include "ags/lib/std/vector.h"
25 #include "ags/shared/core/platform.h"
26 #include "ags/plugins/ags_plugin.h"
27 #include "ags/plugins/core/core.h"
28 #include "ags/shared/ac/common.h"
29 #include "ags/shared/ac/view.h"
30 #include "ags/engine/ac/character_cache.h"
31 #include "ags/engine/ac/display.h"
32 #include "ags/engine/ac/draw.h"
33 #include "ags/engine/ac/dynamic_sprite.h"
34 #include "ags/engine/ac/game.h"
35 #include "ags/engine/ac/game_setup.h"
36 #include "ags/shared/ac/game_setup_struct.h"
37 #include "ags/engine/ac/game_state.h"
38 #include "ags/engine/ac/global_audio.h"
39 #include "ags/engine/ac/global_walkable_area.h"
40 #include "ags/shared/ac/keycode.h"
41 #include "ags/engine/ac/mouse.h"
42 #include "ags/engine/ac/move_list.h"
43 #include "ags/engine/ac/object_cache.h"
44 #include "ags/engine/ac/parser.h"
45 #include "ags/engine/ac/path_helper.h"
46 #include "ags/engine/ac/room_status.h"
47 #include "ags/engine/ac/string.h"
48 #include "ags/shared/ac/sprite_cache.h"
49 #include "ags/engine/ac/dynobj/cc_dynamic_object_addr_and_manager.h"
50 #include "ags/engine/ac/dynobj/script_string.h"
51 #include "ags/shared/font/fonts.h"
52 #include "ags/engine/debugging/debug_log.h"
53 #include "ags/engine/debugging/debugger.h"
54 #include "ags/shared/debugging/out.h"
55 #include "ags/engine/device/mouse_w32.h"
56 #include "ags/shared/gfx/bitmap.h"
57 #include "ags/engine/gfx/graphics_driver.h"
58 #include "ags/engine/gfx/gfx_util.h"
59 #include "ags/engine/gfx/gfxfilter.h"
60 #include "ags/shared/gui/gui_defines.h"
61 #include "ags/engine/main/game_run.h"
62 #include "ags/engine/main/graphics_mode.h"
63 #include "ags/engine/main/engine.h"
64 #include "ags/engine/media/audio/audio_system.h"
65 #include "ags/plugins/plugin_engine.h"
66 #include "ags/plugins/plugin_object_reader.h"
67 #include "ags/engine/script/runtime_script_value.h"
68 #include "ags/engine/script/script.h"
69 #include "ags/engine/script/script_runtime.h"
70 #include "ags/shared/util/file_stream.h"
71 #include "ags/engine/util/library.h"
72 #include "ags/engine/util/library_scummvm.h"
73 #include "ags/shared/util/memory.h"
74 #include "ags/shared/util/stream.h"
75 #include "ags/shared/util/string_compat.h"
76 #include "ags/shared/util/wgt2_allg.h"
77 
78 namespace AGS3 {
79 
80 using namespace AGS::Shared;
81 using namespace AGS::Shared::Memory;
82 using namespace AGS::Engine;
83 
84 const int PLUGIN_API_VERSION = 25;
85 
86 // On save/restore, the Engine will provide the plugin with a handle. Because we only ever save to one file at a time,
87 // we can reuse the same handle.
88 
PluginSimulateMouseClick(int pluginButtonID)89 void PluginSimulateMouseClick(int pluginButtonID) {
90 	_G(pluginSimulatedClick) = pluginButtonID - 1;
91 }
92 
AbortGame(const char * reason)93 void IAGSEngine::AbortGame(const char *reason) {
94 	quit(reason);
95 }
GetEngineVersion()96 const char *IAGSEngine::GetEngineVersion() {
97 	return get_engine_version();
98 }
RegisterScriptFunction(const char * name,Plugins::ScriptContainer * instance)99 void IAGSEngine::RegisterScriptFunction(const char *name, Plugins::ScriptContainer *instance) {
100 	ccAddExternalPluginFunction(name, instance);
101 }
RegisterBuiltInFunction(const char * name,Plugins::ScriptContainer * instance)102 void IAGSEngine::RegisterBuiltInFunction(const char *name, Plugins::ScriptContainer *instance) {
103 	ccAddExternalFunctionForPlugin(name, instance);
104 }
GetGraphicsDriverID()105 const char *IAGSEngine::GetGraphicsDriverID() {
106 	if (_G(gfxDriver) == nullptr)
107 		return nullptr;
108 
109 	return _G(gfxDriver)->GetDriverID();
110 }
111 
GetScreen()112 BITMAP *IAGSEngine::GetScreen() {
113 	// TODO: we could actually return stage buffer here, will that make a difference?
114 	if (!_G(gfxDriver)->UsesMemoryBackBuffer())
115 		quit("!This plugin requires software graphics driver.");
116 
117 	Bitmap *buffer = _G(gfxDriver)->GetMemoryBackBuffer();
118 	return buffer ? (BITMAP *)buffer->GetAllegroBitmap() : nullptr;
119 }
120 
GetVirtualScreen()121 BITMAP *IAGSEngine::GetVirtualScreen() {
122 	Bitmap *stage = _G(gfxDriver)->GetStageBackBuffer(true);
123 	return stage ? (BITMAP *)stage->GetAllegroBitmap() : nullptr;
124 }
125 
RequestEventHook(int32 event)126 void IAGSEngine::RequestEventHook(int32 event) {
127 	if (event >= AGSE_TOOHIGH)
128 		quit("!IAGSEngine::RequestEventHook: invalid event requested");
129 
130 	if ((event & AGSE_SCRIPTDEBUG) &&
131 	        ((_GP(plugins)[this->pluginId].wantHook & AGSE_SCRIPTDEBUG) == 0)) {
132 		_G(pluginsWantingDebugHooks)++;
133 		ccSetDebugHook(scriptDebugHook);
134 	}
135 
136 	if (event & AGSE_AUDIODECODE) {
137 		quit("Plugin requested AUDIODECODE, which is no longer supported");
138 	}
139 
140 	_GP(plugins)[this->pluginId].wantHook |= event;
141 }
142 
UnrequestEventHook(int32 event)143 void IAGSEngine::UnrequestEventHook(int32 event) {
144 	if (event >= AGSE_TOOHIGH)
145 		quit("!IAGSEngine::UnrequestEventHook: invalid event requested");
146 
147 	if ((event & AGSE_SCRIPTDEBUG) &&
148 	        (_GP(plugins)[this->pluginId].wantHook & AGSE_SCRIPTDEBUG)) {
149 		_G(pluginsWantingDebugHooks)--;
150 		if (_G(pluginsWantingDebugHooks) < 1)
151 			ccSetDebugHook(nullptr);
152 	}
153 
154 	_GP(plugins)[this->pluginId].wantHook &= ~event;
155 }
156 
GetSavedData(char * buffer,int32 bufsize)157 int IAGSEngine::GetSavedData(char *buffer, int32 bufsize) {
158 	int savedatasize = _GP(plugins)[this->pluginId].savedatasize;
159 
160 	if (bufsize < savedatasize)
161 		quit("!IAGSEngine::GetSavedData: buffer too small");
162 
163 	if (savedatasize > 0)
164 		memcpy(buffer, _GP(plugins)[this->pluginId].savedata, savedatasize);
165 
166 	return savedatasize;
167 }
168 
DrawText(int32 x,int32 y,int32 font,int32 color,const char * text)169 void IAGSEngine::DrawText(int32 x, int32 y, int32 font, int32 color, const char *text) {
170 	Bitmap *ds = _G(gfxDriver)->GetStageBackBuffer(true);
171 	if (!ds)
172 		return;
173 	color_t text_color = ds->GetCompatibleColor(color);
174 	draw_and_invalidate_text(ds, x, y, font, text_color, text);
175 }
176 
GetScreenDimensions(int32 * width,int32 * height,int32 * coldepth)177 void IAGSEngine::GetScreenDimensions(int32 *width, int32 *height, int32 *coldepth) {
178 	if (width != nullptr)
179 		width[0] = _GP(play).GetMainViewport().GetWidth();
180 	if (height != nullptr)
181 		height[0] = _GP(play).GetMainViewport().GetHeight();
182 	if (coldepth != nullptr)
183 		coldepth[0] = _GP(scsystem).coldepth;
184 }
185 
GetBitmapPitch(BITMAP * bmp)186 int IAGSEngine::GetBitmapPitch(BITMAP *bmp) {
187 	return bmp->pitch;
188 }
189 
GetRawBitmapSurface(BITMAP * bmp)190 uint8 *IAGSEngine::GetRawBitmapSurface(BITMAP *bmp) {
191 	Bitmap *stage = _G(gfxDriver)->GetStageBackBuffer(true);
192 	if (stage && bmp == stage->GetAllegroBitmap())
193 		_GP(plugins)[this->pluginId].invalidatedRegion = 0;
194 
195 	return (uint8 *)bmp->getPixels();
196 }
197 
ReleaseBitmapSurface(BITMAP * bmp)198 void IAGSEngine::ReleaseBitmapSurface(BITMAP *bmp) {
199 	Bitmap *stage = _G(gfxDriver)->GetStageBackBuffer(true);
200 	if (stage && bmp == stage->GetAllegroBitmap()) {
201 		// plugin does not manaually invalidate stuff, so
202 		// we must invalidate the whole screen to be safe
203 		if (!_GP(plugins)[this->pluginId].invalidatedRegion)
204 			invalidate_screen();
205 	}
206 }
207 
GetMousePosition(int32 * x,int32 * y)208 void IAGSEngine::GetMousePosition(int32 *x, int32 *y) {
209 	if (x) x[0] = _G(mousex);
210 	if (y) y[0] = _G(mousey);
211 }
212 
GetCurrentRoom()213 int IAGSEngine::GetCurrentRoom() {
214 	return _G(displayed_room);
215 }
216 
GetNumBackgrounds()217 int IAGSEngine::GetNumBackgrounds() {
218 	return _GP(thisroom).BgFrameCount;
219 }
220 
GetCurrentBackground()221 int IAGSEngine::GetCurrentBackground() {
222 	return _GP(play).bg_frame;
223 }
224 
GetBackgroundScene(int32 index)225 BITMAP *IAGSEngine::GetBackgroundScene(int32 index) {
226 	return (BITMAP *)_GP(thisroom).BgFrames[index].Graphic->GetAllegroBitmap();
227 }
228 
GetBitmapDimensions(BITMAP * bmp,int32 * width,int32 * height,int32 * coldepth)229 void IAGSEngine::GetBitmapDimensions(BITMAP *bmp, int32 *width, int32 *height, int32 *coldepth) {
230 	if (bmp == nullptr)
231 		return;
232 
233 	if (width != nullptr)
234 		width[0] = bmp->w;
235 	if (height != nullptr)
236 		height[0] = bmp->h;
237 	if (coldepth != nullptr)
238 		coldepth[0] = bitmap_color_depth(bmp);
239 }
240 
pl_set_file_handle(long data,Stream * stream)241 void pl_set_file_handle(long data, Stream *stream) {
242 	_G(pl_file_handle) = data;
243 	_G(pl_file_stream) = stream;
244 }
245 
pl_clear_file_handle()246 void pl_clear_file_handle() {
247 	_G(pl_file_handle) = -1;
248 	_G(pl_file_stream) = nullptr;
249 }
250 
FRead(void * buffer,int32 len,int32 handle)251 int IAGSEngine::FRead(void *buffer, int32 len, int32 handle) {
252 	if (handle != _G(pl_file_handle)) {
253 		quitprintf("IAGSEngine::FRead: invalid file handle: %d", handle);
254 	}
255 	if (!_G(pl_file_stream)) {
256 		quit("IAGSEngine::FRead: file stream not set");
257 	}
258 	return _G(pl_file_stream)->Read(buffer, len);
259 }
260 
FWrite(void * buffer,int32 len,int32 handle)261 int IAGSEngine::FWrite(void *buffer, int32 len, int32 handle) {
262 	if (handle != _G(pl_file_handle)) {
263 		quitprintf("IAGSEngine::FWrite: invalid file handle: %d", handle);
264 	}
265 	if (!_G(pl_file_stream)) {
266 		quit("IAGSEngine::FWrite: file stream not set");
267 	}
268 	return _G(pl_file_stream)->Write(buffer, len);
269 }
270 
FSeek(soff_t offset,int origin,int32 handle)271 bool IAGSEngine::FSeek(soff_t offset, int origin, int32 handle) {
272 	if (handle != _G(pl_file_handle)) {
273 		quitprintf("IAGSEngine::FWrite: invalid file handle: %d", handle);
274 	}
275 	if (!_G(pl_file_stream)) {
276 		quit("IAGSEngine::FWrite: file stream not set");
277 	}
278 	return _G(pl_file_stream)->Seek(offset, (AGS::Shared::StreamSeek)origin);
279 }
280 
DrawTextWrapped(int32 xx,int32 yy,int32 wid,int32 font,int32 color,const char * text)281 void IAGSEngine::DrawTextWrapped(int32 xx, int32 yy, int32 wid, int32 font, int32 color, const char *text) {
282 	// TODO: use generic function from the engine instead of having copy&pasted code here
283 	int linespacing = getfontspacing_outlined(font);
284 
285 	if (break_up_text_into_lines(text, _GP(Lines), wid, font) == 0)
286 		return;
287 
288 	Bitmap *ds = _G(gfxDriver)->GetStageBackBuffer(true);
289 	if (!ds)
290 		return;
291 	color_t text_color = ds->GetCompatibleColor(color);
292 	data_to_game_coords((int *)&xx, (int *)&yy); // stupid! quick tweak
293 	for (size_t i = 0; i < _GP(Lines).Count(); i++)
294 		draw_and_invalidate_text(ds, xx, yy + linespacing * i, font, text_color, _GP(Lines)[i].GetCStr());
295 }
296 
297 Bitmap glVirtualScreenWrap;
SetVirtualScreen(BITMAP * bmp)298 void IAGSEngine::SetVirtualScreen(BITMAP *bmp) {
299 	if (!_G(gfxDriver)->UsesMemoryBackBuffer()) {
300 		debug_script_warn("SetVirtualScreen: this plugin requires software graphics driver to work correctly.");
301 		// we let it continue since gfxDriver is supposed to ignore this request without throwing an exception
302 	}
303 
304 	if (bmp) {
305 		glVirtualScreenWrap.WrapAllegroBitmap(bmp, true);
306 		_G(gfxDriver)->SetMemoryBackBuffer(&glVirtualScreenWrap);
307 	} else {
308 		glVirtualScreenWrap.Destroy();
309 		_G(gfxDriver)->SetMemoryBackBuffer(nullptr);
310 	}
311 }
312 
LookupParserWord(const char * word)313 int IAGSEngine::LookupParserWord(const char *word) {
314 	return find_word_in_dictionary(word);
315 }
316 
BlitBitmap(int32 x,int32 y,BITMAP * bmp,int32 masked)317 void IAGSEngine::BlitBitmap(int32 x, int32 y, BITMAP *bmp, int32 masked) {
318 	Bitmap *ds = _G(gfxDriver)->GetStageBackBuffer(true);
319 	if (!ds)
320 		return;
321 	wputblock_raw(ds, x, y, bmp, masked);
322 	invalidate_rect(x, y, x + bmp->w, y + bmp->h, false);
323 }
324 
BlitSpriteTranslucent(int32 x,int32 y,BITMAP * bmp,int32 trans)325 void IAGSEngine::BlitSpriteTranslucent(int32 x, int32 y, BITMAP *bmp, int32 trans) {
326 	Bitmap *ds = _G(gfxDriver)->GetStageBackBuffer(true);
327 	if (!ds)
328 		return;
329 	Bitmap wrap(bmp, true);
330 	if (_G(gfxDriver)->UsesMemoryBackBuffer())
331 		GfxUtil::DrawSpriteWithTransparency(ds, &wrap, x, y, trans);
332 	else
333 		GfxUtil::DrawSpriteBlend(ds, Point(x, y), &wrap, kBlendMode_Alpha, true, false, trans);
334 }
335 
BlitSpriteRotated(int32 x,int32 y,BITMAP * bmp,int32 angle)336 void IAGSEngine::BlitSpriteRotated(int32 x, int32 y, BITMAP *bmp, int32 angle) {
337 	Bitmap *ds = _G(gfxDriver)->GetStageBackBuffer(true);
338 	if (!ds)
339 		return;
340 	// FIXME: call corresponding Graphics Blit
341 	rotate_sprite(ds->GetAllegroBitmap(), bmp, x, y, itofix(angle));
342 }
343 
344 extern void domouse(int);
345 
PollSystem()346 void IAGSEngine::PollSystem() {
347 	domouse(DOMOUSE_NOCURSOR);
348 	update_polled_stuff_if_runtime();
349 	int mbut, mwheelz;
350 	if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && !_GP(play).IsIgnoringInput())
351 		pl_run_plugin_hooks(AGSE_MOUSECLICK, mbut);
352 	KeyInput kp;
353 	if (run_service_key_controls(kp) && !_GP(play).IsIgnoringInput()) {
354 		pl_run_plugin_hooks(AGSE_KEYPRESS, kp.Key);
355 	}
356 }
357 
GetCharacter(int32 charnum)358 AGSCharacter *IAGSEngine::GetCharacter(int32 charnum) {
359 	if (charnum >= _GP(game).numcharacters)
360 		quit("!AGSEngine::GetCharacter: invalid character request");
361 
362 	return (AGSCharacter *)&_GP(game).chars[charnum];
363 }
GetGameOptions()364 AGSGameOptions *IAGSEngine::GetGameOptions() {
365 	return (AGSGameOptions *)&_GP(play);
366 }
GetPalette()367 AGSColor *IAGSEngine::GetPalette() {
368 	return (AGSColor *)&_G(palette)[0];
369 }
SetPalette(int32 start,int32 finish,AGSColor * cpl)370 void IAGSEngine::SetPalette(int32 start, int32 finish, AGSColor *cpl) {
371 	set_palette_range((RGB *)cpl, start, finish, 0);
372 }
GetNumCharacters()373 int IAGSEngine::GetNumCharacters() {
374 	return _GP(game).numcharacters;
375 }
GetPlayerCharacter()376 int IAGSEngine::GetPlayerCharacter() {
377 	return _GP(game).playercharacter;
378 }
RoomToViewport(int32 * x,int32 * y)379 void IAGSEngine::RoomToViewport(int32 *x, int32 *y) {
380 	Point scrp = _GP(play).RoomToScreen(x ? data_to_game_coord(*x) : 0, y ? data_to_game_coord(*y) : 0);
381 	if (x)
382 		*x = scrp.X;
383 	if (y)
384 		*y = scrp.Y;
385 }
ViewportToRoom(int32 * x,int32 * y)386 void IAGSEngine::ViewportToRoom(int32 *x, int32 *y) {
387 	// NOTE: This is an old function that did not account for custom/multiple viewports
388 	// and does not expect to fail, therefore we always use primary viewport here.
389 	// (Not sure if it's good though)
390 	VpPoint vpt = _GP(play).ScreenToRoom(x ? game_to_data_coord(*x) : 0, y ? game_to_data_coord(*y) : 0);
391 	if (x)
392 		*x = vpt.first.X;
393 	if (y)
394 		*y = vpt.first.Y;
395 }
GetNumObjects()396 int IAGSEngine::GetNumObjects() {
397 	return _G(croom)->numobj;
398 }
GetObject(int32 num)399 AGSObject *IAGSEngine::GetObject(int32 num) {
400 	if (num >= _G(croom)->numobj)
401 		quit("!IAGSEngine::GetObject: invalid object");
402 
403 	return (AGSObject *)&_G(croom)->obj[num];
404 }
CreateBlankBitmap(int32 width,int32 height,int32 coldep)405 BITMAP *IAGSEngine::CreateBlankBitmap(int32 width, int32 height, int32 coldep) {
406 	// [IKM] We should not create Bitmap object here, because
407 	// a) we are returning raw allegro bitmap and therefore loosing control over it
408 	// b) plugin won't use Bitmap anyway
409 	BITMAP *tempb = create_bitmap_ex(coldep, width, height);
410 	clear_to_color(tempb, bitmap_mask_color(tempb));
411 	return tempb;
412 }
FreeBitmap(BITMAP * tofree)413 void IAGSEngine::FreeBitmap(BITMAP *tofree) {
414 	if (tofree)
415 		destroy_bitmap(tofree);
416 }
GetSpriteGraphic(int32 num)417 BITMAP *IAGSEngine::GetSpriteGraphic(int32 num) {
418 	return (BITMAP *)_GP(spriteset)[num]->GetAllegroBitmap();
419 }
GetRoomMask(int32 index)420 BITMAP *IAGSEngine::GetRoomMask(int32 index) {
421 	if (index == MASK_WALKABLE)
422 		return (BITMAP *)_GP(thisroom).WalkAreaMask->GetAllegroBitmap();
423 	else if (index == MASK_WALKBEHIND)
424 		return (BITMAP *)_GP(thisroom).WalkBehindMask->GetAllegroBitmap();
425 	else if (index == MASK_HOTSPOT)
426 		return (BITMAP *)_GP(thisroom).HotspotMask->GetAllegroBitmap();
427 	else if (index == MASK_REGIONS)
428 		return (BITMAP *)_GP(thisroom).RegionMask->GetAllegroBitmap();
429 	else
430 		quit("!IAGSEngine::GetRoomMask: invalid mask requested");
431 	return nullptr;
432 }
GetViewFrame(int32 view,int32 loop,int32 frame)433 AGSViewFrame *IAGSEngine::GetViewFrame(int32 view, int32 loop, int32 frame) {
434 	view--;
435 	if ((view < 0) || (view >= _GP(game).numviews))
436 		quit("!IAGSEngine::GetViewFrame: invalid view");
437 	if ((loop < 0) || (loop >= _G(views)[view].numLoops))
438 		quit("!IAGSEngine::GetViewFrame: invalid loop");
439 	if ((frame < 0) || (frame >= _G(views)[view].loops[loop].numFrames))
440 		return nullptr;
441 
442 	return (AGSViewFrame *)&_G(views)[view].loops[loop].frames[frame];
443 }
444 
GetRawPixelColor(int32 color)445 int IAGSEngine::GetRawPixelColor(int32 color) {
446 	// Convert the standardized colour to the local gfx mode color
447 	// NOTE: it is unclear whether this has to be game colour depth or display color depth.
448 	// there was no difference in the original engine, but there is now.
449 	int result;
450 	__my_setcolor(&result, color, _GP(game).GetColorDepth());
451 	return result;
452 }
453 
GetWalkbehindBaseline(int32 wa)454 int IAGSEngine::GetWalkbehindBaseline(int32 wa) {
455 	if ((wa < 1) || (wa >= MAX_WALK_BEHINDS))
456 		quit("!IAGSEngine::GetWalkBehindBase: invalid walk-behind area specified");
457 	return _G(croom)->walkbehind_base[wa];
458 }
GetScriptFunctionAddress(const char * funcName)459 Plugins::PluginMethod IAGSEngine::GetScriptFunctionAddress(const char *funcName) {
460 	return ccGetSymbolAddressForPlugin(funcName);
461 }
GetBitmapTransparentColor(BITMAP * bmp)462 int IAGSEngine::GetBitmapTransparentColor(BITMAP *bmp) {
463 	return bitmap_mask_color(bmp);
464 }
465 // get the character scaling level at a particular point
GetAreaScaling(int32 x,int32 y)466 int IAGSEngine::GetAreaScaling(int32 x, int32 y) {
467 	return GetScalingAt(x, y);
468 }
IsGamePaused()469 int IAGSEngine::IsGamePaused() {
470 	return _G(game_paused);
471 }
GetSpriteWidth(int32 slot)472 int IAGSEngine::GetSpriteWidth(int32 slot) {
473 	return _GP(game).SpriteInfos[slot].Width;
474 }
GetSpriteHeight(int32 slot)475 int IAGSEngine::GetSpriteHeight(int32 slot) {
476 	return _GP(game).SpriteInfos[slot].Height;
477 }
GetTextExtent(int32 font,const char * text,int32 * width,int32 * height)478 void IAGSEngine::GetTextExtent(int32 font, const char *text, int32 *width, int32 *height) {
479 	if ((font < 0) || (font >= _GP(game).numfonts)) {
480 		if (width != nullptr) width[0] = 0;
481 		if (height != nullptr) height[0] = 0;
482 		return;
483 	}
484 
485 	if (width != nullptr)
486 		width[0] = wgettextwidth_compensate(text, font);
487 	if (height != nullptr)
488 		height[0] = wgettextheight(text, font);
489 }
PrintDebugConsole(const char * text)490 void IAGSEngine::PrintDebugConsole(const char *text) {
491 	debug_script_log("[PLUGIN] %s", text);
492 }
IsChannelPlaying(int32 channel)493 int IAGSEngine::IsChannelPlaying(int32 channel) {
494 	return AGS3::IsChannelPlaying(channel);
495 }
PlaySoundChannel(int32 channel,int32 soundType,int32 volume,int32 loop,const char * filename)496 void IAGSEngine::PlaySoundChannel(int32 channel, int32 soundType, int32 volume, int32 loop, const char *filename) {
497 	stop_and_destroy_channel(channel);
498 	// Not sure if it's right to let it play on *any* channel, but this is plugin so let it go...
499 	// we must correctly stop background voice speech if it takes over speech chan
500 	if (channel == SCHAN_SPEECH && _GP(play).IsNonBlockingVoiceSpeech())
501 		stop_voice_nonblocking();
502 
503 	SOUNDCLIP *newcha = nullptr;
504 
505 	if (((soundType == PSND_MP3STREAM) || (soundType == PSND_OGGSTREAM))
506 	        && (loop != 0))
507 		quit("IAGSEngine::PlaySoundChannel: streamed samples cannot loop");
508 
509 	// TODO: find out how engine was supposed to decide on where to load the sound from
510 	AssetPath asset_name(filename, "audio");
511 
512 	if (soundType == PSND_WAVE)
513 		newcha = my_load_wave(asset_name, volume, (loop != 0));
514 	else if (soundType == PSND_MP3STREAM)
515 		newcha = my_load_mp3(asset_name, volume);
516 	else if (soundType == PSND_OGGSTREAM)
517 		newcha = my_load_ogg(asset_name, volume);
518 	else if (soundType == PSND_MP3STATIC)
519 		newcha = my_load_static_mp3(asset_name, volume, (loop != 0));
520 	else if (soundType == PSND_OGGSTATIC)
521 		newcha = my_load_static_ogg(asset_name, volume, (loop != 0));
522 	else if (soundType == PSND_MIDI) {
523 		if (_GP(play).silent_midi != 0 || _G(current_music_type) == MUS_MIDI)
524 			quit("!IAGSEngine::PlaySoundChannel: MIDI already in use");
525 		newcha = my_load_midi(asset_name, (loop != 0));
526 		newcha->set_volume(volume);
527 	} else if (soundType == PSND_MOD) {
528 		newcha = my_load_mod(asset_name, (loop != 0));
529 		newcha->set_volume(volume);
530 	} else
531 		quit("!IAGSEngine::PlaySoundChannel: unknown sound type");
532 
533 	set_clip_to_channel(channel, newcha);
534 }
535 // Engine interface 12 and above are below
MarkRegionDirty(int32 left,int32 top,int32 right,int32 bottom)536 void IAGSEngine::MarkRegionDirty(int32 left, int32 top, int32 right, int32 bottom) {
537 	invalidate_rect(left, top, right, bottom, false);
538 	_GP(plugins)[this->pluginId].invalidatedRegion++;
539 }
GetMouseCursor(int32 cursor)540 AGSMouseCursor *IAGSEngine::GetMouseCursor(int32 cursor) {
541 	if ((cursor < 0) || (cursor >= _GP(game).numcursors))
542 		return nullptr;
543 
544 	return (AGSMouseCursor *)&_GP(game).mcurs[cursor];
545 }
GetRawColorComponents(int32 coldepth,int32 color,int32 * red,int32 * green,int32 * blue,int32 * alpha)546 void IAGSEngine::GetRawColorComponents(int32 coldepth, int32 color, int32 *red, int32 *green, int32 *blue, int32 *alpha) {
547 	if (red)
548 		*red = getr_depth(coldepth, color);
549 	if (green)
550 		*green = getg_depth(coldepth, color);
551 	if (blue)
552 		*blue = getb_depth(coldepth, color);
553 	if (alpha)
554 		*alpha = geta_depth(coldepth, color);
555 }
MakeRawColorPixel(int32 coldepth,int32 red,int32 green,int32 blue,int32 alpha)556 int IAGSEngine::MakeRawColorPixel(int32 coldepth, int32 red, int32 green, int32 blue, int32 alpha) {
557 	return makeacol_depth(coldepth, red, green, blue, alpha);
558 }
GetFontType(int32 fontNum)559 int IAGSEngine::GetFontType(int32 fontNum) {
560 	if ((fontNum < 0) || (fontNum >= _GP(game).numfonts))
561 		return FNT_INVALID;
562 
563 	if (font_supports_extended_characters(fontNum))
564 		return FNT_TTF;
565 
566 	return FNT_SCI;
567 }
CreateDynamicSprite(int32 coldepth,int32 width,int32 height)568 int IAGSEngine::CreateDynamicSprite(int32 coldepth, int32 width, int32 height) {
569 
570 	// TODO: why is this implemented right here, should not an existing
571 	// script handling implementation be called instead?
572 
573 	int gotSlot = _GP(spriteset).GetFreeIndex();
574 	if (gotSlot <= 0)
575 		return 0;
576 
577 	if ((width < 1) || (height < 1))
578 		quit("!IAGSEngine::CreateDynamicSprite: invalid width/height requested by plugin");
579 
580 	// resize the sprite to the requested size
581 	Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, coldepth);
582 	if (newPic == nullptr)
583 		return 0;
584 
585 	// add it into the sprite set
586 	add_dynamic_sprite(gotSlot, newPic);
587 	return gotSlot;
588 }
DeleteDynamicSprite(int32 slot)589 void IAGSEngine::DeleteDynamicSprite(int32 slot) {
590 	free_dynamic_sprite(slot);
591 }
IsSpriteAlphaBlended(int32 slot)592 int IAGSEngine::IsSpriteAlphaBlended(int32 slot) {
593 	if (_GP(game).SpriteInfos[slot].Flags & SPF_ALPHACHANNEL)
594 		return 1;
595 	return 0;
596 }
597 
598 // disable AGS's sound engine
DisableSound()599 void IAGSEngine::DisableSound() {
600 	shutdown_sound();
601 	_GP(usetup).audio_backend = 0;
602 }
CanRunScriptFunctionNow()603 int IAGSEngine::CanRunScriptFunctionNow() {
604 	if (_G(inside_script))
605 		return 0;
606 	return 1;
607 }
CallGameScriptFunction(const char * name,int32 globalScript,int32 numArgs,long arg1,long arg2,long arg3)608 int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2, long arg3) {
609 	if (_G(inside_script))
610 		return -300;
611 
612 	ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom);
613 
614 	RuntimeScriptValue params[3];
615 	params[0].SetPluginArgument(arg1);
616 	params[1].SetPluginArgument(arg2);
617 	params[2].SetPluginArgument(arg3);
618 	int toret = RunScriptFunctionIfExists(toRun, name, numArgs, params);
619 	return toret;
620 }
621 
NotifySpriteUpdated(int32 slot)622 void IAGSEngine::NotifySpriteUpdated(int32 slot) {
623 	game_sprite_updated(slot);
624 }
625 
SetSpriteAlphaBlended(int32 slot,int32 isAlphaBlended)626 void IAGSEngine::SetSpriteAlphaBlended(int32 slot, int32 isAlphaBlended) {
627 
628 	_GP(game).SpriteInfos[slot].Flags &= ~SPF_ALPHACHANNEL;
629 
630 	if (isAlphaBlended)
631 		_GP(game).SpriteInfos[slot].Flags |= SPF_ALPHACHANNEL;
632 }
633 
QueueGameScriptFunction(const char * name,int32 globalScript,int32 numArgs,long arg1,long arg2)634 void IAGSEngine::QueueGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2) {
635 	if (!_G(inside_script)) {
636 		this->CallGameScriptFunction(name, globalScript, numArgs, arg1, arg2, 0);
637 		return;
638 	}
639 
640 	if (numArgs < 0 || numArgs > 2)
641 		quit("IAGSEngine::QueueGameScriptFunction: invalid number of arguments");
642 
643 	_G(curscript)->run_another(name, globalScript ? kScInstGame : kScInstRoom, numArgs,
644 	                           RuntimeScriptValue().SetPluginArgument(arg1), RuntimeScriptValue().SetPluginArgument(arg2));
645 }
646 
RegisterManagedObject(const void * object,IAGSScriptManagedObject * callback)647 int IAGSEngine::RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback) {
648 	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
649 	return ccRegisterManagedObject(object, (ICCDynamicObject *)callback, true);
650 }
651 
AddManagedObjectReader(const char * typeName,IAGSManagedObjectReader * reader)652 void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader) {
653 	if (_G(numPluginReaders) >= MAX_PLUGIN_OBJECT_READERS)
654 		quit("Plugin error: IAGSEngine::AddObjectReader: Too many object readers added");
655 
656 	if ((typeName == nullptr) || (typeName[0] == 0))
657 		quit("Plugin error: IAGSEngine::AddObjectReader: invalid name for type");
658 
659 	for (int ii = 0; ii < _G(numPluginReaders); ii++) {
660 		if (strcmp(_G(pluginReaders)[ii].type, typeName) == 0)
661 			quitprintf("Plugin error: IAGSEngine::AddObjectReader: type '%s' has been registered already", typeName);
662 	}
663 
664 	_G(pluginReaders)[_G(numPluginReaders)].reader = reader;
665 	_G(pluginReaders)[_G(numPluginReaders)].type = typeName;
666 	_G(numPluginReaders)++;
667 }
668 
RegisterUnserializedObject(int key,const void * object,IAGSScriptManagedObject * callback)669 void IAGSEngine::RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback) {
670 	_GP(GlobalReturnValue).SetPluginObject(const_cast<void *>(object), (ICCDynamicObject *)callback);
671 	ccRegisterUnserializedObject(key, object, (ICCDynamicObject *)callback, true);
672 }
673 
GetManagedObjectKeyByAddress(const char * address)674 int IAGSEngine::GetManagedObjectKeyByAddress(const char *address) {
675 	return ccGetObjectHandleFromAddress(address);
676 }
677 
GetManagedObjectAddressByKey(int key)678 void *IAGSEngine::GetManagedObjectAddressByKey(int key) {
679 	void *object;
680 	ICCDynamicObject *manager;
681 	ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager);
682 	if (obj_type == kScValPluginObject) {
683 		_GP(GlobalReturnValue).SetPluginObject(object, manager);
684 	} else {
685 		_GP(GlobalReturnValue).SetDynamicObject(object, manager);
686 	}
687 	return object;
688 }
689 
CreateScriptString(const char * fromText)690 const char *IAGSEngine::CreateScriptString(const char *fromText) {
691 	const char *string = CreateNewScriptString(fromText);
692 	// Should be still standard dynamic object, because not managed by plugin
693 	_GP(GlobalReturnValue).SetDynamicObject(const_cast<char *>(string), &_GP(myScriptStringImpl));
694 	return string;
695 }
696 
IncrementManagedObjectRefCount(const char * address)697 int IAGSEngine::IncrementManagedObjectRefCount(const char *address) {
698 	return ccAddObjectReference(GetManagedObjectKeyByAddress(address));
699 }
700 
DecrementManagedObjectRefCount(const char * address)701 int IAGSEngine::DecrementManagedObjectRefCount(const char *address) {
702 	return ccReleaseObjectReference(GetManagedObjectKeyByAddress(address));
703 }
704 
SetMousePosition(int32 x,int32 y)705 void IAGSEngine::SetMousePosition(int32 x, int32 y) {
706 	_GP(mouse).SetPosition(Point(x, y));
707 	RefreshMouse();
708 }
709 
SimulateMouseClick(int32 button)710 void IAGSEngine::SimulateMouseClick(int32 button) {
711 	PluginSimulateMouseClick(button);
712 }
713 
GetMovementPathWaypointCount(int32 pathId)714 int IAGSEngine::GetMovementPathWaypointCount(int32 pathId) {
715 	return _G(mls)[pathId % TURNING_AROUND].numstage;
716 }
717 
GetMovementPathLastWaypoint(int32 pathId)718 int IAGSEngine::GetMovementPathLastWaypoint(int32 pathId) {
719 	return _G(mls)[pathId % TURNING_AROUND].onstage;
720 }
721 
GetMovementPathWaypointLocation(int32 pathId,int32 waypoint,int32 * x,int32 * y)722 void IAGSEngine::GetMovementPathWaypointLocation(int32 pathId, int32 waypoint, int32 *x, int32 *y) {
723 	*x = (_G(mls)[pathId % TURNING_AROUND].pos[waypoint] >> 16) & 0x0000ffff;
724 	*y = (_G(mls)[pathId % TURNING_AROUND].pos[waypoint] & 0x0000ffff);
725 }
726 
GetMovementPathWaypointSpeed(int32 pathId,int32 waypoint,int32 * xSpeed,int32 * ySpeed)727 void IAGSEngine::GetMovementPathWaypointSpeed(int32 pathId, int32 waypoint, int32 *xSpeed, int32 *ySpeed) {
728 	*xSpeed = _G(mls)[pathId % TURNING_AROUND].xpermove[waypoint];
729 	*ySpeed = _G(mls)[pathId % TURNING_AROUND].ypermove[waypoint];
730 }
731 
IsRunningUnderDebugger()732 int IAGSEngine::IsRunningUnderDebugger() {
733 	return (_G(editor_debugging_enabled) != 0) ? 1 : 0;
734 }
735 
GetPathToFileInCompiledFolder(const char * fileName,char * buffer)736 void IAGSEngine::GetPathToFileInCompiledFolder(const char *fileName, char *buffer) {
737 	// TODO: this is very unsafe, deprecate and make a better API function if still necessary
738 	strcpy(buffer, PathFromInstallDir(fileName).GetCStr());
739 }
740 
BreakIntoDebugger()741 void IAGSEngine::BreakIntoDebugger() {
742 	_G(break_on_next_script_step) = 1;
743 }
744 
ReplaceFontRenderer(int fontNumber,IAGSFontRenderer * newRenderer)745 IAGSFontRenderer *IAGSEngine::ReplaceFontRenderer(int fontNumber, IAGSFontRenderer *newRenderer) {
746 	auto *old_render = font_replace_renderer(fontNumber, newRenderer);
747 	GUI::MarkForFontUpdate(fontNumber);
748 	return old_render;
749 }
750 
GetRenderStageDesc(AGSRenderStageDesc * desc)751 void IAGSEngine::GetRenderStageDesc(AGSRenderStageDesc *desc) {
752 	if (desc->Version >= 25) {
753 		_G(gfxDriver)->GetStageMatrixes((RenderMatrixes &)desc->Matrixes);
754 	}
755 }
756 
757 
758 // *********** General plugin implementation **********
759 
pl_stop_plugins()760 void pl_stop_plugins() {
761 	uint a;
762 	ccSetDebugHook(nullptr);
763 
764 	for (a = 0; a < _GP(plugins).size(); a++) {
765 		if (_GP(plugins)[a].available) {
766 			_GP(plugins)[a]._plugin->AGS_EngineShutdown();
767 			_GP(plugins)[a].wantHook = 0;
768 			if (_GP(plugins)[a].savedata) {
769 				free(_GP(plugins)[a].savedata);
770 				_GP(plugins)[a].savedata = nullptr;
771 			}
772 			if (!_GP(plugins)[a].builtin) {
773 				_GP(plugins)[a].library.Unload();
774 			}
775 		}
776 	}
777 
778 	_GP(plugins).clear();
779 	_GP(plugins).reserve(MAXPLUGINS);
780 }
781 
pl_startup_plugins()782 void pl_startup_plugins() {
783 	for (uint i = 0; i < _GP(plugins).size(); i++) {
784 		if (i == 0)
785 			_GP(engineExports).AGS_EngineStartup(&_GP(plugins)[0].eiface);
786 
787 		if (_GP(plugins)[i].available) {
788 			EnginePlugin &ep = _GP(plugins)[i];
789 			ep._plugin->AGS_EngineStartup(&ep.eiface);
790 		}
791 	}
792 }
793 
pl_run_plugin_hooks(int event,NumberPtr data)794 NumberPtr pl_run_plugin_hooks(int event, NumberPtr data) {
795 	int retval = 0;
796 	for (uint i = 0; i < _GP(plugins).size(); i++) {
797 		if (_GP(plugins)[i].wantHook & event) {
798 			retval = _GP(plugins)[i]._plugin->AGS_EngineOnEvent(event, data);
799 			if (retval)
800 				return retval;
801 		}
802 	}
803 
804 	return 0;
805 }
806 
pl_run_plugin_debug_hooks(const char * scriptfile,int linenum)807 int pl_run_plugin_debug_hooks(const char *scriptfile, int linenum) {
808 	int retval = 0;
809 	for (uint i = 0; i < _GP(plugins).size(); i++) {
810 		if (_GP(plugins)[i].wantHook & AGSE_SCRIPTDEBUG) {
811 			retval = _GP(plugins)[i]._plugin->AGS_EngineDebugHook(scriptfile, linenum, 0);
812 			if (retval)
813 				return retval;
814 		}
815 	}
816 	return 0;
817 }
818 
pl_run_plugin_init_gfx_hooks(const char * driverName,void * data)819 void pl_run_plugin_init_gfx_hooks(const char *driverName, void *data) {
820 	for (uint i = 0; i < _GP(plugins).size(); i++) {
821 		_GP(plugins)[i]._plugin->AGS_EngineInitGfx(driverName, data);
822 	}
823 }
824 
pl_register_plugins(const std::vector<Shared::PluginInfo> & infos)825 Engine::GameInitError pl_register_plugins(const std::vector<Shared::PluginInfo> &infos) {
826 	_GP(plugins).clear();
827 	_GP(plugins).reserve(MAXPLUGINS);
828 
829 	for (size_t inf_index = 0; inf_index < infos.size(); ++inf_index) {
830 		const Shared::PluginInfo &info = infos[inf_index];
831 		String name = info.Name;
832 		if (name.GetLast() == '!')
833 			continue; // editor-only plugin, ignore it
834 		if (_GP(plugins).size() == MAXPLUGINS)
835 			return kGameInitErr_TooManyPlugins;
836 		// AGS Editor currently saves plugin names in game data with
837 		// ".dll" extension appended; we need to take care of that
838 		const String name_ext = ".dll";
839 		if (name.GetLength() <= name_ext.GetLength() || name.GetLength() > PLUGIN_FILENAME_MAX + name_ext.GetLength() ||
840 		        name.CompareRightNoCase(name_ext, name_ext.GetLength())) {
841 			return kGameInitErr_PluginNameInvalid;
842 		}
843 		// remove ".dll" from plugin's name
844 		name.ClipRight(name_ext.GetLength());
845 
846 		_GP(plugins).resize(_GP(plugins).size() + 1);
847 		EnginePlugin *apl = &_GP(plugins).back();
848 
849 		// Copy plugin info
850 		snprintf(apl->filename, sizeof(apl->filename), "%s", name.GetCStr());
851 		if (info.DataLen) {
852 			apl->savedata = (char *)malloc(info.DataLen);
853 			memcpy(apl->savedata, info.Data.get(), info.DataLen);
854 		}
855 		apl->savedatasize = info.DataLen;
856 
857 		// Compatibility with the old SnowRain module
858 		if (ags_stricmp(apl->filename, "ags_SnowRain20") == 0) {
859 			strcpy(apl->filename, "ags_snowrain");
860 		}
861 
862 		String expect_filename = apl->library.GetFilenameForLib(apl->filename);
863 		if (apl->library.Load(apl->filename)) {
864 			apl->_plugin = apl->library.getPlugin();
865 			AGS::Shared::Debug::Printf(kDbgMsg_Info, "Plugin '%s' loaded as '%s', resolving imports...", apl->filename, expect_filename.GetCStr());
866 
867 		} else {
868 			AGS::Shared::Debug::Printf(kDbgMsg_Info, "Plugin '%s' could not be loaded (expected '%s')",
869 			                           apl->filename, expect_filename.GetCStr());
870 			_GP(plugins).pop_back();
871 			continue;
872 		}
873 
874 		apl->eiface.pluginId = _GP(plugins).size() - 1;
875 		apl->eiface.version = PLUGIN_API_VERSION;
876 		apl->wantHook = 0;
877 		apl->available = true;
878 	}
879 	return kGameInitErr_NoError;
880 }
881 
pl_is_plugin_loaded(const char * pl_name)882 bool pl_is_plugin_loaded(const char *pl_name) {
883 	if (!pl_name)
884 		return false;
885 
886 	for (uint i = 0; i < _GP(plugins).size(); ++i) {
887 		if (ags_stricmp(pl_name, _GP(plugins)[i].filename) == 0)
888 			return _GP(plugins)[i].available;
889 	}
890 	return false;
891 }
892 
pl_any_want_hook(int event)893 bool pl_any_want_hook(int event) {
894 	for (uint i = 0; i < _GP(plugins).size(); ++i) {
895 		if (_GP(plugins)[i].wantHook & event)
896 			return true;
897 	}
898 	return false;
899 }
900 
901 } // namespace AGS3
902