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/std/algorithm.h"
24 #include "ags/lib/std/math.h"
25 #include "ags/lib/aastr-0.1.1/aastr.h"
26 #include "ags/shared/core/platform.h"
27 #include "ags/shared/ac/common.h"
28 #include "ags/shared/util/compress.h"
29 #include "ags/shared/util/wgt2_allg.h"
30 #include "ags/shared/ac/view.h"
31 #include "ags/engine/ac/character_cache.h"
32 #include "ags/engine/ac/character_extras.h"
33 #include "ags/shared/ac/character_info.h"
34 #include "ags/engine/ac/display.h"
35 #include "ags/engine/ac/draw.h"
36 #include "ags/engine/ac/draw_software.h"
37 #include "ags/engine/ac/game_setup.h"
38 #include "ags/shared/ac/game_setup_struct.h"
39 #include "ags/engine/ac/game_state.h"
40 #include "ags/engine/ac/global_game.h"
41 #include "ags/engine/ac/global_gui.h"
42 #include "ags/engine/ac/global_region.h"
43 #include "ags/engine/ac/gui.h"
44 #include "ags/engine/ac/mouse.h"
45 #include "ags/engine/ac/object_cache.h"
46 #include "ags/engine/ac/overlay.h"
47 #include "ags/engine/ac/sys_events.h"
48 #include "ags/engine/ac/room_object.h"
49 #include "ags/engine/ac/room_status.h"
50 #include "ags/engine/ac/runtime_defines.h"
51 #include "ags/engine/ac/screen_overlay.h"
52 #include "ags/engine/ac/sprite.h"
53 #include "ags/engine/ac/sprite_list_entry.h"
54 #include "ags/engine/ac/string.h"
55 #include "ags/engine/ac/system.h"
56 #include "ags/engine/ac/view_frame.h"
57 #include "ags/engine/ac/walkable_area.h"
58 #include "ags/engine/ac/walk_behind.h"
59 #include "ags/engine/ac/dynobj/script_system.h"
60 #include "ags/engine/debugging/debugger.h"
61 #include "ags/engine/debugging/debug_log.h"
62 #include "ags/shared/font/fonts.h"
63 #include "ags/shared/gui/gui_main.h"
64 #include "ags/engine/platform/base/ags_platform_driver.h"
65 #include "ags/plugins/ags_plugin.h"
66 #include "ags/plugins/plugin_engine.h"
67 #include "ags/shared/ac/sprite_cache.h"
68 #include "ags/engine/gfx/gfx_util.h"
69 #include "ags/engine/gfx/graphics_driver.h"
70 #include "ags/engine/gfx/blender.h"
71 #include "ags/engine/media/audio/audio_system.h"
72 #include "ags/engine/ac/game.h"
73 #include "ags/ags.h"
74 #include "ags/globals.h"
75
76 namespace AGS3 {
77
78 using namespace AGS::Shared;
79 using namespace AGS::Engine;
80
SpriteListEntry()81 SpriteListEntry::SpriteListEntry()
82 : bmp(nullptr)
83 , pic(nullptr)
84 , baseline(0), x(0), y(0)
85 , transparent(0)
86 , takesPriorityIfEqual(false), hasAlphaChannel(false) {
87 }
88
setpal()89 void setpal() {
90 set_palette_range(_G(palette), 0, 255, 0);
91 }
92
93 int _places_r = 3, _places_g = 2, _places_b = 3;
94
95 // convert RGB to BGR for strange graphics cards
convert_16_to_16bgr(Bitmap * tempbl)96 Bitmap *convert_16_to_16bgr(Bitmap *tempbl) {
97
98 int x, y;
99 unsigned short c, r, ds, b;
100
101 for (y = 0; y < tempbl->GetHeight(); y++) {
102 unsigned short *p16 = (unsigned short *)tempbl->GetScanLine(y);
103
104 for (x = 0; x < tempbl->GetWidth(); x++) {
105 c = p16[x];
106 if (c != MASK_COLOR_16) {
107 b = _rgb_scale_5[c & 0x1F];
108 ds = _rgb_scale_6[(c >> 5) & 0x3F];
109 r = _rgb_scale_5[(c >> 11) & 0x1F];
110 // allegro assumes 5-6-5 for 16-bit
111 p16[x] = (((r >> _places_r) << _G(_rgb_r_shift_16)) |
112 ((ds >> _places_g) << _G(_rgb_g_shift_16)) |
113 ((b >> _places_b) << _G(_rgb_b_shift_16)));
114
115 }
116 }
117 }
118
119 return tempbl;
120 }
121
122 // PSP: convert 32 bit RGB to BGR.
convert_32_to_32bgr(Bitmap * tempbl)123 Bitmap *convert_32_to_32bgr(Bitmap *tempbl) {
124
125 int i = 0;
126 int j = 0;
127 unsigned char *current;
128 while (i < tempbl->GetHeight()) {
129 current = tempbl->GetScanLineForWriting(i);
130 while (j < tempbl->GetWidth()) {
131 current[0] ^= current[2];
132 current[2] ^= current[0];
133 current[0] ^= current[2];
134 current += 4;
135 j++;
136 }
137 i++;
138 j = 0;
139 }
140
141 return tempbl;
142 }
143
144 // NOTE: Some of these conversions are required even when using
145 // D3D and OpenGL rendering, for two reasons:
146 // 1) certain raw drawing operations are still performed by software
147 // Allegro methods, hence bitmaps should be kept compatible to any native
148 // software operations, such as blitting two bitmaps of different formats.
149 // 2) mobile ports feature an OpenGL renderer built in Allegro library,
150 // that assumes native bitmaps are in OpenGL-compatible format, so that it
151 // could copy them to texture without additional changes.
152 // AGS own OpenGL renderer tries to sync its behavior with the former one.
153 //
154 // TODO: make gfxDriver->GetCompatibleBitmapFormat describe all necessary
155 // conversions, so that we did not have to guess.
156 //
AdjustBitmapForUseWithDisplayMode(Bitmap * bitmap,bool has_alpha)157 Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap *bitmap, bool has_alpha) {
158 const int bmp_col_depth = bitmap->GetColorDepth();
159 // const int sys_col_depth = System_GetColorDepth();
160 const int game_col_depth = _GP(game).GetColorDepth();
161 Bitmap *new_bitmap = bitmap;
162
163 //
164 // The only special case when bitmap needs to be prepared for graphics driver
165 //
166 // In 32-bit display mode, 32-bit bitmaps may require component conversion
167 // to match graphics driver expectation about pixel format.
168 // TODO: make GetCompatibleBitmapFormat tell this somehow
169 #if defined (AGS_INVERTED_COLOR_ORDER)
170 if (sys_col_depth > 16 && bmp_col_depth == 32) {
171 // Convert RGB to BGR.
172 new_bitmap = convert_32_to_32bgr(bitmap);
173 }
174 #endif
175
176 //
177 // The rest is about bringing bitmaps to the native game's format
178 // (has no dependency on display mode).
179 //
180 // In 32-bit game 32-bit bitmaps should have transparent pixels marked
181 // (this adjustment is probably needed for DrawingSurface ops)
182 if (game_col_depth == 32 && bmp_col_depth == 32) {
183 if (has_alpha)
184 set_rgb_mask_using_alpha_channel(new_bitmap);
185 }
186 // In 32-bit game hicolor bitmaps must be converted to the true color
187 else if (game_col_depth == 32 && (bmp_col_depth > 8 && bmp_col_depth <= 16)) {
188 new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth);
189 }
190 // In non-32-bit game truecolor bitmaps must be downgraded
191 else if (game_col_depth <= 16 && bmp_col_depth > 16) {
192 if (has_alpha) // if has valid alpha channel, convert it to regular transparency mask
193 new_bitmap = remove_alpha_channel(bitmap);
194 else // else simply convert bitmap
195 new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth);
196 }
197 // Special case when we must convert 16-bit RGB to BGR
198 else if (_G(convert_16bit_bgr) == 1 && bmp_col_depth == 16) {
199 new_bitmap = convert_16_to_16bgr(bitmap);
200 }
201 return new_bitmap;
202 }
203
ReplaceBitmapWithSupportedFormat(Bitmap * bitmap)204 Bitmap *ReplaceBitmapWithSupportedFormat(Bitmap *bitmap) {
205 Bitmap *new_bitmap = GfxUtil::ConvertBitmap(bitmap, _G(gfxDriver)->GetCompatibleBitmapFormat(bitmap->GetColorDepth()));
206 if (new_bitmap != bitmap)
207 delete bitmap;
208 return new_bitmap;
209 }
210
PrepareSpriteForUse(Bitmap * bitmap,bool has_alpha)211 Bitmap *PrepareSpriteForUse(Bitmap *bitmap, bool has_alpha) {
212 bool must_switch_palette = bitmap->GetColorDepth() == 8 && _GP(game).GetColorDepth() > 8;
213 if (must_switch_palette)
214 select_palette(_G(palette));
215
216 Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap, has_alpha);
217 if (new_bitmap != bitmap)
218 delete bitmap;
219 new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap);
220
221 if (must_switch_palette)
222 unselect_palette();
223 return new_bitmap;
224 }
225
PrepareSpriteForUse(PBitmap bitmap,bool has_alpha)226 PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha) {
227 bool must_switch_palette = bitmap->GetColorDepth() == 8 && System_GetColorDepth() > 8;
228 if (must_switch_palette)
229 select_palette(_G(palette));
230
231 Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap.get(), has_alpha);
232 new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap);
233
234 if (must_switch_palette)
235 unselect_palette();
236 return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr!
237 }
238
CopyScreenIntoBitmap(int width,int height,bool at_native_res)239 Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res) {
240 Bitmap *dst = new Bitmap(width, height, _GP(game).GetColorDepth());
241 GraphicResolution want_fmt;
242 // If the size and color depth are supported we may copy right into our bitmap
243 if (_G(gfxDriver)->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt))
244 return dst;
245 // Otherwise we might need to copy between few bitmaps...
246 Bitmap *buf_screenfmt = new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth);
247 _G(gfxDriver)->GetCopyOfScreenIntoBitmap(buf_screenfmt, at_native_res);
248 // If at least size matches then we may blit
249 if (dst->GetSize() == buf_screenfmt->GetSize()) {
250 dst->Blit(buf_screenfmt);
251 }
252 // Otherwise we need to go through another bitmap of the matching format
253 else {
254 Bitmap *buf_dstfmt = new Bitmap(buf_screenfmt->GetWidth(), buf_screenfmt->GetHeight(), dst->GetColorDepth());
255 buf_dstfmt->Blit(buf_screenfmt);
256 dst->StretchBlt(buf_dstfmt, RectWH(dst->GetSize()));
257 delete buf_dstfmt;
258 }
259 delete buf_screenfmt;
260 return dst;
261 }
262
263
264 // Begin resolution system functions
265
266 // Multiplies up the number of pixels depending on the current
267 // resolution, to give a relatively fixed size at any game res
get_fixed_pixel_size(int pixels)268 AGS_INLINE int get_fixed_pixel_size(int pixels) {
269 return pixels * _GP(game).GetRelativeUIMult();
270 }
271
data_to_game_coord(int coord)272 AGS_INLINE int data_to_game_coord(int coord) {
273 return coord * _GP(game).GetDataUpscaleMult();
274 }
275
data_to_game_coords(int * x,int * y)276 AGS_INLINE void data_to_game_coords(int *x, int *y) {
277 const int mul = _GP(game).GetDataUpscaleMult();
278 x[0] *= mul;
279 y[0] *= mul;
280 }
281
data_to_game_round_up(int * x,int * y)282 AGS_INLINE void data_to_game_round_up(int *x, int *y) {
283 const int mul = _GP(game).GetDataUpscaleMult();
284 x[0] = x[0] * mul + (mul - 1);
285 y[0] = y[0] * mul + (mul - 1);
286 }
287
game_to_data_coord(int coord)288 AGS_INLINE int game_to_data_coord(int coord) {
289 return coord / _GP(game).GetDataUpscaleMult();
290 }
291
game_to_data_coords(int & x,int & y)292 AGS_INLINE void game_to_data_coords(int &x, int &y) {
293 const int mul = _GP(game).GetDataUpscaleMult();
294 x /= mul;
295 y /= mul;
296 }
297
game_to_data_round_up(int coord)298 AGS_INLINE int game_to_data_round_up(int coord) {
299 const int mul = _GP(game).GetDataUpscaleMult();
300 return (coord / mul) + (mul - 1);
301 }
302
ctx_data_to_game_coord(int & x,int & y,bool hires_ctx)303 AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) {
304 if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
305 x /= HIRES_COORD_MULTIPLIER;
306 y /= HIRES_COORD_MULTIPLIER;
307 } else if (!hires_ctx && _GP(game).IsLegacyHiRes()) {
308 x *= HIRES_COORD_MULTIPLIER;
309 y *= HIRES_COORD_MULTIPLIER;
310 }
311 }
312
ctx_data_to_game_size(int & w,int & h,bool hires_ctx)313 AGS_INLINE void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) {
314 if (hires_ctx && !_GP(game).IsLegacyHiRes()) {
315 w = Math::Max(1, (w / HIRES_COORD_MULTIPLIER));
316 h = Math::Max(1, (h / HIRES_COORD_MULTIPLIER));
317 } else if (!hires_ctx && _GP(game).IsLegacyHiRes()) {
318 w *= HIRES_COORD_MULTIPLIER;
319 h *= HIRES_COORD_MULTIPLIER;
320 }
321 }
322
ctx_data_to_game_size(int size,bool hires_ctx)323 AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx) {
324 if (hires_ctx && !_GP(game).IsLegacyHiRes())
325 return Math::Max(1, (size / HIRES_COORD_MULTIPLIER));
326 if (!hires_ctx && _GP(game).IsLegacyHiRes())
327 return size * HIRES_COORD_MULTIPLIER;
328 return size;
329 }
330
game_to_ctx_data_size(int size,bool hires_ctx)331 AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx) {
332 if (hires_ctx && !_GP(game).IsLegacyHiRes())
333 return size * HIRES_COORD_MULTIPLIER;
334 else if (!hires_ctx && _GP(game).IsLegacyHiRes())
335 return Math::Max(1, (size / HIRES_COORD_MULTIPLIER));
336 return size;
337 }
338
defgame_to_finalgame_coords(int & x,int & y)339 AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y) {
340 // Note we support only upscale now
341 x *= _GP(game).GetScreenUpscaleMult();
342 y *= _GP(game).GetScreenUpscaleMult();
343 }
344
345 // End resolution system functions
346
347 // Create blank (black) images used to repaint borders around game frame
create_blank_image(int coldepth)348 void create_blank_image(int coldepth) {
349 // this is the first time that we try to use the graphics driver,
350 // so it's the most likey place for a crash
351 //try
352 //{
353 Bitmap *blank = BitmapHelper::CreateBitmap(16, 16, coldepth);
354 blank = ReplaceBitmapWithSupportedFormat(blank);
355 blank->Clear();
356 _G(blankImage) = _G(gfxDriver)->CreateDDBFromBitmap(blank, false, true);
357 _G(blankSidebarImage) = _G(gfxDriver)->CreateDDBFromBitmap(blank, false, true);
358 delete blank;
359 /*}
360 catch (Ali3DException gfxException)
361 {
362 quit((char*)gfxException._message);
363 }*/
364 }
365
destroy_blank_image()366 void destroy_blank_image() {
367 if (_G(blankImage))
368 _G(gfxDriver)->DestroyDDB(_G(blankImage));
369 if (_G(blankSidebarImage))
370 _G(gfxDriver)->DestroyDDB(_G(blankSidebarImage));
371 _G(blankImage) = nullptr;
372 _G(blankSidebarImage) = nullptr;
373 }
374
MakeColor(int color_index)375 int MakeColor(int color_index) {
376 color_t real_color = 0;
377 __my_setcolor(&real_color, color_index, _GP(game).GetColorDepth());
378 return real_color;
379 }
380
init_draw_method()381 void init_draw_method() {
382 if (_G(gfxDriver)->HasAcceleratedTransform()) {
383 _G(walkBehindMethod) = DrawAsSeparateSprite;
384 create_blank_image(_GP(game).GetColorDepth());
385 } else {
386 _G(walkBehindMethod) = DrawOverCharSprite;
387 }
388
389 on_mainviewport_changed();
390 init_room_drawdata();
391 if (_G(gfxDriver)->UsesMemoryBackBuffer())
392 _G(gfxDriver)->GetMemoryBackBuffer()->Clear();
393 }
394
dispose_draw_method()395 void dispose_draw_method() {
396 dispose_room_drawdata();
397 dispose_invalid_regions(false);
398 destroy_blank_image();
399 }
400
dispose_room_drawdata()401 void dispose_room_drawdata() {
402 _GP(CameraDrawData).clear();
403 dispose_invalid_regions(true);
404 }
405
on_mainviewport_changed()406 void on_mainviewport_changed() {
407 if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
408 const auto &view = _GP(play).GetMainViewport();
409 set_invalidrects_globaloffs(view.Left, view.Top);
410 // the black background region covers whole game screen
411 init_invalid_regions(-1, _GP(game).GetGameRes(), RectWH(_GP(game).GetGameRes()));
412 if (_GP(game).GetGameRes().ExceedsByAny(view.GetSize()))
413 clear_letterbox_borders();
414 }
415 }
416
417 // Allocates a bitmap for rendering camera/viewport pair (software render mode)
prepare_roomview_frame(Viewport * view)418 void prepare_roomview_frame(Viewport *view) {
419 const int view_index = view->GetID();
420 const Size view_sz = view->GetRect().GetSize();
421 const Size cam_sz = view->GetCamera()->GetRect().GetSize();
422 RoomCameraDrawData &draw_dat = _GP(CameraDrawData)[view_index];
423 // We use intermediate bitmap to render camera/viewport pair in software mode under these conditions:
424 // * camera size and viewport size are different (this may be suboptimal to paint dirty rects stretched,
425 // and also Allegro backend cannot stretch background of different colour depth).
426 // * viewport is located outside of the virtual screen (even if partially): subbitmaps cannot contain
427 // regions outside of master bitmap, and we must not clamp surface size to virtual screen because
428 // plugins may want to also use viewport bitmap, therefore it should retain full size.
429 if (cam_sz == view_sz && !draw_dat.IsOffscreen) {
430 // note we keep the buffer allocated in case it will become useful later
431 draw_dat.Frame.reset();
432 } else {
433 PBitmap &camera_frame = draw_dat.Frame;
434 PBitmap &camera_buffer = draw_dat.Buffer;
435 if (!camera_buffer || camera_buffer->GetWidth() < cam_sz.Width || camera_buffer->GetHeight() < cam_sz.Height) {
436 // Allocate new buffer bitmap with an extra size in case they will want to zoom out
437 int room_width = data_to_game_coord(_GP(thisroom).Width);
438 int room_height = data_to_game_coord(_GP(thisroom).Height);
439 Size alloc_sz = Size::Clamp(cam_sz * 2, Size(1, 1), Size(room_width, room_height));
440 camera_buffer.reset(new Bitmap(alloc_sz.Width, alloc_sz.Height, _G(gfxDriver)->GetMemoryBackBuffer()->GetColorDepth()));
441 }
442
443 if (!camera_frame || camera_frame->GetSize() != cam_sz) {
444 camera_frame.reset(BitmapHelper::CreateSubBitmap(camera_buffer.get(), RectWH(cam_sz)));
445 }
446 }
447 }
448
449 // Syncs room viewport and camera in case either size has changed
sync_roomview(Viewport * view)450 void sync_roomview(Viewport *view) {
451 if (view->GetCamera() == nullptr)
452 return;
453 // Note the dirty regions' viewport is found using absolute offset on game screen
454 init_invalid_regions(view->GetID(),
455 view->GetCamera()->GetRect().GetSize(),
456 _GP(play).GetRoomViewportAbs(view->GetID()));
457 prepare_roomview_frame(view);
458 }
459
init_room_drawdata()460 void init_room_drawdata() {
461 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
462 return;
463 // Make sure all frame buffers are created for software drawing
464 int view_count = _GP(play).GetRoomViewportCount();
465 _GP(CameraDrawData).resize(view_count);
466 for (int i = 0; i < _GP(play).GetRoomViewportCount(); ++i)
467 sync_roomview(_GP(play).GetRoomViewport(i).get());
468 }
469
on_roomviewport_created(int index)470 void on_roomviewport_created(int index) {
471 if (!_G(gfxDriver) || _G(gfxDriver)->RequiresFullRedrawEachFrame())
472 return;
473 if ((size_t)index < _GP(CameraDrawData).size())
474 return;
475 _GP(CameraDrawData).resize(index + 1);
476 }
477
on_roomviewport_deleted(int index)478 void on_roomviewport_deleted(int index) {
479 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
480 return;
481 _GP(CameraDrawData).erase(_GP(CameraDrawData).begin() + index);
482 delete_invalid_regions(index);
483 }
484
on_roomviewport_changed(Viewport * view)485 void on_roomviewport_changed(Viewport *view) {
486 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
487 return;
488 if (!view->IsVisible() || view->GetCamera() == nullptr)
489 return;
490 const bool off = !IsRectInsideRect(RectWH(_G(gfxDriver)->GetMemoryBackBuffer()->GetSize()), view->GetRect());
491 const bool off_changed = off != _GP(CameraDrawData)[view->GetID()].IsOffscreen;
492 _GP(CameraDrawData)[view->GetID()].IsOffscreen = off;
493 if (view->HasChangedSize())
494 sync_roomview(view);
495 else if (off_changed)
496 prepare_roomview_frame(view);
497 // TODO: don't have to do this all the time, perhaps do "dirty rect" method
498 // and only clear previous viewport location?
499 invalidate_screen();
500 _G(gfxDriver)->GetMemoryBackBuffer()->Clear();
501 }
502
detect_roomviewport_overlaps(size_t z_index)503 void detect_roomviewport_overlaps(size_t z_index) {
504 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
505 return;
506 // Find out if we overlap or are overlapped by anything;
507 const auto &viewports = _GP(play).GetRoomViewportsZOrdered();
508 for (; z_index < viewports.size(); ++z_index) {
509 auto this_view = viewports[z_index];
510 const int this_id = this_view->GetID();
511 bool is_overlap = false;
512 if (!this_view->IsVisible()) continue;
513 for (size_t z_index2 = 0; z_index2 < z_index; ++z_index2) {
514 if (!viewports[z_index2]->IsVisible()) continue;
515 if (AreRectsIntersecting(this_view->GetRect(), viewports[z_index2]->GetRect())) {
516 is_overlap = true;
517 break;
518 }
519 }
520 if (_GP(CameraDrawData)[this_id].IsOverlap != is_overlap) {
521 _GP(CameraDrawData)[this_id].IsOverlap = is_overlap;
522 prepare_roomview_frame(this_view.get());
523 }
524 }
525 }
526
on_roomcamera_changed(Camera * cam)527 void on_roomcamera_changed(Camera *cam) {
528 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
529 return;
530 if (cam->HasChangedSize()) {
531 auto viewrefs = cam->GetLinkedViewports();
532 for (auto vr : viewrefs) {
533 PViewport vp = vr.lock();
534 if (vp)
535 sync_roomview(vp.get());
536 }
537 }
538 // TODO: only invalidate what this particular camera sees
539 invalidate_screen();
540 }
541
mark_screen_dirty()542 void mark_screen_dirty() {
543 _G(screen_is_dirty) = true;
544 }
545
is_screen_dirty()546 bool is_screen_dirty() {
547 return _G(screen_is_dirty);
548 }
549
invalidate_screen()550 void invalidate_screen() {
551 invalidate_all_rects();
552 }
553
invalidate_camera_frame(int index)554 void invalidate_camera_frame(int index) {
555 invalidate_all_camera_rects(index);
556 }
557
invalidate_rect(int x1,int y1,int x2,int y2,bool in_room)558 void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room) {
559 invalidate_rect_ds(x1, y1, x2, y2, in_room);
560 }
561
invalidate_sprite(int x1,int y1,IDriverDependantBitmap * pic,bool in_room)562 void invalidate_sprite(int x1, int y1, IDriverDependantBitmap *pic, bool in_room) {
563 invalidate_rect_ds(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight(), in_room);
564 }
565
invalidate_sprite_glob(int x1,int y1,IDriverDependantBitmap * pic)566 void invalidate_sprite_glob(int x1, int y1, IDriverDependantBitmap *pic) {
567 invalidate_rect_global(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight());
568 }
569
mark_current_background_dirty()570 void mark_current_background_dirty() {
571 _G(current_background_is_dirty) = true;
572 }
573
574
draw_and_invalidate_text(Bitmap * ds,int x1,int y1,int font,color_t text_color,const char * text)575 void draw_and_invalidate_text(Bitmap *ds, int x1, int y1, int font, color_t text_color, const char *text) {
576 wouttext_outline(ds, x1, y1, font, text_color, text);
577 invalidate_rect(x1, y1, x1 + wgettextwidth_compensate(text, font), y1 + getfontheight_outlined(font) + get_fixed_pixel_size(1), false);
578 }
579
580 // Renders black borders for the legacy boxed game mode,
581 // where whole game screen changes size between large and small rooms
render_black_borders()582 void render_black_borders() {
583 if (_G(gfxDriver)->UsesMemoryBackBuffer())
584 return;
585 {
586 _G(gfxDriver)->BeginSpriteBatch(RectWH(_GP(game).GetGameRes()), SpriteTransform());
587 const Rect &viewport = _GP(play).GetMainViewport();
588 if (viewport.Top > 0) {
589 // letterbox borders
590 _G(blankImage)->SetStretch(_GP(game).GetGameRes().Width, viewport.Top, false);
591 _G(gfxDriver)->DrawSprite(0, 0, _G(blankImage));
592 _G(gfxDriver)->DrawSprite(0, viewport.Bottom + 1, _G(blankImage));
593 }
594 if (viewport.Left > 0) {
595 // sidebar borders for widescreen
596 _G(blankSidebarImage)->SetStretch(viewport.Left, viewport.GetHeight(), false);
597 _G(gfxDriver)->DrawSprite(0, 0, _G(blankSidebarImage));
598 _G(gfxDriver)->DrawSprite(viewport.Right + 1, 0, _G(blankSidebarImage));
599 }
600 }
601 }
602
603
render_to_screen()604 void render_to_screen() {
605 // Stage: final plugin callback (still drawn on game screen
606 if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) {
607 _G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
608 _G(gfxDriver)->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr);
609 }
610 // Stage: engine overlay
611 construct_engine_overlay();
612
613 // only vsync in full screen mode, it makes things worse in a window
614 _G(gfxDriver)->EnableVsyncBeforeRender((_GP(scsystem).vsync > 0) && (!_GP(scsystem).windowed));
615
616 bool succeeded = false;
617 while (!succeeded) {
618 // try
619 // {
620 // For software renderer, need to blacken upper part of the game frame when shaking screen moves image down
621 const Rect &viewport = _GP(play).GetMainViewport();
622 if (_GP(play).shake_screen_yoff > 0 && !_G(gfxDriver)->RequiresFullRedrawEachFrame())
623 _G(gfxDriver)->ClearRectangle(viewport.Left, viewport.Top, viewport.GetWidth() - 1, _GP(play).shake_screen_yoff, nullptr);
624 _G(gfxDriver)->Render(0, _GP(play).shake_screen_yoff, (GlobalFlipType)_GP(play).screen_flipped);
625
626 #if AGS_PLATFORM_OS_ANDROID
627 if (_GP(game).color_depth == 1)
628 android_render();
629 #elif AGS_PLATFORM_OS_IOS
630 if (_GP(game).color_depth == 1)
631 ios_render();
632 #endif
633
634 succeeded = true;
635 /*}
636 catch (Ali3DFullscreenLostException)
637 {
638 platform->Delay(500);
639 }*/
640 }
641 }
642
643 // Blanks out borders around main viewport in case it became smaller (e.g. after loading another room)
clear_letterbox_borders()644 void clear_letterbox_borders() {
645 const Rect &viewport = _GP(play).GetMainViewport();
646 _G(gfxDriver)->ClearRectangle(0, 0, _GP(game).GetGameRes().Width - 1, viewport.Top - 1, nullptr);
647 _G(gfxDriver)->ClearRectangle(0, viewport.Bottom + 1, _GP(game).GetGameRes().Width - 1, _GP(game).GetGameRes().Height - 1, nullptr);
648 }
649
draw_game_screen_callback()650 void draw_game_screen_callback() {
651 construct_game_scene(true);
652 construct_game_screen_overlay(false);
653 }
654
655
656
putpixel_compensate(Bitmap * ds,int xx,int yy,int col)657 void putpixel_compensate(Bitmap *ds, int xx, int yy, int col) {
658 if ((ds->GetColorDepth() == 32) && (col != 0)) {
659 // ensure the alpha channel is preserved if it has one
660 int alphaval = geta32(ds->GetPixel(xx, yy));
661 col = makeacol32(getr32(col), getg32(col), getb32(col), alphaval);
662 }
663 ds->FillRect(Rect(xx, yy, xx + get_fixed_pixel_size(1) - 1, yy + get_fixed_pixel_size(1) - 1), col);
664 }
665
666
667
668
draw_sprite_support_alpha(Bitmap * ds,bool ds_has_alpha,int xpos,int ypos,Bitmap * image,bool src_has_alpha,BlendMode blend_mode,int alpha)669 void draw_sprite_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, Bitmap *image, bool src_has_alpha,
670 BlendMode blend_mode, int alpha) {
671 if (alpha <= 0)
672 return;
673
674 if (_GP(game).options[OPT_SPRITEALPHA] == kSpriteAlphaRender_Proper) {
675 GfxUtil::DrawSpriteBlend(ds, Point(xpos, ypos), image, blend_mode, ds_has_alpha, src_has_alpha, alpha);
676 }
677 // Backwards-compatible drawing
678 else if (src_has_alpha && alpha == 0xFF) {
679 set_alpha_blender();
680 ds->TransBlendBlt(image, xpos, ypos);
681 } else {
682 GfxUtil::DrawSpriteWithTransparency(ds, image, xpos, ypos, alpha);
683 }
684 }
685
draw_sprite_slot_support_alpha(Bitmap * ds,bool ds_has_alpha,int xpos,int ypos,int src_slot,BlendMode blend_mode,int alpha)686 void draw_sprite_slot_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, int src_slot,
687 BlendMode blend_mode, int alpha) {
688 draw_sprite_support_alpha(ds, ds_has_alpha, xpos, ypos, _GP(spriteset)[src_slot], (_GP(game).SpriteInfos[src_slot].Flags & SPF_ALPHACHANNEL) != 0,
689 blend_mode, alpha);
690 }
691
692
recycle_ddb_bitmap(IDriverDependantBitmap * bimp,Bitmap * source,bool hasAlpha,bool opaque)693 IDriverDependantBitmap *recycle_ddb_bitmap(IDriverDependantBitmap *bimp, Bitmap *source, bool hasAlpha, bool opaque) {
694 if (bimp != nullptr) {
695 // same colour depth, width and height -> reuse
696 if (((bimp->GetColorDepth() + 1) / 8 == source->GetBPP()) &&
697 (bimp->GetWidth() == source->GetWidth()) && (bimp->GetHeight() == source->GetHeight())) {
698 _G(gfxDriver)->UpdateDDBFromBitmap(bimp, source, hasAlpha);
699 return bimp;
700 }
701
702 _G(gfxDriver)->DestroyDDB(bimp);
703 }
704 bimp = _G(gfxDriver)->CreateDDBFromBitmap(source, hasAlpha, opaque);
705 return bimp;
706 }
707
invalidate_cached_walkbehinds()708 void invalidate_cached_walkbehinds() {
709 memset(&_G(actspswbcache)[0], 0, sizeof(CachedActSpsData) * _G(actSpsCount));
710 }
711
712 // sort_out_walk_behinds: modifies the supplied sprite by overwriting parts
713 // of it with transparent pixels where there are walk-behind areas
714 // Returns whether any pixels were updated
sort_out_walk_behinds(Bitmap * sprit,int xx,int yy,int basel,Bitmap * copyPixelsFrom=nullptr,Bitmap * checkPixelsFrom=nullptr,int zoom=100)715 int sort_out_walk_behinds(Bitmap *sprit, int xx, int yy, int basel, Bitmap *copyPixelsFrom = nullptr, Bitmap *checkPixelsFrom = nullptr, int zoom = 100) {
716 if (_G(noWalkBehindsAtAll))
717 return 0;
718
719 if ((!_GP(thisroom).WalkBehindMask->IsMemoryBitmap()) ||
720 (!sprit->IsMemoryBitmap()))
721 quit("!sort_out_walk_behinds: wb bitmap not linear");
722
723 int rr, tmm, toheight; //,tcol;
724 // precalculate this to try and shave some time off
725 int maskcol = sprit->GetMaskColor();
726 int spcoldep = sprit->GetColorDepth();
727 int screenhit = _GP(thisroom).WalkBehindMask->GetHeight();
728 short *shptr, *shptr2;
729 int *loptr, *loptr2;
730 int pixelsChanged = 0;
731 int ee = 0;
732 if (xx < 0)
733 ee = 0 - xx;
734
735 if ((checkPixelsFrom != nullptr) && (checkPixelsFrom->GetColorDepth() != spcoldep))
736 quit("sprite colour depth does not match background colour depth");
737
738 for (; ee < sprit->GetWidth(); ee++) {
739 if (ee + xx >= _GP(thisroom).WalkBehindMask->GetWidth())
740 break;
741
742 if ((!_G(walkBehindExists)[ee + xx]) ||
743 (_G(walkBehindEndY)[ee + xx] <= yy) ||
744 (_G(walkBehindStartY)[ee + xx] > yy + sprit->GetHeight()))
745 continue;
746
747 toheight = sprit->GetHeight();
748
749 if (_G(walkBehindStartY)[ee + xx] < yy)
750 rr = 0;
751 else
752 rr = (_G(walkBehindStartY)[ee + xx] - yy);
753
754 // Since we will use _getpixel, ensure we only check within the screen
755 if (rr + yy < 0)
756 rr = 0 - yy;
757 if (toheight + yy > screenhit)
758 toheight = screenhit - yy;
759 if (toheight + yy > _G(walkBehindEndY)[ee + xx])
760 toheight = _G(walkBehindEndY)[ee + xx] - yy;
761 if (rr < 0)
762 rr = 0;
763
764 for (; rr < toheight; rr++) {
765
766 // we're ok with _getpixel because we've checked the screen edges
767 //tmm = _getpixel(_GP(thisroom).WalkBehindMask,ee+xx,rr+yy);
768 // actually, _getpixel is well inefficient, do it ourselves
769 // since we know it's 8-bit bitmap
770 tmm = _GP(thisroom).WalkBehindMask->GetScanLine(rr + yy)[ee + xx];
771 if (tmm < 1) continue;
772 if (_G(croom)->walkbehind_base[tmm] <= basel) continue;
773
774 if (copyPixelsFrom != nullptr) {
775 if (spcoldep <= 8) {
776 if (checkPixelsFrom->GetScanLine((rr * 100) / zoom)[(ee * 100) / zoom] != maskcol) {
777 sprit->GetScanLineForWriting(rr)[ee] = copyPixelsFrom->GetScanLine(rr + yy)[ee + xx];
778 pixelsChanged = 1;
779 }
780 } else if (spcoldep <= 16) {
781 shptr = (short *)&sprit->GetScanLine(rr)[0];
782 shptr2 = (short *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
783 if (shptr2[(ee * 100) / zoom] != maskcol) {
784 shptr[ee] = ((short *)(©PixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
785 pixelsChanged = 1;
786 }
787 } else if (spcoldep == 24) {
788 char *chptr = (char *)&sprit->GetScanLine(rr)[0];
789 char *chptr2 = (char *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
790 if (memcmp(&chptr2[((ee * 100) / zoom) * 3], &maskcol, 3) != 0) {
791 memcpy(&chptr[ee * 3], ©PixelsFrom->GetScanLine(rr + yy)[(ee + xx) * 3], 3);
792 pixelsChanged = 1;
793 }
794 } else if (spcoldep <= 32) {
795 loptr = (int *)&sprit->GetScanLine(rr)[0];
796 loptr2 = (int *)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0];
797 if (loptr2[(ee * 100) / zoom] != maskcol) {
798 loptr[ee] = ((int *)(©PixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx];
799 pixelsChanged = 1;
800 }
801 }
802 } else {
803 pixelsChanged = 1;
804 if (spcoldep <= 8)
805 sprit->GetScanLineForWriting(rr)[ee] = maskcol;
806 else if (spcoldep <= 16) {
807 shptr = (short *)&sprit->GetScanLine(rr)[0];
808 shptr[ee] = maskcol;
809 } else if (spcoldep == 24) {
810 char *chptr = (char *)&sprit->GetScanLine(rr)[0];
811 memcpy(&chptr[ee * 3], &maskcol, 3);
812 } else if (spcoldep <= 32) {
813 loptr = (int *)&sprit->GetScanLine(rr)[0];
814 loptr[ee] = maskcol;
815 } else
816 quit("!Sprite colour depth >32 ??");
817 }
818 }
819 }
820 return pixelsChanged;
821 }
822
sort_out_char_sprite_walk_behind(int actspsIndex,int xx,int yy,int basel,int zoom,int width,int height)823 void sort_out_char_sprite_walk_behind(int actspsIndex, int xx, int yy, int basel, int zoom, int width, int height) {
824 if (_G(noWalkBehindsAtAll))
825 return;
826
827 if ((!_G(actspswbcache)[actspsIndex].valid) ||
828 (_G(actspswbcache)[actspsIndex].xWas != xx) ||
829 (_G(actspswbcache)[actspsIndex].yWas != yy) ||
830 (_G(actspswbcache)[actspsIndex].baselineWas != basel)) {
831 _G(actspswb)[actspsIndex] = recycle_bitmap(_G(actspswb)[actspsIndex], _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic->GetColorDepth(), width, height, true);
832 Bitmap *wbSprite = _G(actspswb)[actspsIndex];
833
834 _G(actspswbcache)[actspsIndex].isWalkBehindHere = sort_out_walk_behinds(wbSprite, xx, yy, basel, _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), _G(actsps)[actspsIndex], zoom);
835 _G(actspswbcache)[actspsIndex].xWas = xx;
836 _G(actspswbcache)[actspsIndex].yWas = yy;
837 _G(actspswbcache)[actspsIndex].baselineWas = basel;
838 _G(actspswbcache)[actspsIndex].valid = 1;
839
840 if (_G(actspswbcache)[actspsIndex].isWalkBehindHere) {
841 _G(actspswbbmp)[actspsIndex] = recycle_ddb_bitmap(_G(actspswbbmp)[actspsIndex], _G(actspswb)[actspsIndex], false);
842 }
843 }
844
845 if (_G(actspswbcache)[actspsIndex].isWalkBehindHere) {
846 add_to_sprite_list(_G(actspswbbmp)[actspsIndex], xx, yy, basel, 0, -1, true);
847 }
848 }
849
clear_draw_list()850 void clear_draw_list() {
851 _GP(thingsToDrawList).clear();
852 }
add_thing_to_draw(IDriverDependantBitmap * bmp,int x,int y,int trans,bool alphaChannel)853 void add_thing_to_draw(IDriverDependantBitmap *bmp, int x, int y, int trans, bool alphaChannel) {
854 SpriteListEntry sprite;
855 sprite.pic = nullptr;
856 sprite.bmp = bmp;
857 sprite.x = x;
858 sprite.y = y;
859 sprite.transparent = trans;
860 sprite.hasAlphaChannel = alphaChannel;
861 _GP(thingsToDrawList).push_back(sprite);
862 }
863
864 // the sprite list is an intermediate list used to order
865 // objects and characters by their baselines before everything
866 // is added to the Thing To Draw List
clear_sprite_list()867 void clear_sprite_list() {
868 _GP(sprlist).clear();
869 }
add_to_sprite_list(IDriverDependantBitmap * spp,int xx,int yy,int baseline,int trans,int sprNum,bool isWalkBehind)870 void add_to_sprite_list(IDriverDependantBitmap *spp, int xx, int yy, int baseline, int trans, int sprNum, bool isWalkBehind) {
871
872 if (spp == nullptr)
873 quit("add_to_sprite_list: attempted to draw NULL sprite");
874 // completely invisible, so don't draw it at all
875 if (trans == 255)
876 return;
877
878 SpriteListEntry sprite;
879 if ((sprNum >= 0) && ((_GP(game).SpriteInfos[sprNum].Flags & SPF_ALPHACHANNEL) != 0))
880 sprite.hasAlphaChannel = true;
881 else
882 sprite.hasAlphaChannel = false;
883
884 sprite.bmp = spp;
885 sprite.baseline = baseline;
886 sprite.x = xx;
887 sprite.y = yy;
888 sprite.transparent = trans;
889
890 if (_G(walkBehindMethod) == DrawAsSeparateSprite)
891 sprite.takesPriorityIfEqual = !isWalkBehind;
892 else
893 sprite.takesPriorityIfEqual = isWalkBehind;
894
895 _GP(sprlist).push_back(sprite);
896 }
897
repair_alpha_channel(Bitmap * dest,Bitmap * bgpic)898 void repair_alpha_channel(Bitmap *dest, Bitmap *bgpic) {
899 // Repair the alpha channel, because sprites may have been drawn
900 // over it by the buttons, etc
901 int theWid = (dest->GetWidth() < bgpic->GetWidth()) ? dest->GetWidth() : bgpic->GetWidth();
902 int theHit = (dest->GetHeight() < bgpic->GetHeight()) ? dest->GetHeight() : bgpic->GetHeight();
903 for (int y = 0; y < theHit; y++) {
904 unsigned int *destination = ((unsigned int *)dest->GetScanLineForWriting(y));
905 unsigned int *source = ((unsigned int *)bgpic->GetScanLineForWriting(y));
906 for (int x = 0; x < theWid; x++) {
907 destination[x] |= (source[x] & 0xff000000);
908 }
909 }
910 }
911
912
913 // used by GUI renderer to draw images
draw_gui_sprite(Bitmap * ds,int pic,int x,int y,bool use_alpha,BlendMode blend_mode)914 void draw_gui_sprite(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) {
915 Bitmap *sprite = _GP(spriteset)[pic];
916 const bool ds_has_alpha = ds->GetColorDepth() == 32;
917 const bool src_has_alpha = (_GP(game).SpriteInfos[pic].Flags & SPF_ALPHACHANNEL) != 0;
918
919 if (use_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) {
920 GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha);
921 }
922 // Backwards-compatible drawing
923 else if (use_alpha && ds_has_alpha && _GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) {
924 if (src_has_alpha)
925 set_additive_alpha_blender();
926 else
927 set_opaque_alpha_blender();
928 ds->TransBlendBlt(sprite, x, y);
929 } else {
930 GfxUtil::DrawSpriteWithTransparency(ds, sprite, x, y);
931 }
932 }
933
draw_gui_sprite_v330(Bitmap * ds,int pic,int x,int y,bool use_alpha,BlendMode blend_mode)934 void draw_gui_sprite_v330(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) {
935 draw_gui_sprite(ds, pic, x, y, use_alpha && (_G(loaded_game_file_version) >= kGameVersion_330), blend_mode);
936 }
937
938 // function to sort the sprites into baseline order
spritelistentry_less(const SpriteListEntry & e1,const SpriteListEntry & e2)939 bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntry &e2) {
940 if (e1.baseline == e2.baseline) {
941 if (e1.takesPriorityIfEqual)
942 return false;
943 if (e2.takesPriorityIfEqual)
944 return true;
945 }
946 return e1.baseline < e2.baseline;
947 }
948
949
950
951
draw_sprite_list()952 void draw_sprite_list() {
953
954 if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
955 for (int ee = 1; ee < MAX_WALK_BEHINDS; ee++) {
956 if (_G(walkBehindBitmap)[ee] != nullptr) {
957 add_to_sprite_list(_G(walkBehindBitmap)[ee], _G(walkBehindLeft)[ee], _G(walkBehindTop)[ee],
958 _G(croom)->walkbehind_base[ee], 0, -1, true);
959 }
960 }
961 }
962
963 std::sort(_GP(sprlist).begin(), _GP(sprlist).end(), spritelistentry_less);
964
965 if (pl_any_want_hook(AGSE_PRESCREENDRAW))
966 add_thing_to_draw(nullptr, AGSE_PRESCREENDRAW, 0, TRANS_RUN_PLUGIN, false);
967
968 // copy the sorted sprites into the Things To Draw list
969 _GP(thingsToDrawList).insert(_GP(thingsToDrawList).end(), _GP(sprlist).begin(), _GP(sprlist).end());
970 }
971
972 // Avoid freeing and reallocating the memory if possible
recycle_bitmap(Bitmap * bimp,int coldep,int wid,int hit,bool make_transparent)973 Bitmap *recycle_bitmap(Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent) {
974 if (bimp != nullptr) {
975 // same colour depth, width and height -> reuse
976 if ((bimp->GetColorDepth() == coldep) && (bimp->GetWidth() == wid)
977 && (bimp->GetHeight() == hit)) {
978 if (make_transparent) {
979 bimp->ClearTransparent();
980 }
981 return bimp;
982 }
983
984 delete bimp;
985 }
986 bimp = make_transparent ? BitmapHelper::CreateTransparentBitmap(wid, hit, coldep) :
987 BitmapHelper::CreateBitmap(wid, hit, coldep);
988 return bimp;
989 }
990
991
992 // Get the local tint at the specified X & Y co-ordinates, based on
993 // room regions and SetAmbientTint
994 // tint_amnt will be set to 0 if there is no tint enabled
995 // if this is the case, then light_lev holds the light level (0=none)
get_local_tint(int xpp,int ypp,int nolight,int * tint_amnt,int * tint_r,int * tint_g,int * tint_b,int * tint_lit,int * light_lev)996 void get_local_tint(int xpp, int ypp, int nolight,
997 int *tint_amnt, int *tint_r, int *tint_g,
998 int *tint_b, int *tint_lit,
999 int *light_lev) {
1000
1001 int tint_level = 0, light_level = 0;
1002 int tint_amount = 0;
1003 int tint_red = 0;
1004 int tint_green = 0;
1005 int tint_blue = 0;
1006 int tint_light = 255;
1007
1008 if (nolight == 0) {
1009
1010 int onRegion = 0;
1011
1012 if ((_GP(play).ground_level_areas_disabled & GLED_EFFECTS) == 0) {
1013 // check if the player is on a region, to find its
1014 // light/tint level
1015 onRegion = GetRegionIDAtRoom(xpp, ypp);
1016 if (onRegion == 0) {
1017 // when walking, he might just be off a walkable area
1018 onRegion = GetRegionIDAtRoom(xpp - 3, ypp);
1019 if (onRegion == 0)
1020 onRegion = GetRegionIDAtRoom(xpp + 3, ypp);
1021 if (onRegion == 0)
1022 onRegion = GetRegionIDAtRoom(xpp, ypp - 3);
1023 if (onRegion == 0)
1024 onRegion = GetRegionIDAtRoom(xpp, ypp + 3);
1025 }
1026 }
1027
1028 if ((onRegion > 0) && (onRegion < MAX_ROOM_REGIONS)) {
1029 light_level = _GP(thisroom).Regions[onRegion].Light;
1030 tint_level = _GP(thisroom).Regions[onRegion].Tint;
1031 } else if (onRegion <= 0) {
1032 light_level = _GP(thisroom).Regions[0].Light;
1033 tint_level = _GP(thisroom).Regions[0].Tint;
1034 }
1035
1036 int tint_sat = (tint_level >> 24) & 0xFF;
1037 if ((_GP(game).color_depth == 1) || ((tint_level & 0x00ffffff) == 0) ||
1038 (tint_sat == 0))
1039 tint_level = 0;
1040
1041 if (tint_level) {
1042 tint_red = (unsigned char)(tint_level & 0x000ff);
1043 tint_green = (unsigned char)((tint_level >> 8) & 0x000ff);
1044 tint_blue = (unsigned char)((tint_level >> 16) & 0x000ff);
1045 tint_amount = tint_sat;
1046 tint_light = light_level;
1047 }
1048
1049 if (_GP(play).rtint_enabled) {
1050 if (_GP(play).rtint_level > 0) {
1051 // override with room tint
1052 tint_red = _GP(play).rtint_red;
1053 tint_green = _GP(play).rtint_green;
1054 tint_blue = _GP(play).rtint_blue;
1055 tint_amount = _GP(play).rtint_level;
1056 tint_light = _GP(play).rtint_light;
1057 } else {
1058 // override with room light level
1059 tint_amount = 0;
1060 light_level = _GP(play).rtint_light;
1061 }
1062 }
1063 }
1064
1065 // copy to output parameters
1066 *tint_amnt = tint_amount;
1067 *tint_r = tint_red;
1068 *tint_g = tint_green;
1069 *tint_b = tint_blue;
1070 *tint_lit = tint_light;
1071 if (light_lev)
1072 *light_lev = light_level;
1073 }
1074
1075
1076
1077
1078 // Applies the specified RGB Tint or Light Level to the actsps
1079 // sprite indexed with actspsindex
apply_tint_or_light(int actspsindex,int light_level,int tint_amount,int tint_red,int tint_green,int tint_blue,int tint_light,int coldept,Bitmap * blitFrom)1080 void apply_tint_or_light(int actspsindex, int light_level,
1081 int tint_amount, int tint_red, int tint_green,
1082 int tint_blue, int tint_light, int coldept,
1083 Bitmap *blitFrom) {
1084
1085 // In a 256-colour game, we cannot do tinting or lightening
1086 // (but we can do darkening, if light_level < 0)
1087 if (_GP(game).color_depth == 1) {
1088 if ((light_level > 0) || (tint_amount != 0))
1089 return;
1090 }
1091
1092 // we can only do tint/light if the colour depths match
1093 if (_GP(game).GetColorDepth() == _G(actsps)[actspsindex]->GetColorDepth()) {
1094 Bitmap *oldwas;
1095 // if the caller supplied a source bitmap, ->Blit from it
1096 // (used as a speed optimisation where possible)
1097 if (blitFrom)
1098 oldwas = blitFrom;
1099 // otherwise, make a new target bmp
1100 else {
1101 oldwas = _G(actsps)[actspsindex];
1102 _G(actsps)[actspsindex] = BitmapHelper::CreateBitmap(oldwas->GetWidth(), oldwas->GetHeight(), coldept);
1103 }
1104 Bitmap *active_spr = _G(actsps)[actspsindex];
1105
1106 if (tint_amount) {
1107 // It is an RGB tint
1108 tint_image(active_spr, oldwas, tint_red, tint_green, tint_blue, tint_amount, tint_light);
1109 } else {
1110 // the RGB values passed to set_trans_blender decide whether it will darken
1111 // or lighten sprites ( <128=darken, >128=lighten). The parameter passed
1112 // to LitBlendBlt defines how much it will be darkened/lightened by.
1113
1114 int lit_amnt;
1115 active_spr->FillTransparent();
1116 // It's a light level, not a tint
1117 if (_GP(game).color_depth == 1) {
1118 // 256-col
1119 lit_amnt = (250 - ((-light_level) * 5) / 2);
1120 } else {
1121 // hi-color
1122 if (light_level < 0)
1123 set_my_trans_blender(8, 8, 8, 0);
1124 else
1125 set_my_trans_blender(248, 248, 248, 0);
1126 lit_amnt = abs(light_level) * 2;
1127 }
1128
1129 active_spr->LitBlendBlt(oldwas, 0, 0, lit_amnt);
1130 }
1131
1132 if (oldwas != blitFrom)
1133 delete oldwas;
1134
1135 } else if (blitFrom) {
1136 // sprite colour depth != game colour depth, so don't try and tint
1137 // but we do need to do something, so copy the source
1138 Bitmap *active_spr = _G(actsps)[actspsindex];
1139 active_spr->Blit(blitFrom, 0, 0, 0, 0, active_spr->GetWidth(), active_spr->GetHeight());
1140 }
1141
1142 }
1143
1144 // Draws the specified 'sppic' sprite onto actsps[useindx] at the
1145 // specified width and height, and flips the sprite if necessary.
1146 // Returns 1 if something was drawn to actsps; returns 0 if no
1147 // scaling or stretching was required, in which case nothing was done
scale_and_flip_sprite(int useindx,int coldept,int zoom_level,int sppic,int newwidth,int newheight,int isMirrored)1148 int scale_and_flip_sprite(int useindx, int coldept, int zoom_level,
1149 int sppic, int newwidth, int newheight,
1150 int isMirrored) {
1151
1152 int actsps_used = 1;
1153
1154 // create and blank out the new sprite
1155 _G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, newwidth, newheight, true);
1156 Bitmap *active_spr = _G(actsps)[useindx];
1157
1158 if (zoom_level != 100) {
1159 // Scaled character
1160
1161 _G(our_eip) = 334;
1162
1163 // Ensure that anti-aliasing routines have a palette to
1164 // use for mapping while faded out
1165 if (_G(in_new_room))
1166 select_palette(_G(palette));
1167
1168
1169 if (isMirrored) {
1170 Bitmap *tempspr = BitmapHelper::CreateBitmap(newwidth, newheight, coldept);
1171 tempspr->Fill(_G(actsps)[useindx]->GetMaskColor());
1172 if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
1173 tempspr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1174 else
1175 tempspr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1176 active_spr->FlipBlt(tempspr, 0, 0, Shared::kBitmap_HFlip);
1177 delete tempspr;
1178 } else if ((IS_ANTIALIAS_SPRITES) && ((_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0))
1179 active_spr->AAStretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1180 else
1181 active_spr->StretchBlt(_GP(spriteset)[sppic], RectWH(0, 0, newwidth, newheight), Shared::kBitmap_Transparency);
1182
1183 /* AASTR2 version of code (doesn't work properly, gives black borders)
1184 if (IS_ANTIALIAS_SPRITES) {
1185 int aa_mode = AA_MASKED;
1186 if (_GP(game).spriteflags[sppic] & SPF_ALPHACHANNEL)
1187 aa_mode |= AA_ALPHA | AA_RAW_ALPHA;
1188 if (isMirrored)
1189 aa_mode |= AA_HFLIP;
1190
1191 aa_set_mode(aa_mode);
1192 ->AAStretchBlt(_G(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
1193 }
1194 else if (isMirrored) {
1195 Bitmap *tempspr = BitmapHelper::CreateBitmap_ (coldept, newwidth, newheight);
1196 ->Clear (tempspr, ->GetMaskColor(_G(actsps)[useindx]));
1197 ->StretchBlt (tempspr, _GP(spriteset)[sppic], 0, 0, newwidth, newheight);
1198 ->FlipBlt(Shared::kBitmap_HFlip, (_G(actsps)[useindx], tempspr, 0, 0);
1199 wfreeblock (tempspr);
1200 }
1201 else
1202 ->StretchBlt(_G(actsps)[useindx],_GP(spriteset)[sppic],0,0,newwidth,newheight);
1203 */
1204 if (_G(in_new_room))
1205 unselect_palette();
1206
1207 } else {
1208 // Not a scaled character, draw at normal size
1209
1210 _G(our_eip) = 339;
1211
1212 if (isMirrored)
1213 active_spr->FlipBlt(_GP(spriteset)[sppic], 0, 0, Shared::kBitmap_HFlip);
1214 else
1215 actsps_used = 0;
1216 //->Blit (_GP(spriteset)[sppic], _G(actsps)[useindx], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1217 }
1218
1219 return actsps_used;
1220 }
1221
1222
1223
1224 // create the actsps[aa] image with the object drawn correctly
1225 // returns 1 if nothing at all has changed and actsps is still
1226 // intact from last time; 0 otherwise
construct_object_gfx(int aa,int * drawnWidth,int * drawnHeight,bool alwaysUseSoftware)1227 int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) {
1228 int useindx = aa;
1229 bool hardwareAccelerated = !alwaysUseSoftware && _G(gfxDriver)->HasAcceleratedTransform();
1230
1231 if (_GP(spriteset)[_G(objs)[aa].num] == nullptr)
1232 quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", aa, _G(objs)[aa].num);
1233
1234 int coldept = _GP(spriteset)[_G(objs)[aa].num]->GetColorDepth();
1235 int sprwidth = _GP(game).SpriteInfos[_G(objs)[aa].num].Width;
1236 int sprheight = _GP(game).SpriteInfos[_G(objs)[aa].num].Height;
1237
1238 int tint_red, tint_green, tint_blue;
1239 int tint_level, tint_light, light_level;
1240 int zoom_level = 100;
1241
1242 // calculate the zoom level
1243 if ((_G(objs)[aa].flags & OBJF_USEROOMSCALING) == 0) {
1244 zoom_level = _G(objs)[aa].zoom;
1245 } else {
1246 int onarea = get_walkable_area_at_location(_G(objs)[aa].x, _G(objs)[aa].y);
1247
1248 if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
1249 // just off the edge of an area -- use the scaling we had
1250 // while on the area
1251 zoom_level = _G(objs)[aa].zoom;
1252 } else
1253 zoom_level = get_area_scaling(onarea, _G(objs)[aa].x, _G(objs)[aa].y);
1254 }
1255
1256 if (zoom_level != 100)
1257 scale_sprite_size(_G(objs)[aa].num, zoom_level, &sprwidth, &sprheight);
1258 _G(objs)[aa].zoom = zoom_level;
1259
1260 // save width/height into parameters if requested
1261 if (drawnWidth)
1262 *drawnWidth = sprwidth;
1263 if (drawnHeight)
1264 *drawnHeight = sprheight;
1265
1266 _G(objs)[aa].last_width = sprwidth;
1267 _G(objs)[aa].last_height = sprheight;
1268
1269 tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0;
1270
1271 if (_G(objs)[aa].flags & OBJF_HASTINT) {
1272 // object specific tint, use it
1273 tint_red = _G(objs)[aa].tint_r;
1274 tint_green = _G(objs)[aa].tint_g;
1275 tint_blue = _G(objs)[aa].tint_b;
1276 tint_level = _G(objs)[aa].tint_level;
1277 tint_light = _G(objs)[aa].tint_light;
1278 light_level = 0;
1279 } else if (_G(objs)[aa].flags & OBJF_HASLIGHT) {
1280 light_level = _G(objs)[aa].tint_light;
1281 } else {
1282 // get the ambient or region tint
1283 int ignoreRegionTints = 1;
1284 if (_G(objs)[aa].flags & OBJF_USEREGIONTINTS)
1285 ignoreRegionTints = 0;
1286
1287 get_local_tint(_G(objs)[aa].x, _G(objs)[aa].y, ignoreRegionTints,
1288 &tint_level, &tint_red, &tint_green, &tint_blue,
1289 &tint_light, &light_level);
1290 }
1291
1292 // check whether the image should be flipped
1293 int isMirrored = 0;
1294 if ((_G(objs)[aa].view != (uint16_t)-1) &&
1295 (_G(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].pic == _G(objs)[aa].num) &&
1296 ((_G(views)[_G(objs)[aa].view].loops[_G(objs)[aa].loop].frames[_G(objs)[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) {
1297 isMirrored = 1;
1298 }
1299
1300 if ((hardwareAccelerated) &&
1301 (_G(walkBehindMethod) != DrawOverCharSprite) &&
1302 (_G(objcache)[aa].image != nullptr) &&
1303 (_G(objcache)[aa].sppic == _G(objs)[aa].num) &&
1304 (_G(actsps)[useindx] != nullptr)) {
1305 // HW acceleration
1306 _G(objcache)[aa].tintamntwas = tint_level;
1307 _G(objcache)[aa].tintredwas = tint_red;
1308 _G(objcache)[aa].tintgrnwas = tint_green;
1309 _G(objcache)[aa].tintbluwas = tint_blue;
1310 _G(objcache)[aa].tintlightwas = tint_light;
1311 _G(objcache)[aa].lightlevwas = light_level;
1312 _G(objcache)[aa].zoomWas = zoom_level;
1313 _G(objcache)[aa].mirroredWas = isMirrored;
1314
1315 return 1;
1316 }
1317
1318 if ((!hardwareAccelerated) && (_G(gfxDriver)->HasAcceleratedTransform())) {
1319 // They want to draw it in software mode with the D3D driver,
1320 // so force a redraw
1321 _G(objcache)[aa].sppic = -389538;
1322 }
1323
1324 // If we have the image cached, use it
1325 if ((_G(objcache)[aa].image != nullptr) &&
1326 (_G(objcache)[aa].sppic == _G(objs)[aa].num) &&
1327 (_G(objcache)[aa].tintamntwas == tint_level) &&
1328 (_G(objcache)[aa].tintlightwas == tint_light) &&
1329 (_G(objcache)[aa].tintredwas == tint_red) &&
1330 (_G(objcache)[aa].tintgrnwas == tint_green) &&
1331 (_G(objcache)[aa].tintbluwas == tint_blue) &&
1332 (_G(objcache)[aa].lightlevwas == light_level) &&
1333 (_G(objcache)[aa].zoomWas == zoom_level) &&
1334 (_G(objcache)[aa].mirroredWas == isMirrored)) {
1335 // the image is the same, we can use it cached!
1336 if ((_G(walkBehindMethod) != DrawOverCharSprite) &&
1337 (_G(actsps)[useindx] != nullptr))
1338 return 1;
1339 // Check if the X & Y co-ords are the same, too -- if so, there
1340 // is scope for further optimisations
1341 if ((_G(objcache)[aa].xwas == _G(objs)[aa].x) &&
1342 (_G(objcache)[aa].ywas == _G(objs)[aa].y) &&
1343 (_G(actsps)[useindx] != nullptr) &&
1344 (_G(walk_behind_baselines_changed) == 0))
1345 return 1;
1346 _G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, sprwidth, sprheight);
1347 _G(actsps)[useindx]->Blit(_G(objcache)[aa].image, 0, 0, 0, 0, _G(objcache)[aa].image->GetWidth(), _G(objcache)[aa].image->GetHeight());
1348 return 0;
1349 }
1350
1351 // Not cached, so draw the image
1352
1353 int actspsUsed = 0;
1354 if (!hardwareAccelerated) {
1355 // draw the base sprite, scaled and flipped as appropriate
1356 actspsUsed = scale_and_flip_sprite(useindx, coldept, zoom_level,
1357 _G(objs)[aa].num, sprwidth, sprheight, isMirrored);
1358 } else {
1359 // ensure actsps exists
1360 _G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, _GP(game).SpriteInfos[_G(objs)[aa].num].Width, _GP(game).SpriteInfos[_G(objs)[aa].num].Height);
1361 }
1362
1363 // direct read from source bitmap, where possible
1364 Bitmap *comeFrom = nullptr;
1365 if (!actspsUsed)
1366 comeFrom = _GP(spriteset)[_G(objs)[aa].num];
1367
1368 // apply tints or lightenings where appropriate, else just copy
1369 // the source bitmap
1370 if (!hardwareAccelerated && ((tint_level > 0) || (light_level != 0))) {
1371 apply_tint_or_light(useindx, light_level, tint_level, tint_red,
1372 tint_green, tint_blue, tint_light, coldept,
1373 comeFrom);
1374 } else if (!actspsUsed) {
1375 _G(actsps)[useindx]->Blit(_GP(spriteset)[_G(objs)[aa].num], 0, 0, 0, 0, _GP(game).SpriteInfos[_G(objs)[aa].num].Width, _GP(game).SpriteInfos[_G(objs)[aa].num].Height);
1376 }
1377
1378 // Re-use the bitmap if it's the same size
1379 _G(objcache)[aa].image = recycle_bitmap(_G(objcache)[aa].image, coldept, sprwidth, sprheight);
1380 // Create the cached image and store it
1381 _G(objcache)[aa].image->Blit(_G(actsps)[useindx], 0, 0, 0, 0, sprwidth, sprheight);
1382 _G(objcache)[aa].sppic = _G(objs)[aa].num;
1383 _G(objcache)[aa].tintamntwas = tint_level;
1384 _G(objcache)[aa].tintredwas = tint_red;
1385 _G(objcache)[aa].tintgrnwas = tint_green;
1386 _G(objcache)[aa].tintbluwas = tint_blue;
1387 _G(objcache)[aa].tintlightwas = tint_light;
1388 _G(objcache)[aa].lightlevwas = light_level;
1389 _G(objcache)[aa].zoomWas = zoom_level;
1390 _G(objcache)[aa].mirroredWas = isMirrored;
1391 return 0;
1392 }
1393
1394
1395
1396
1397 // This is only called from draw_screen_background, but it's seperated
1398 // to help with profiling the program
prepare_objects_for_drawing()1399 void prepare_objects_for_drawing() {
1400 _G(our_eip) = 32;
1401
1402 for (int aa = 0; aa < _G(croom)->numobj; aa++) {
1403 if (_G(objs)[aa].on != 1) continue;
1404 // offscreen, don't draw
1405 if ((_G(objs)[aa].x >= _GP(thisroom).Width) || (_G(objs)[aa].y < 1))
1406 continue;
1407
1408 const int useindx = aa;
1409 int tehHeight;
1410 int actspsIntact = construct_object_gfx(aa, nullptr, &tehHeight, false);
1411
1412 // update the cache for next time
1413 _G(objcache)[aa].xwas = _G(objs)[aa].x;
1414 _G(objcache)[aa].ywas = _G(objs)[aa].y;
1415 int atxp = data_to_game_coord(_G(objs)[aa].x);
1416 int atyp = data_to_game_coord(_G(objs)[aa].y) - tehHeight;
1417
1418 int usebasel = _G(objs)[aa].get_baseline();
1419
1420 if (_G(objs)[aa].flags & OBJF_NOWALKBEHINDS) {
1421 // ignore walk-behinds, do nothing
1422 if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1423 usebasel += _GP(thisroom).Height;
1424 }
1425 } else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
1426 sort_out_char_sprite_walk_behind(useindx, atxp, atyp, usebasel, _G(objs)[aa].zoom, _G(objs)[aa].last_width, _G(objs)[aa].last_height);
1427 } else if ((!actspsIntact) && (_G(walkBehindMethod) == DrawOverCharSprite)) {
1428 sort_out_walk_behinds(_G(actsps)[useindx], atxp, atyp, usebasel);
1429 }
1430
1431 if ((!actspsIntact) || (_G(actspsbmp)[useindx] == nullptr)) {
1432 bool hasAlpha = (_GP(game).SpriteInfos[_G(objs)[aa].num].Flags & SPF_ALPHACHANNEL) != 0;
1433
1434 if (_G(actspsbmp)[useindx] != nullptr)
1435 _G(gfxDriver)->DestroyDDB(_G(actspsbmp)[useindx]);
1436 _G(actspsbmp)[useindx] = _G(gfxDriver)->CreateDDBFromBitmap(_G(actsps)[useindx], hasAlpha);
1437 }
1438
1439 if (_G(gfxDriver)->HasAcceleratedTransform()) {
1440 _G(actspsbmp)[useindx]->SetFlippedLeftRight(_G(objcache)[aa].mirroredWas != 0);
1441 _G(actspsbmp)[useindx]->SetStretch(_G(objs)[aa].last_width, _G(objs)[aa].last_height);
1442 _G(actspsbmp)[useindx]->SetTint(_G(objcache)[aa].tintredwas, _G(objcache)[aa].tintgrnwas, _G(objcache)[aa].tintbluwas, (_G(objcache)[aa].tintamntwas * 256) / 100);
1443
1444 if (_G(objcache)[aa].tintamntwas > 0) {
1445 if (_G(objcache)[aa].tintlightwas == 0) // luminance of 0 -- pass 1 to enable
1446 _G(actspsbmp)[useindx]->SetLightLevel(1);
1447 else if (_G(objcache)[aa].tintlightwas < 250)
1448 _G(actspsbmp)[useindx]->SetLightLevel(_G(objcache)[aa].tintlightwas);
1449 else
1450 _G(actspsbmp)[useindx]->SetLightLevel(0);
1451 } else if (_G(objcache)[aa].lightlevwas != 0)
1452 _G(actspsbmp)[useindx]->SetLightLevel((_G(objcache)[aa].lightlevwas * 25) / 10 + 256);
1453 else
1454 _G(actspsbmp)[useindx]->SetLightLevel(0);
1455 }
1456
1457 add_to_sprite_list(_G(actspsbmp)[useindx], atxp, atyp, usebasel, _G(objs)[aa].transparent, _G(objs)[aa].num);
1458 }
1459 }
1460
1461
1462
1463 // Draws srcimg onto destimg, tinting to the specified level
1464 // Totally overwrites the contents of the destination image
tint_image(Bitmap * ds,Bitmap * srcimg,int red,int grn,int blu,int light_level,int luminance)1465 void tint_image(Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light_level, int luminance) {
1466
1467 if ((srcimg->GetColorDepth() != ds->GetColorDepth()) ||
1468 (srcimg->GetColorDepth() <= 8)) {
1469 debug_script_warn("Image tint failed - images must both be hi-color");
1470 // the caller expects something to have been copied
1471 ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight());
1472 return;
1473 }
1474
1475 // For performance reasons, we have a seperate blender for
1476 // when light is being adjusted and when it is not.
1477 // If luminance >= 250, then normal brightness, otherwise darken
1478 if (luminance >= 250)
1479 set_blender_mode(kTintBlenderMode, red, grn, blu, 0);
1480 else
1481 set_blender_mode(kTintLightBlenderMode, red, grn, blu, 0);
1482
1483 if (light_level >= 100) {
1484 // fully colourised
1485 ds->FillTransparent();
1486 ds->LitBlendBlt(srcimg, 0, 0, luminance);
1487 } else {
1488 // light_level is between -100 and 100 normally; 0-100 in
1489 // this case when it's a RGB tint
1490 light_level = (light_level * 25) / 10;
1491
1492 // Copy the image to the new bitmap
1493 ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight());
1494 // Render the colourised image to a temporary bitmap,
1495 // then transparently draw it over the original image
1496 Bitmap *finaltarget = BitmapHelper::CreateTransparentBitmap(srcimg->GetWidth(), srcimg->GetHeight(), srcimg->GetColorDepth());
1497 finaltarget->LitBlendBlt(srcimg, 0, 0, luminance);
1498
1499 // customized trans blender to preserve alpha channel
1500 set_my_trans_blender(0, 0, 0, light_level);
1501 ds->TransBlendBlt(finaltarget, 0, 0);
1502 delete finaltarget;
1503 }
1504 }
1505
1506
1507
1508
prepare_characters_for_drawing()1509 void prepare_characters_for_drawing() {
1510 int zoom_level, newwidth, newheight, onarea, sppic;
1511 int light_level, coldept;
1512 int tint_red, tint_green, tint_blue, tint_amount, tint_light = 255;
1513
1514 _G(our_eip) = 33;
1515
1516 // draw characters
1517 for (int aa = 0; aa < _GP(game).numcharacters; aa++) {
1518 if (_GP(game).chars[aa].on == 0) continue;
1519 if (_GP(game).chars[aa].room != _G(displayed_room)) continue;
1520 _G(eip_guinum) = aa;
1521 const int useindx = aa + MAX_ROOM_OBJECTS;
1522
1523 CharacterInfo *chin = &_GP(game).chars[aa];
1524 _G(our_eip) = 330;
1525 // if it's on but set to view -1, they're being silly
1526 if (chin->view < 0) {
1527 quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
1528 chin->name, _G(displayed_room));
1529 }
1530
1531 if (chin->frame >= _G(views)[chin->view].loops[chin->loop].numFrames)
1532 chin->frame = 0;
1533
1534 if ((chin->loop >= _G(views)[chin->view].numLoops) ||
1535 (_G(views)[chin->view].loops[chin->loop].numFrames < 1)) {
1536 warning("The character '%s' could not be displayed because there were no frames in loop %d of view %d.",
1537 chin->name, chin->loop, chin->view + 1);
1538 continue;
1539 }
1540
1541 sppic = _G(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
1542 if (sppic < 0)
1543 sppic = 0; // in case it's screwed up somehow
1544 _G(our_eip) = 331;
1545 // sort out the stretching if required
1546 onarea = get_walkable_area_at_character(aa);
1547 _G(our_eip) = 332;
1548
1549 // calculates the zoom level
1550 if (chin->flags & CHF_MANUALSCALING) // character ignores scaling
1551 zoom_level = _G(charextra)[aa].zoom;
1552 else if ((onarea <= 0) && (_GP(thisroom).WalkAreas[0].ScalingFar == 0)) {
1553 zoom_level = _G(charextra)[aa].zoom;
1554 // NOTE: room objects don't have this fix
1555 if (zoom_level == 0)
1556 zoom_level = 100;
1557 } else
1558 zoom_level = get_area_scaling(onarea, chin->x, chin->y);
1559
1560 _G(charextra)[aa].zoom = zoom_level;
1561
1562 tint_red = tint_green = tint_blue = tint_amount = tint_light = light_level = 0;
1563
1564 if (chin->flags & CHF_HASTINT) {
1565 // object specific tint, use it
1566 tint_red = _G(charextra)[aa].tint_r;
1567 tint_green = _G(charextra)[aa].tint_g;
1568 tint_blue = _G(charextra)[aa].tint_b;
1569 tint_amount = _G(charextra)[aa].tint_level;
1570 tint_light = _G(charextra)[aa].tint_light;
1571 light_level = 0;
1572 } else if (chin->flags & CHF_HASLIGHT) {
1573 light_level = _G(charextra)[aa].tint_light;
1574 } else {
1575 get_local_tint(chin->x, chin->y, chin->flags & CHF_NOLIGHTING,
1576 &tint_amount, &tint_red, &tint_green, &tint_blue,
1577 &tint_light, &light_level);
1578 }
1579
1580 _G(our_eip) = 3330;
1581 int isMirrored = 0, specialpic = sppic;
1582 bool usingCachedImage = false;
1583
1584 coldept = _GP(spriteset)[sppic]->GetColorDepth();
1585
1586 // adjust the sppic if mirrored, so it doesn't accidentally
1587 // cache the mirrored frame as the real one
1588 if (_G(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE) {
1589 isMirrored = 1;
1590 specialpic = -sppic;
1591 }
1592
1593 _G(our_eip) = 3331;
1594
1595 // if the character was the same sprite and scaling last time,
1596 // just use the cached image
1597 if ((_G(charcache)[aa].inUse) &&
1598 (_G(charcache)[aa].sppic == specialpic) &&
1599 (_G(charcache)[aa].scaling == zoom_level) &&
1600 (_G(charcache)[aa].tintredwas == tint_red) &&
1601 (_G(charcache)[aa].tintgrnwas == tint_green) &&
1602 (_G(charcache)[aa].tintbluwas == tint_blue) &&
1603 (_G(charcache)[aa].tintamntwas == tint_amount) &&
1604 (_G(charcache)[aa].tintlightwas == tint_light) &&
1605 (_G(charcache)[aa].lightlevwas == light_level)) {
1606 if (_G(walkBehindMethod) == DrawOverCharSprite) {
1607 _G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], _G(charcache)[aa].image->GetColorDepth(), _G(charcache)[aa].image->GetWidth(), _G(charcache)[aa].image->GetHeight());
1608 _G(actsps)[useindx]->Blit(_G(charcache)[aa].image, 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1609 } else {
1610 usingCachedImage = true;
1611 }
1612 } else if ((_G(charcache)[aa].inUse) &&
1613 (_G(charcache)[aa].sppic == specialpic) &&
1614 (_G(gfxDriver)->HasAcceleratedTransform())) {
1615 usingCachedImage = true;
1616 } else if (_G(charcache)[aa].inUse) {
1617 //destroy_bitmap (charcache[aa].image);
1618 _G(charcache)[aa].inUse = 0;
1619 }
1620
1621 _G(our_eip) = 3332;
1622
1623 if (zoom_level != 100) {
1624 // it needs to be stretched, so calculate the new dimensions
1625
1626 scale_sprite_size(sppic, zoom_level, &newwidth, &newheight);
1627 _G(charextra)[aa].width = newwidth;
1628 _G(charextra)[aa].height = newheight;
1629 } else {
1630 // draw at original size, so just use the sprite width and height
1631 // TODO: store width and height always, that's much simplier to use for reference!
1632 _G(charextra)[aa].width = 0;
1633 _G(charextra)[aa].height = 0;
1634 newwidth = _GP(game).SpriteInfos[sppic].Width;
1635 newheight = _GP(game).SpriteInfos[sppic].Height;
1636 }
1637
1638 _G(our_eip) = 3336;
1639
1640 // Calculate the X & Y co-ordinates of where the sprite will be
1641 const int atxp = (data_to_game_coord(chin->x)) - newwidth / 2;
1642 const int atyp = (data_to_game_coord(chin->y) - newheight)
1643 // adjust the Y positioning for the character's Z co-ord
1644 - data_to_game_coord(chin->z);
1645
1646 _G(charcache)[aa].scaling = zoom_level;
1647 _G(charcache)[aa].sppic = specialpic;
1648 _G(charcache)[aa].tintredwas = tint_red;
1649 _G(charcache)[aa].tintgrnwas = tint_green;
1650 _G(charcache)[aa].tintbluwas = tint_blue;
1651 _G(charcache)[aa].tintamntwas = tint_amount;
1652 _G(charcache)[aa].tintlightwas = tint_light;
1653 _G(charcache)[aa].lightlevwas = light_level;
1654
1655 // If cache needs to be re-drawn
1656 if (!_G(charcache)[aa].inUse) {
1657
1658 // create the base sprite in actsps[useindx], which will
1659 // be scaled and/or flipped, as appropriate
1660 int actspsUsed = 0;
1661 if (!_G(gfxDriver)->HasAcceleratedTransform()) {
1662 actspsUsed = scale_and_flip_sprite(
1663 useindx, coldept, zoom_level, sppic,
1664 newwidth, newheight, isMirrored);
1665 } else {
1666 // ensure actsps exists
1667 _G(actsps)[useindx] = recycle_bitmap(_G(actsps)[useindx], coldept, _GP(game).SpriteInfos[sppic].Width, _GP(game).SpriteInfos[sppic].Height);
1668 }
1669
1670 _G(our_eip) = 335;
1671
1672 if (((light_level != 0) || (tint_amount != 0)) &&
1673 (!_G(gfxDriver)->HasAcceleratedTransform())) {
1674 // apply the lightening or tinting
1675 Bitmap *comeFrom = nullptr;
1676 // if possible, direct read from the source image
1677 if (!actspsUsed)
1678 comeFrom = _GP(spriteset)[sppic];
1679
1680 apply_tint_or_light(useindx, light_level, tint_amount, tint_red,
1681 tint_green, tint_blue, tint_light, coldept,
1682 comeFrom);
1683 } else if (!actspsUsed) {
1684 // no scaling, flipping or tinting was done, so just blit it normally
1685 _G(actsps)[useindx]->Blit(_GP(spriteset)[sppic], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1686 }
1687
1688 // update the character cache with the new image
1689 _G(charcache)[aa].inUse = 1;
1690 //_G(charcache)[aa].image = BitmapHelper::CreateBitmap_ (coldept, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1691 _G(charcache)[aa].image = recycle_bitmap(_G(charcache)[aa].image, coldept, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1692 _G(charcache)[aa].image->Blit(_G(actsps)[useindx], 0, 0, 0, 0, _G(actsps)[useindx]->GetWidth(), _G(actsps)[useindx]->GetHeight());
1693
1694 } // end if !cache.inUse
1695
1696 int usebasel = chin->get_baseline();
1697
1698 _G(our_eip) = 336;
1699
1700 const int bgX = atxp + chin->pic_xoffs;
1701 const int bgY = atyp + chin->pic_yoffs;
1702
1703 if (chin->flags & CHF_NOWALKBEHINDS) {
1704 // ignore walk-behinds, do nothing
1705 if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1706 usebasel += _GP(thisroom).Height;
1707 }
1708 } else if (_G(walkBehindMethod) == DrawAsSeparateCharSprite) {
1709 sort_out_char_sprite_walk_behind(useindx, bgX, bgY, usebasel, _G(charextra)[aa].zoom, newwidth, newheight);
1710 } else if (_G(walkBehindMethod) == DrawOverCharSprite) {
1711 sort_out_walk_behinds(_G(actsps)[useindx], bgX, bgY, usebasel);
1712 }
1713
1714 if ((!usingCachedImage) || (_G(actspsbmp)[useindx] == nullptr)) {
1715 bool hasAlpha = (_GP(game).SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0;
1716
1717 _G(actspsbmp)[useindx] = recycle_ddb_bitmap(_G(actspsbmp)[useindx], _G(actsps)[useindx], hasAlpha);
1718 }
1719
1720 if (_G(gfxDriver)->HasAcceleratedTransform()) {
1721 _G(actspsbmp)[useindx]->SetStretch(newwidth, newheight);
1722 _G(actspsbmp)[useindx]->SetFlippedLeftRight(isMirrored != 0);
1723 _G(actspsbmp)[useindx]->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100);
1724
1725 if (tint_amount != 0) {
1726 if (tint_light == 0) // tint with 0 luminance, pass as 1 instead
1727 _G(actspsbmp)[useindx]->SetLightLevel(1);
1728 else if (tint_light < 250)
1729 _G(actspsbmp)[useindx]->SetLightLevel(tint_light);
1730 else
1731 _G(actspsbmp)[useindx]->SetLightLevel(0);
1732 } else if (light_level != 0)
1733 _G(actspsbmp)[useindx]->SetLightLevel((light_level * 25) / 10 + 256);
1734 else
1735 _G(actspsbmp)[useindx]->SetLightLevel(0);
1736
1737 }
1738
1739 _G(our_eip) = 337;
1740
1741 chin->actx = atxp;
1742 chin->acty = atyp;
1743
1744 add_to_sprite_list(_G(actspsbmp)[useindx], bgX, bgY, usebasel, chin->transparency, sppic);
1745 }
1746 }
1747
1748
1749 // Compiles a list of room sprites (characters, objects, background)
prepare_room_sprites()1750 void prepare_room_sprites() {
1751 // Background sprite is required for the non-software renderers always,
1752 // and for software renderer in case there are overlapping viewports.
1753 // Note that software DDB is just a tiny wrapper around bitmap, so overhead is negligible.
1754 if (_G(roomBackgroundBmp) == nullptr) {
1755 update_polled_stuff_if_runtime();
1756 _G(roomBackgroundBmp) = _G(gfxDriver)->CreateDDBFromBitmap(_GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false, true);
1757 } else if (_G(current_background_is_dirty)) {
1758 update_polled_stuff_if_runtime();
1759 _G(gfxDriver)->UpdateDDBFromBitmap(_G(roomBackgroundBmp), _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), false);
1760 }
1761 if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
1762 if (_G(current_background_is_dirty) || _G(walkBehindsCachedForBgNum) != _GP(play).bg_frame) {
1763 if (_G(walkBehindMethod) == DrawAsSeparateSprite) {
1764 update_walk_behind_images();
1765 }
1766 }
1767 add_thing_to_draw(_G(roomBackgroundBmp), 0, 0, 0, false);
1768 }
1769 _G(current_background_is_dirty) = false; // Note this is only place where this flag is checked
1770
1771 clear_sprite_list();
1772
1773 if ((_G(debug_flags) & DBG_NOOBJECTS) == 0) {
1774 prepare_objects_for_drawing();
1775 prepare_characters_for_drawing();
1776
1777 if ((_G(debug_flags) & DBG_NODRAWSPRITES) == 0) {
1778 _G(our_eip) = 34;
1779 draw_sprite_list();
1780 }
1781 }
1782 _G(our_eip) = 36;
1783 }
1784
1785 // Draws the black surface behind (or rather between) the room viewports
draw_preroom_background()1786 void draw_preroom_background() {
1787 if (_G(gfxDriver)->RequiresFullRedrawEachFrame())
1788 return;
1789 update_black_invreg_and_reset(_G(gfxDriver)->GetMemoryBackBuffer());
1790 }
1791
1792 // Draws the room background on the given surface.
1793 //
1794 // NOTE that this is **strictly** for software rendering.
1795 // ds is a full game screen surface, and roomcam_surface is a surface for drawing room camera content to.
1796 // ds and roomcam_surface may be the same bitmap.
1797 // no_transform flag tells to copy dirty regions on roomcam_surface without any coordinate conversion
1798 // whatsoever.
draw_room_background(Viewport * view,const SpriteTransform & room_trans)1799 PBitmap draw_room_background(Viewport *view, const SpriteTransform &room_trans) {
1800 _G(our_eip) = 31;
1801
1802 // For the sake of software renderer, if there is any kind of camera transform required
1803 // except screen offset, we tell it to draw on separate bitmap first with zero transformation.
1804 // There are few reasons for this, primary is that Allegro does not support StretchBlt
1805 // between different colour depths (i.e. it won't correctly stretch blit 16-bit rooms to
1806 // 32-bit virtual screen).
1807 // Also see comment to ALSoftwareGraphicsDriver::RenderToBackBuffer().
1808 const int view_index = view->GetID();
1809 Bitmap *ds = _G(gfxDriver)->GetMemoryBackBuffer();
1810 // If separate bitmap was prepared for this view/camera pair then use it, draw untransformed
1811 // and blit transformed whole surface later.
1812 const bool draw_to_camsurf = _GP(CameraDrawData)[view_index].Frame != nullptr;
1813 Bitmap *roomcam_surface = draw_to_camsurf ? _GP(CameraDrawData)[view_index].Frame.get() : ds;
1814 {
1815 // For software renderer: copy dirty rects onto the virtual screen.
1816 // TODO: that would be SUPER NICE to reorganize the code and move this operation into SoftwareGraphicDriver somehow.
1817 // Because basically we duplicate sprite batch transform here.
1818
1819 auto camera = view->GetCamera();
1820 set_invalidrects_cameraoffs(view_index, camera->GetRect().Left, camera->GetRect().Top);
1821
1822 // TODO: (by CJ)
1823 // the following line takes up to 50% of the game CPU time at
1824 // high resolutions and colour depths - if we can optimise it
1825 // somehow, significant performance gains to be had
1826 update_room_invreg_and_reset(view_index, roomcam_surface, _GP(thisroom).BgFrames[_GP(play).bg_frame].Graphic.get(), draw_to_camsurf);
1827 }
1828
1829 return _GP(CameraDrawData)[view_index].Frame;
1830 }
1831
1832
draw_fps(const Rect & viewport)1833 void draw_fps(const Rect &viewport) {
1834 // TODO: make allocated "fps struct" instead of using static vars!!
1835 static IDriverDependantBitmap *ddb = nullptr;
1836 static Bitmap *fpsDisplay = nullptr;
1837 const int font = FONT_NORMAL;
1838 if (fpsDisplay == nullptr) {
1839 fpsDisplay = BitmapHelper::CreateBitmap(viewport.GetWidth(), (getfontheight_outlined(font) + get_fixed_pixel_size(5)), _GP(game).GetColorDepth());
1840 fpsDisplay = ReplaceBitmapWithSupportedFormat(fpsDisplay);
1841 }
1842 fpsDisplay->ClearTransparent();
1843
1844 color_t text_color = fpsDisplay->GetCompatibleColor(14);
1845
1846 char base_buffer[20];
1847 if (!isTimerFpsMaxed()) {
1848 sprintf(base_buffer, "%d", _G(frames_per_second));
1849 } else {
1850 sprintf(base_buffer, "unlimited");
1851 }
1852
1853 char fps_buffer[60];
1854 // Don't display fps if we don't have enough information (because loop count was just reset)
1855 if (!std::isUndefined(_G(fps))) {
1856 snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", _G(fps), base_buffer);
1857 } else {
1858 snprintf(fps_buffer, sizeof(fps_buffer), "FPS: --.- / %s", base_buffer);
1859 }
1860 wouttext_outline(fpsDisplay, 1, 1, font, text_color, fps_buffer);
1861
1862 char loop_buffer[60];
1863 sprintf(loop_buffer, "Loop %u", _G(loopcounter));
1864 wouttext_outline(fpsDisplay, viewport.GetWidth() / 2, 1, font, text_color, loop_buffer);
1865
1866 if (ddb)
1867 _G(gfxDriver)->UpdateDDBFromBitmap(ddb, fpsDisplay, false);
1868 else
1869 ddb = _G(gfxDriver)->CreateDDBFromBitmap(fpsDisplay, false);
1870 int yp = viewport.GetHeight() - fpsDisplay->GetHeight();
1871 _G(gfxDriver)->DrawSprite(1, yp, ddb);
1872 invalidate_sprite_glob(1, yp, ddb);
1873 }
1874
1875 // Draw GUI and overlays of all kinds, anything outside the room space
draw_gui_and_overlays()1876 void draw_gui_and_overlays() {
1877 int gg;
1878
1879 if (pl_any_want_hook(AGSE_PREGUIDRAW))
1880 add_thing_to_draw(nullptr, AGSE_PREGUIDRAW, 0, TRANS_RUN_PLUGIN, false);
1881
1882 // draw overlays, except text boxes and portraits
1883 for (gg = 0; gg < _G(numscreenover); gg++) {
1884 // complete overlay draw in non-transparent mode
1885 if (_G(screenover)[gg].type == OVER_COMPLETE)
1886 add_thing_to_draw(_G(screenover)[gg].bmp, _G(screenover)[gg].x, _G(screenover)[gg].y, TRANS_OPAQUE, false);
1887 else if (_G(screenover)[gg].type != OVER_TEXTMSG && _G(screenover)[gg].type != OVER_PICTURE) {
1888 int tdxp, tdyp;
1889 get_overlay_position(_G(screenover)[gg], &tdxp, &tdyp);
1890 add_thing_to_draw(_G(screenover)[gg].bmp, tdxp, tdyp, 0, _G(screenover)[gg].hasAlphaChannel);
1891 }
1892 }
1893
1894 // Draw GUIs - they should always be on top of overlays like
1895 // speech background text
1896 _G(our_eip) = 35;
1897 if (((_G(debug_flags) & DBG_NOIFACE) == 0) && (_G(displayed_room) >= 0)) {
1898 int aa;
1899
1900 if (_G(playerchar)->activeinv >= MAX_INV) {
1901 quit("!The player.activeinv variable has been corrupted, probably as a result\n"
1902 "of an incorrect assignment in the game script.");
1903 }
1904 if (_G(playerchar)->activeinv < 1)
1905 _G(gui_inv_pic) = -1;
1906 else
1907 _G(gui_inv_pic) = _GP(game).invinfo[_G(playerchar)->activeinv].pic;
1908 _G(our_eip) = 37;
1909 {
1910 for (aa = 0; aa < _GP(game).numgui; aa++) {
1911 if (!_GP(guis)[aa].IsDisplayed()) continue;
1912 if (!_GP(guis)[aa].HasChanged()) continue;
1913 if (_GP(guis)[aa].Transparency == 255) continue;
1914
1915 _GP(guis)[aa].ClearChanged();
1916 if (_G(guibg)[aa] == nullptr)
1917 recreate_guibg_image(&_GP(guis)[aa]);
1918
1919 _G(eip_guinum) = aa;
1920 _G(our_eip) = 370;
1921 _G(guibg)[aa]->ClearTransparent();
1922 _G(our_eip) = 372;
1923 _GP(guis)[aa].DrawAt(_G(guibg)[aa], 0, 0);
1924 _G(our_eip) = 373;
1925
1926 bool isAlpha = false;
1927 if (_GP(guis)[aa].HasAlphaChannel()) {
1928 isAlpha = true;
1929
1930 if ((_GP(game).options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (_GP(guis)[aa].BgImage > 0)) {
1931 // old-style (pre-3.0.2) GUI alpha rendering
1932 repair_alpha_channel(_G(guibg)[aa], _GP(spriteset)[_GP(guis)[aa].BgImage]);
1933 }
1934 }
1935
1936 if (_G(guibgbmp)[aa] != nullptr) {
1937 _G(gfxDriver)->UpdateDDBFromBitmap(_G(guibgbmp)[aa], _G(guibg)[aa], isAlpha);
1938 } else {
1939 _G(guibgbmp)[aa] = _G(gfxDriver)->CreateDDBFromBitmap(_G(guibg)[aa], isAlpha);
1940 }
1941 _G(our_eip) = 374;
1942 }
1943 }
1944 _G(our_eip) = 38;
1945 // Draw the GUIs
1946 for (gg = 0; gg < _GP(game).numgui; gg++) {
1947 aa = _GP(play).gui_draw_order[gg];
1948 if (!_GP(guis)[aa].IsDisplayed()) continue;
1949 if (_GP(guis)[aa].Transparency == 255) continue;
1950
1951 // Don't draw GUI if "GUIs Turn Off When Disabled"
1952 if ((_GP(game).options[OPT_DISABLEOFF] == 3) &&
1953 (_G(all_buttons_disabled) > 0) &&
1954 (_GP(guis)[aa].PopupStyle != kGUIPopupNoAutoRemove))
1955 continue;
1956
1957 add_thing_to_draw(_G(guibgbmp)[aa], _GP(guis)[aa].X, _GP(guis)[aa].Y, _GP(guis)[aa].Transparency, _GP(guis)[aa].HasAlphaChannel());
1958
1959 // only poll if the interface is enabled (mouseovers should not
1960 // work while in Wait state)
1961 if (IsInterfaceEnabled())
1962 _GP(guis)[aa].Poll();
1963 }
1964 }
1965
1966 // draw speech and portraits (so that they appear over GUIs)
1967 for (gg = 0; gg < _G(numscreenover); gg++) {
1968 if (_G(screenover)[gg].type == OVER_TEXTMSG || _G(screenover)[gg].type == OVER_PICTURE) {
1969 int tdxp, tdyp;
1970 get_overlay_position(_G(screenover)[gg], &tdxp, &tdyp);
1971 add_thing_to_draw(_G(screenover)[gg].bmp, tdxp, tdyp, 0, false);
1972 }
1973 }
1974
1975 _G(our_eip) = 1099;
1976 }
1977
1978 // Push the gathered list of sprites into the active graphic renderer
put_sprite_list_on_screen(bool in_room)1979 void put_sprite_list_on_screen(bool in_room) {
1980 // *** Draw the Things To Draw List ***
1981
1982 SpriteListEntry *thisThing;
1983
1984 for (size_t i = 0; i < _GP(thingsToDrawList).size(); ++i) {
1985 thisThing = &_GP(thingsToDrawList)[i];
1986
1987 if (thisThing->bmp != nullptr) {
1988 // mark the image's region as dirty
1989 invalidate_sprite(thisThing->x, thisThing->y, thisThing->bmp, in_room);
1990 } else if ((thisThing->transparent != TRANS_RUN_PLUGIN) &&
1991 (thisThing->bmp == nullptr)) {
1992 quit("Null pointer added to draw list");
1993 }
1994
1995 if (thisThing->bmp != nullptr) {
1996 if (thisThing->transparent <= 255) {
1997 thisThing->bmp->SetTransparency(thisThing->transparent);
1998 }
1999
2000 _G(gfxDriver)->DrawSprite(thisThing->x, thisThing->y, thisThing->bmp);
2001 } else if (thisThing->transparent == TRANS_RUN_PLUGIN) {
2002 // meta entry to run the plugin hook
2003 _G(gfxDriver)->DrawSprite(thisThing->x, thisThing->y, nullptr);
2004 } else
2005 quit("Unknown entry in draw list");
2006 }
2007
2008 _G(our_eip) = 1100;
2009 }
2010
GfxDriverNullSpriteCallback(int x,int y)2011 bool GfxDriverNullSpriteCallback(int x, int y) {
2012 if (_G(displayed_room) < 0) {
2013 // if no room loaded, various stuff won't be initialized yet
2014 return 1;
2015 }
2016 return (pl_run_plugin_hooks(x, y) != 0);
2017 }
2018
GfxDriverOnInitCallback(void * data)2019 void GfxDriverOnInitCallback(void *data) {
2020 pl_run_plugin_init_gfx_hooks(_G(gfxDriver)->GetDriverID(), data);
2021 }
2022
2023 // Schedule room rendering: background, objects, characters
construct_room_view()2024 static void construct_room_view() {
2025 draw_preroom_background();
2026 prepare_room_sprites();
2027 // reset the Baselines Changed flag now that we've drawn stuff
2028 _G(walk_behind_baselines_changed) = 0;
2029
2030 for (const auto &viewport : _GP(play).GetRoomViewportsZOrdered()) {
2031 if (!viewport->IsVisible())
2032 continue;
2033 auto camera = viewport->GetCamera();
2034 if (!camera)
2035 continue;
2036 const Rect &view_rc = _GP(play).GetRoomViewportAbs(viewport->GetID());
2037 const Rect &cam_rc = camera->GetRect();
2038 SpriteTransform room_trans(-cam_rc.Left, -cam_rc.Top,
2039 (float)view_rc.GetWidth() / (float)cam_rc.GetWidth(),
2040 (float)view_rc.GetHeight() / (float)cam_rc.GetHeight(),
2041 0.f);
2042 if (_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2043 // we draw everything as a sprite stack
2044 _G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans, Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2045 } else {
2046 if (_GP(CameraDrawData)[viewport->GetID()].Frame == nullptr && _GP(CameraDrawData)[viewport->GetID()].IsOverlap) {
2047 // room background is prepended to the sprite stack
2048 // TODO: here's why we have blit whole piece of background now:
2049 // if we draw directly to the virtual screen overlapping another
2050 // viewport, then we'd have to also mark and repaint every our
2051 // region located directly over their dirty regions. That would
2052 // require to update regions up the stack, converting their
2053 // coordinates (cam1 -> screen -> cam2).
2054 // It's not clear whether this is worth the effort, but if it is,
2055 // then we'd need to optimise view/cam data first.
2056 _G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans);
2057 _G(gfxDriver)->DrawSprite(0, 0, _G(roomBackgroundBmp));
2058 } else {
2059 // room background is drawn by dirty rects system
2060 PBitmap bg_surface = draw_room_background(viewport.get(), room_trans);
2061 _G(gfxDriver)->BeginSpriteBatch(view_rc, room_trans, Point(), kFlip_None, bg_surface);
2062 }
2063 }
2064 put_sprite_list_on_screen(true);
2065 }
2066
2067 clear_draw_list();
2068 }
2069
2070 // Schedule ui rendering
construct_ui_view()2071 static void construct_ui_view() {
2072 _G(gfxDriver)->BeginSpriteBatch(_GP(play).GetUIViewportAbs(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2073 draw_gui_and_overlays();
2074 put_sprite_list_on_screen(false);
2075 clear_draw_list();
2076 }
2077
construct_game_scene(bool full_redraw)2078 void construct_game_scene(bool full_redraw) {
2079 _G(gfxDriver)->ClearDrawLists();
2080
2081 if (_GP(play).fast_forward)
2082 return;
2083
2084 _G(our_eip) = 3;
2085
2086 // React to changes to viewports and cameras (possibly from script) just before the render
2087 _GP(play).UpdateViewports();
2088
2089 _G(gfxDriver)->UseSmoothScaling(IS_ANTIALIAS_SPRITES);
2090 _G(gfxDriver)->RenderSpritesAtScreenResolution(_GP(usetup).RenderAtScreenRes, _GP(usetup).Supersampling);
2091
2092 pl_run_plugin_hooks(AGSE_PRERENDER, 0);
2093
2094 // Possible reasons to invalidate whole screen for the software renderer
2095 if (full_redraw || _GP(play).screen_tint > 0 || _GP(play).shakesc_length > 0)
2096 invalidate_screen();
2097
2098 // TODO: move to game update! don't call update during rendering pass!
2099 // IMPORTANT: keep the order same because sometimes script may depend on it
2100 if (_G(displayed_room) >= 0)
2101 _GP(play).UpdateRoomCameras();
2102
2103 // Stage: room viewports
2104 if (_GP(play).screen_is_faded_out == 0 && _GP(play).complete_overlay_on == 0) {
2105 if (_G(displayed_room) >= 0) {
2106 construct_room_view();
2107 } else if (!_G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2108 // black it out so we don't get cursor trails
2109 // TODO: this is possible to do with dirty rects system now too (it can paint black rects outside of room viewport)
2110 _G(gfxDriver)->GetMemoryBackBuffer()->Fill(0);
2111 }
2112 }
2113
2114 _G(our_eip) = 4;
2115
2116 // Stage: UI overlay
2117 if (_GP(play).screen_is_faded_out == 0) {
2118 construct_ui_view();
2119 }
2120 }
2121
construct_game_screen_overlay(bool draw_mouse)2122 void construct_game_screen_overlay(bool draw_mouse) {
2123 _G(gfxDriver)->BeginSpriteBatch(_GP(play).GetMainViewport(), SpriteTransform(), Point(0, _GP(play).shake_screen_yoff), (GlobalFlipType)_GP(play).screen_flipped);
2124 if (pl_any_want_hook(AGSE_POSTSCREENDRAW))
2125 _G(gfxDriver)->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr);
2126
2127 // TODO: find out if it's okay to move cursor animation and state update
2128 // to the update loop instead of doing it in the drawing routine
2129 // update animating mouse cursor
2130 if (_GP(game).mcurs[_G(cur_cursor)].view >= 0) {
2131 ags_domouse(DOMOUSE_NOCURSOR);
2132 // only on mousemove, and it's not moving
2133 if (((_GP(game).mcurs[_G(cur_cursor)].flags & MCF_ANIMMOVE) != 0) &&
2134 (_G(mousex) == _G(lastmx)) && (_G(mousey) == _G(lastmy)));
2135 // only on hotspot, and it's not on one
2136 else if (((_GP(game).mcurs[_G(cur_cursor)].flags & MCF_HOTSPOT) != 0) &&
2137 (GetLocationType(game_to_data_coord(_G(mousex)), game_to_data_coord(_G(mousey))) == 0))
2138 set_new_cursor_graphic(_GP(game).mcurs[_G(cur_cursor)].pic);
2139 else if (_G(mouse_delay) > 0) _G(mouse_delay)--;
2140 else {
2141 int viewnum = _GP(game).mcurs[_G(cur_cursor)].view;
2142 int loopnum = 0;
2143 if (loopnum >= _G(views)[viewnum].numLoops)
2144 quitprintf("An animating mouse cursor is using view %d which has no loops", viewnum + 1);
2145 if (_G(views)[viewnum].loops[loopnum].numFrames < 1)
2146 quitprintf("An animating mouse cursor is using view %d which has no frames in loop %d", viewnum + 1, loopnum);
2147
2148 _G(mouse_frame)++;
2149 if (_G(mouse_frame) >= _G(views)[viewnum].loops[loopnum].numFrames)
2150 _G(mouse_frame) = 0;
2151 set_new_cursor_graphic(_G(views)[viewnum].loops[loopnum].frames[_G(mouse_frame)].pic);
2152 _G(mouse_delay) = _G(views)[viewnum].loops[loopnum].frames[_G(mouse_frame)].speed + 5;
2153 CheckViewFrame(viewnum, loopnum, _G(mouse_frame));
2154 }
2155 _G(lastmx) = _G(mousex);
2156 _G(lastmy) = _G(mousey);
2157 }
2158
2159 ags_domouse(DOMOUSE_NOCURSOR);
2160
2161 // Stage: mouse cursor
2162 if (draw_mouse && !_GP(play).mouse_cursor_hidden && _GP(play).screen_is_faded_out == 0) {
2163 _G(gfxDriver)->DrawSprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty),
2164 _G(mouseCursor));
2165 invalidate_sprite(_G(mousex) - _G(hotx), _G(mousey) - _G(hoty), _G(mouseCursor), false);
2166 }
2167
2168 if (_GP(play).screen_is_faded_out == 0) {
2169 // Stage: screen fx
2170 if (_GP(play).screen_tint >= 1)
2171 _G(gfxDriver)->SetScreenTint(_GP(play).screen_tint & 0xff, (_GP(play).screen_tint >> 8) & 0xff, (_GP(play).screen_tint >> 16) & 0xff);
2172 // Stage: legacy letterbox mode borders
2173 render_black_borders();
2174 }
2175
2176 if (_GP(play).screen_is_faded_out != 0 && _G(gfxDriver)->RequiresFullRedrawEachFrame()) {
2177 const Rect &main_viewport = _GP(play).GetMainViewport();
2178 _G(gfxDriver)->BeginSpriteBatch(main_viewport, SpriteTransform());
2179 _G(gfxDriver)->SetScreenFade(_GP(play).fade_to_red, _GP(play).fade_to_green, _GP(play).fade_to_blue);
2180 }
2181 }
2182
construct_engine_overlay()2183 void construct_engine_overlay() {
2184 const Rect &viewport = RectWH(_GP(game).GetGameRes());
2185 _G(gfxDriver)->BeginSpriteBatch(viewport, SpriteTransform());
2186
2187 // draw the debug console, if appropriate
2188 if ((_GP(play).debug_mode > 0) && (_G(display_console) != 0)) {
2189 const int font = FONT_NORMAL;
2190 int ypp = 1;
2191 int txtspacing = getfontspacing_outlined(font);
2192 int barheight = getheightoflines(font, DEBUG_CONSOLE_NUMLINES - 1) + 4;
2193
2194 if (_G(debugConsoleBuffer) == nullptr) {
2195 _G(debugConsoleBuffer) = BitmapHelper::CreateBitmap(viewport.GetWidth(), barheight, _GP(game).GetColorDepth());
2196 _G(debugConsoleBuffer) = ReplaceBitmapWithSupportedFormat(_G(debugConsoleBuffer));
2197 }
2198
2199 color_t draw_color = _G(debugConsoleBuffer)->GetCompatibleColor(15);
2200 _G(debugConsoleBuffer)->FillRect(Rect(0, 0, viewport.GetWidth() - 1, barheight), draw_color);
2201 color_t text_color = _G(debugConsoleBuffer)->GetCompatibleColor(16);
2202 for (int jj = _G(first_debug_line); jj != _G(last_debug_line); jj = (jj + 1) % DEBUG_CONSOLE_NUMLINES) {
2203 wouttextxy(_G(debugConsoleBuffer), 1, ypp, font, text_color, _G(debug_line)[jj].GetCStr());
2204 ypp += txtspacing;
2205 }
2206
2207 if (_G(debugConsole) == nullptr)
2208 _G(debugConsole) = _G(gfxDriver)->CreateDDBFromBitmap(_G(debugConsoleBuffer), false, true);
2209 else
2210 _G(gfxDriver)->UpdateDDBFromBitmap(_G(debugConsole), _G(debugConsoleBuffer), false);
2211
2212 _G(gfxDriver)->DrawSprite(0, 0, _G(debugConsole));
2213 invalidate_sprite_glob(0, 0, _G(debugConsole));
2214 }
2215
2216 if (_G(display_fps) != kFPS_Hide)
2217 draw_fps(viewport);
2218 }
2219
update_shakescreen()2220 static void update_shakescreen() {
2221 // TODO: unify blocking and non-blocking shake update
2222 _GP(play).shake_screen_yoff = 0;
2223 if (_GP(play).shakesc_length > 0) {
2224 if ((_G(loopcounter) % _GP(play).shakesc_delay) < (_GP(play).shakesc_delay / 2))
2225 _GP(play).shake_screen_yoff = _GP(play).shakesc_amount;
2226 }
2227 }
2228
2229 // Draw everything
render_graphics(IDriverDependantBitmap * extraBitmap,int extraX,int extraY)2230 void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY) {
2231 // Don't render if skipping cutscene
2232 if (_GP(play).fast_forward)
2233 return;
2234 // Don't render if we've just entered new room and are before fade-in
2235 // TODO: find out why this is not skipped for 8-bit games
2236 if ((_G(in_new_room) > 0) & (_GP(game).color_depth > 1))
2237 return;
2238
2239 // TODO: find out if it's okay to move shake to update function
2240 update_shakescreen();
2241
2242 construct_game_scene(false);
2243 _G(our_eip) = 5;
2244 // NOTE: extraBitmap will always be drawn with the UI render stage
2245 if (extraBitmap != nullptr) {
2246 invalidate_sprite(extraX, extraY, extraBitmap, false);
2247 _G(gfxDriver)->DrawSprite(extraX, extraY, extraBitmap);
2248 }
2249 construct_game_screen_overlay(true);
2250 render_to_screen();
2251
2252 if (!SHOULD_QUIT && !_GP(play).screen_is_faded_out) {
2253 // always update the palette, regardless of whether the plugin
2254 // vetos the screen update
2255 if (_G(bg_just_changed)) {
2256 setpal();
2257 _G(bg_just_changed) = 0;
2258 }
2259 }
2260
2261 _G(screen_is_dirty) = false;
2262 }
2263
2264 } // namespace AGS3
2265