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