1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14 
15 #include "ac/display.h"
16 #include "ac/common.h"
17 #include "font/agsfontrenderer.h"
18 #include "font/fonts.h"
19 #include "ac/character.h"
20 #include "ac/draw.h"
21 #include "ac/game.h"
22 #include "ac/gamesetupstruct.h"
23 #include "ac/gamestate.h"
24 #include "ac/global_audio.h"
25 #include "ac/global_game.h"
26 #include "ac/gui.h"
27 #include "ac/mouse.h"
28 #include "ac/overlay.h"
29 #include "ac/record.h"
30 #include "ac/screenoverlay.h"
31 #include "ac/speech.h"
32 #include "ac/string.h"
33 #include "ac/system.h"
34 #include "ac/topbarsettings.h"
35 #include "debug/debug_log.h"
36 #include "gui/guibutton.h"
37 #include "gui/guimain.h"
38 #include "main/game_run.h"
39 #include "media/audio/audio.h"
40 #include "platform/base/agsplatformdriver.h"
41 #include "ac/spritecache.h"
42 #include "gfx/gfx_util.h"
43 #include "util/string_utils.h"
44 
45 using AGS::Common::Bitmap;
46 namespace BitmapHelper = AGS::Common::BitmapHelper;
47 
48 extern GameState play;
49 extern GameSetupStruct game;
50 extern int longestline;
51 extern Bitmap *virtual_screen;
52 extern ScreenOverlay screenover[MAX_SCREEN_OVERLAYS];
53 extern volatile int timerloop;
54 extern AGSPlatformDriver *platform;
55 extern volatile unsigned long globalTimerCounter;
56 extern int time_between_timers;
57 extern int offsetx, offsety;
58 extern int frames_per_second;
59 extern int loops_per_character;
60 extern int spritewidth[MAX_SPRITES],spriteheight[MAX_SPRITES];
61 extern SpriteCache spriteset;
62 
63 int display_message_aschar=0;
64 
65 
66 TopBarSettings topBar;
67 struct DisplayVars
68 {
69     int lineheight;    // font's height of single line
70     int linespacing;   // font's line spacing
71     int fulltxtheight; // total height of all the text
72 } disp;
73 
74 // Pass yy = -1 to find Y co-ord automatically
75 // allowShrink = 0 for none, 1 for leftwards, 2 for rightwards
76 // pass blocking=2 to create permanent overlay
_display_main(int xx,int yy,int wii,const char * text,int blocking,int usingfont,int asspch,int isThought,int allowShrink,bool overlayPositionFixed)77 int _display_main(int xx,int yy,int wii,const char*text,int blocking,int usingfont,int asspch, int isThought, int allowShrink, bool overlayPositionFixed)
78 {
79     const bool use_speech_textwindow = (asspch < 0) && (game.options[OPT_SPEECHTYPE] >= 2);
80     const bool use_thought_gui = (isThought) && (game.options[OPT_THOUGHTGUI] > 0);
81 
82     bool alphaChannel = false;
83     char todis[STD_BUFFER_SIZE];
84     snprintf(todis, STD_BUFFER_SIZE - 1, "%s", text);
85     int usingGui = -1;
86     if (use_speech_textwindow)
87 	    usingGui = play.speech_textwindow_gui;
88     else if (use_thought_gui)
89 	    usingGui = game.options[OPT_THOUGHTGUI];
90 
91     int padding = get_textwindow_padding(usingGui);
92     int paddingScaled = get_fixed_pixel_size(padding);
93     int paddingDoubledScaled = get_fixed_pixel_size(padding * 2); // Just in case screen size does is not neatly divisible by 320x200
94 
95     ensure_text_valid_for_font(todis, usingfont);
96     break_up_text_into_lines(wii-2*padding,usingfont,todis);
97     disp.lineheight = getfontheight_outlined(usingfont);
98     disp.linespacing= getfontspacing_outlined(usingfont);
99     disp.fulltxtheight = getheightoflines(usingfont, numlines);
100 
101     // AGS 2.x: If the screen is faded out, fade in again when displaying a message box.
102     if (!asspch && (loaded_game_file_version <= kGameVersion_272))
103         play.screen_is_faded_out = 0;
104 
105     // if it's a normal message box and the game was being skipped,
106     // ensure that the screen is up to date before the message box
107     // is drawn on top of it
108     if ((play.skip_until_char_stops >= 0) && (blocking == 1))
109         render_graphics();
110 
111     EndSkippingUntilCharStops();
112 
113     if (topBar.wantIt) {
114         // ensure that the window is wide enough to display
115         // any top bar text
116         int topBarWid = wgettextwidth_compensate(topBar.text, topBar.font);
117         topBarWid += multiply_up_coordinate(play.top_bar_borderwidth + 2) * 2;
118         if (longestline < topBarWid)
119             longestline = topBarWid;
120         // the top bar should behave like DisplaySpeech wrt blocking
121         blocking = 0;
122     }
123 
124     if (asspch > 0) {
125         // update the all_buttons_disabled variable in advance
126         // of the adjust_x/y_for_guis calls
127         play.disabled_user_interface++;
128         update_gui_disabled_status();
129         play.disabled_user_interface--;
130     }
131 
132     if (xx == OVR_AUTOPLACE) ;
133     // centre text in middle of screen
134     else if (yy<0) yy=play.viewport.GetHeight()/2-disp.fulltxtheight/2-padding;
135     // speech, so it wants to be above the character's head
136     else if (asspch > 0) {
137         yy-=disp.fulltxtheight;
138         if (yy < 5) yy=5;
139         yy = adjust_y_for_guis (yy);
140     }
141 
142     if (longestline < wii - paddingDoubledScaled) {
143         // shrink the width of the dialog box to fit the text
144         int oldWid = wii;
145         //if ((asspch >= 0) || (allowShrink > 0))
146         // If it's not speech, or a shrink is allowed, then shrink it
147         if ((asspch == 0) || (allowShrink > 0))
148             wii = longestline + paddingDoubledScaled;
149 
150         // shift the dialog box right to align it, if necessary
151         if ((allowShrink == 2) && (xx >= 0))
152             xx += (oldWid - wii);
153     }
154 
155     if (xx<-1) {
156         xx=(-xx)-wii/2;
157         if (xx < 0)
158             xx = 0;
159 
160         xx = adjust_x_for_guis (xx, yy);
161 
162         if (xx + wii >= play.viewport.GetWidth())
163             xx = (play.viewport.GetWidth() - wii) - 5;
164     }
165     else if (xx<0) xx=play.viewport.GetWidth()/2-wii/2;
166 
167     int ee, extraHeight = paddingDoubledScaled;
168     Bitmap *ds = GetVirtualScreen();
169     color_t text_color = ds->GetCompatibleColor(15);
170     if (blocking < 2)
171         remove_screen_overlay(OVER_TEXTMSG);
172 
173     Bitmap *text_window_ds = BitmapHelper::CreateTransparentBitmap((wii > 0) ? wii : 2, disp.fulltxtheight + extraHeight, game.GetColorDepth());
174     SetVirtualScreen(text_window_ds);
175 
176     // inform draw_text_window to free the old bitmap
177     const bool wantFreeScreenop = true;
178 
179     if ((strlen (todis) < 1) || (strcmp (todis, "  ") == 0) || (wii == 0)) ;
180     // if it's an empty speech line, don't draw anything
181     else if (asspch) { //text_color = ds->GetCompatibleColor(12);
182         int ttxleft = 0, ttxtop = paddingScaled, oriwid = wii - padding * 2;
183         int drawBackground = 0;
184 
185         if (use_speech_textwindow) {
186             drawBackground = 1;
187         }
188         else if (use_thought_gui) {
189             // make it treat it as drawing inside a window now
190             if (asspch > 0)
191                 asspch = -asspch;
192             drawBackground = 1;
193         }
194 
195         if (drawBackground)
196         {
197             draw_text_window_and_bar(&text_window_ds, wantFreeScreenop, &ttxleft, &ttxtop, &xx, &yy, &wii, &text_color, 0, usingGui);
198             if (usingGui > 0)
199             {
200                 alphaChannel = guis[usingGui].HasAlphaChannel();
201             }
202         }
203         else if ((ShouldAntiAliasText()) && (game.GetColorDepth() >= 24))
204             alphaChannel = true;
205 
206         for (ee=0;ee<numlines;ee++) {
207             //int ttxp=wii/2 - wgettextwidth_compensate(lines[ee], usingfont)/2;
208             int ttyp=ttxtop+ee*disp.linespacing;
209             // asspch < 0 means that it's inside a text box so don't
210             // centre the text
211             if (asspch < 0) {
212                 if ((usingGui >= 0) &&
213                     ((game.options[OPT_SPEECHTYPE] >= 2) || (isThought)))
214                     text_color = text_window_ds->GetCompatibleColor(guis[usingGui].FgColor);
215                 else
216                     text_color = text_window_ds->GetCompatibleColor(-asspch);
217 
218                 wouttext_aligned(text_window_ds, ttxleft, ttyp, oriwid, usingfont, text_color, lines[ee], play.text_align);
219             }
220             else {
221                 text_color = text_window_ds->GetCompatibleColor(asspch);
222                 //wouttext_outline(ttxp,ttyp,usingfont,lines[ee]);
223                 wouttext_aligned(text_window_ds, ttxleft, ttyp, wii, usingfont, text_color, lines[ee], play.speech_text_align);
224             }
225         }
226     }
227     else {
228 
229         int xoffs,yoffs, oriwid = wii - padding * 2;
230         draw_text_window_and_bar(&text_window_ds, wantFreeScreenop, &xoffs,&yoffs,&xx,&yy,&wii,&text_color);
231 
232         if (game.options[OPT_TWCUSTOM] > 0)
233         {
234             alphaChannel = guis[game.options[OPT_TWCUSTOM]].HasAlphaChannel();
235         }
236 
237         adjust_y_coordinate_for_text(&yoffs, usingfont);
238 
239         for (ee=0;ee<numlines;ee++)
240             wouttext_aligned (text_window_ds, xoffs, yoffs + ee * disp.linespacing, oriwid, usingfont, text_color, lines[ee], play.text_align);
241     }
242 
243     int ovrtype = OVER_TEXTMSG;
244     if (blocking == 2) ovrtype=OVER_CUSTOM;
245     else if (blocking >= OVER_CUSTOM) ovrtype=blocking;
246 
247     int nse = add_screen_overlay(xx, yy, ovrtype, text_window_ds, alphaChannel);
248     // we should not delete text_window_ds here, because it is now owned by Overlay
249 
250     ds = SetVirtualScreen(virtual_screen);
251     if (blocking>=2) {
252         return screenover[nse].type;
253     }
254 
255     if (blocking) {
256         if (play.fast_forward) {
257             remove_screen_overlay(OVER_TEXTMSG);
258             play.messagetime=-1;
259             return 0;
260         }
261 
262         /*    wputblock(xx,yy,screenop,1);
263         remove_screen_overlay(OVER_TEXTMSG);*/
264 
265         if (!play.mouse_cursor_hidden)
266             domouse(1);
267         // play.skip_display has same values as SetSkipSpeech:
268         // 0 = click mouse or key to skip
269         // 1 = key only
270         // 2 = can't skip at all
271         // 3 = only on keypress, no auto timer
272         // 4 = mouse only
273         int countdown = GetTextDisplayTime (todis);
274         int skip_setting = user_to_internal_skip_speech((SkipSpeechStyle)play.skip_display);
275         while (1) {
276             timerloop = 0;
277             NEXT_ITERATION();
278             /*      if (!play.mouse_cursor_hidden)
279             domouse(0);
280             write_screen();*/
281 
282             render_graphics();
283 
284             update_polled_audio_and_crossfade();
285             if (mgetbutton()>NONE) {
286                 // If we're allowed, skip with mouse
287                 if (skip_setting & SKIP_MOUSECLICK)
288                     break;
289             }
290             int kp;
291             if (run_service_key_controls(kp)) {
292                 // let them press ESC to skip the cutscene
293                 check_skip_cutscene_keypress (kp);
294                 if (play.fast_forward)
295                     break;
296 
297                 if (skip_setting & SKIP_KEYPRESS)
298                     break;
299             }
300             update_polled_stuff_if_runtime();
301             if (play.fast_forward == 0) WaitForNextFrame();
302             countdown--;
303 
304             if (channels[SCHAN_SPEECH] != NULL) {
305                 // extend life of text if the voice hasn't finished yet
306                 if ((!rec_isSpeechFinished()) && (play.fast_forward == 0)) {
307                     if (countdown <= 1)
308                         countdown = 1;
309                 }
310                 else  // if the voice has finished, remove the speech
311                     countdown = 0;
312             }
313 
314             if ((countdown < 1) && (skip_setting & SKIP_AUTOTIMER))
315             {
316                 play.ignore_user_input_until_time = globalTimerCounter + (play.ignore_user_input_after_text_timeout_ms / time_between_timers);
317                 break;
318             }
319             // if skipping cutscene, don't get stuck on No Auto Remove
320             // text boxes
321             if ((countdown < 1) && (play.fast_forward))
322                 break;
323         }
324         if (!play.mouse_cursor_hidden)
325             domouse(2);
326         remove_screen_overlay(OVER_TEXTMSG);
327 
328         construct_virtual_screen(true);
329     }
330     else {
331         // if the speech does not time out, but we are skipping a cutscene,
332         // allow it to time out
333         if ((play.messagetime < 0) && (play.fast_forward))
334             play.messagetime = 2;
335 
336         if (!overlayPositionFixed)
337         {
338             screenover[nse].positionRelativeToScreen = false;
339             screenover[nse].x += offsetx;
340             screenover[nse].y += offsety;
341         }
342 
343         GameLoopUntilEvent(UNTIL_NOOVERLAY,0);
344     }
345 
346     play.messagetime=-1;
347     return 0;
348 }
349 
_display_at(int xx,int yy,int wii,const char * todis,int blocking,int asspch,int isThought,int allowShrink,bool overlayPositionFixed)350 void _display_at(int xx,int yy,int wii,const char*todis,int blocking,int asspch, int isThought, int allowShrink, bool overlayPositionFixed) {
351     int usingfont=FONT_NORMAL;
352     if (asspch) usingfont=FONT_SPEECH;
353     int needStopSpeech = 0;
354 
355     EndSkippingUntilCharStops();
356 
357     if (todis[0]=='&') {
358         // auto-speech
359         int igr=atoi(&todis[1]);
360         while ((todis[0]!=' ') & (todis[0]!=0)) todis++;
361         if (todis[0]==' ') todis++;
362         if (igr <= 0)
363             quit("Display: auto-voice symbol '&' not followed by valid integer");
364         if (play_speech(play.narrator_speech,igr)) {
365             // if Voice Only, then blank out the text
366             if (play.want_speech == 2)
367                 todis = "  ";
368         }
369         needStopSpeech = 1;
370     }
371     _display_main(xx,yy,wii,todis,blocking,usingfont,asspch, isThought, allowShrink, overlayPositionFixed);
372 
373     if (needStopSpeech)
374         stop_speech();
375 }
376 
377 // TODO: refactor this global variable out; currently it is set at the every get_translation call.
378 // Be careful: a number of Say/Display functions expect it to be set beforehand.
379 int source_text_length = -1;
380 
GetTextDisplayLength(const char * text)381 int GetTextDisplayLength(const char *text)
382 {
383     int len = (int)strlen(text);
384     if ((text[0] == '&') && (play.unfactor_speech_from_textlength != 0))
385     {
386         // if there's an "&12 text" type line, remove "&12 " from the source length
387         size_t j = 0;
388         while ((text[j] != ' ') && (text[j] != 0))
389             j++;
390         j++;
391         len -= j;
392     }
393     return len;
394 }
395 
GetTextDisplayTime(const char * text,int canberel)396 int GetTextDisplayTime(const char *text, int canberel) {
397     int uselen = 0;
398     int fpstimer = frames_per_second;
399 
400     // if it's background speech, make it stay relative to game speed
401     if ((canberel == 1) && (play.bgspeech_game_speed == 1))
402         fpstimer = 40;
403 
404     if (source_text_length >= 0) {
405         // sync to length of original text, to make sure any animations
406         // and music sync up correctly
407         uselen = source_text_length;
408         source_text_length = -1;
409     }
410     else {
411         uselen = GetTextDisplayLength(text);
412     }
413 
414     if (uselen <= 0)
415         return 0;
416 
417     if (play.text_speed + play.text_speed_modifier <= 0)
418         quit("!Text speed is zero; unable to display text. Check your game.text_speed settings.");
419 
420     // Store how many game loops per character of text
421     // This is calculated using a hard-coded 15 for the text speed,
422     // so that it's always the same no matter how fast the user
423     // can read.
424     loops_per_character = (((uselen/play.lipsync_speed)+1) * fpstimer) / uselen;
425 
426     int textDisplayTimeInMS = ((uselen / (play.text_speed + play.text_speed_modifier)) + 1) * 1000;
427     if (textDisplayTimeInMS < play.text_min_display_time_ms)
428         textDisplayTimeInMS = play.text_min_display_time_ms;
429 
430     return (textDisplayTimeInMS * fpstimer) / 1000;
431 }
432 
ShouldAntiAliasText()433 bool ShouldAntiAliasText() {
434     return (game.options[OPT_ANTIALIASFONTS] != 0);
435 }
436 
wouttext_outline(Common::Bitmap * ds,int xxp,int yyp,int usingfont,color_t text_color,const char * texx)437 void wouttext_outline(Common::Bitmap *ds, int xxp, int yyp, int usingfont, color_t text_color, const char*texx) {
438 
439     color_t outline_color = ds->GetCompatibleColor(play.speech_text_shadow);
440     if (get_font_outline(usingfont) >= 0) {
441         // MACPORT FIX 9/6/5: cast
442         wouttextxy(ds, xxp, yyp, (int)get_font_outline(usingfont), outline_color, texx);
443     }
444     else if (get_font_outline(usingfont) == FONT_OUTLINE_AUTO) {
445         int outlineDist = 1;
446 
447         if ((game.options[OPT_NOSCALEFNT] == 0) && (!font_supports_extended_characters(usingfont))) {
448             // if it's a scaled up SCI font, move the outline out more
449             outlineDist = get_fixed_pixel_size(1);
450         }
451 
452         // move the text over so that it's still within the bounding rect
453         xxp += outlineDist;
454         yyp += outlineDist;
455 
456         wouttextxy(ds, xxp - outlineDist, yyp, usingfont, outline_color, texx);
457         wouttextxy(ds, xxp + outlineDist, yyp, usingfont, outline_color, texx);
458         wouttextxy(ds, xxp, yyp + outlineDist, usingfont, outline_color, texx);
459         wouttextxy(ds, xxp, yyp - outlineDist, usingfont, outline_color, texx);
460         wouttextxy(ds, xxp - outlineDist, yyp - outlineDist, usingfont, outline_color, texx);
461         wouttextxy(ds, xxp - outlineDist, yyp + outlineDist, usingfont, outline_color, texx);
462         wouttextxy(ds, xxp + outlineDist, yyp + outlineDist, usingfont, outline_color, texx);
463         wouttextxy(ds, xxp + outlineDist, yyp - outlineDist, usingfont, outline_color, texx);
464     }
465 
466     wouttextxy(ds, xxp, yyp, usingfont, text_color, texx);
467 }
468 
wouttext_aligned(Bitmap * ds,int usexp,int yy,int oriwid,int usingfont,color_t text_color,const char * text,int align)469 void wouttext_aligned (Bitmap *ds, int usexp, int yy, int oriwid, int usingfont, color_t text_color, const char *text, int align) {
470 
471     if (align == SCALIGN_CENTRE)
472         usexp = usexp + (oriwid / 2) - (wgettextwidth_compensate(text, usingfont) / 2);
473     else if (align == SCALIGN_RIGHT)
474         usexp = usexp + (oriwid - wgettextwidth_compensate(text, usingfont));
475 
476     wouttext_outline(ds, usexp, yy, usingfont, text_color, (char *)text);
477 }
478 
get_outline_adjustment(int font)479 int get_outline_adjustment(int font)
480 {
481     // automatic outline fonts are 2 pixels taller
482     if (get_font_outline(font) == FONT_OUTLINE_AUTO) {
483         // scaled up SCI font, push outline further out
484         if ((game.options[OPT_NOSCALEFNT] == 0) && (!font_supports_extended_characters(font)))
485             return get_fixed_pixel_size(2);
486         // otherwise, just push outline by 1 pixel
487         else
488             return 2;
489     }
490     return 0;
491 }
492 
getfontheight_outlined(int font)493 int getfontheight_outlined(int font)
494 {
495     return getfontheight(font) + get_outline_adjustment(font);
496 }
497 
getfontspacing_outlined(int font)498 int getfontspacing_outlined(int font)
499 {
500     return use_default_linespacing(font) ?
501         getfontheight_outlined(font) :
502         getfontlinespacing(font);
503 }
504 
getfontlinegap(int font)505 int getfontlinegap(int font)
506 {
507     return getfontspacing_outlined(font) - getfontheight_outlined(font);
508 }
509 
getheightoflines(int font,int numlines)510 int getheightoflines(int font, int numlines)
511 {
512     return getfontspacing_outlined(font) * (numlines - 1) + getfontheight_outlined(font);
513 }
514 
wgettextwidth_compensate(const char * tex,int font)515 int wgettextwidth_compensate(const char *tex, int font) {
516     int wdof = wgettextwidth(tex, font);
517 
518     if (get_font_outline(font) == FONT_OUTLINE_AUTO) {
519         // scaled up SCI font, push outline further out
520         if ((game.options[OPT_NOSCALEFNT] == 0) && (!font_supports_extended_characters(font)))
521             wdof += get_fixed_pixel_size(2);
522         // otherwise, just push outline by 1 pixel
523         else
524             wdof += get_fixed_pixel_size(1);
525     }
526 
527     return wdof;
528 }
529 
do_corner(Bitmap * ds,int sprn,int x,int y,int offx,int offy)530 void do_corner(Bitmap *ds, int sprn, int x, int y, int offx, int offy) {
531     if (sprn<0) return;
532     if (spriteset[sprn] == NULL)
533     {
534         sprn = 0;
535     }
536 
537     x = x + offx * spritewidth[sprn];
538     y = y + offy * spriteheight[sprn];
539     draw_gui_sprite_v330(ds, sprn, x, y);
540 }
541 
get_but_pic(GUIMain * guo,int indx)542 int get_but_pic(GUIMain*guo,int indx) {
543     return guibuts[guo->CtrlRefs[indx] & 0x000ffff].pic;
544 }
545 
draw_button_background(Bitmap * ds,int xx1,int yy1,int xx2,int yy2,GUIMain * iep)546 void draw_button_background(Bitmap *ds, int xx1,int yy1,int xx2,int yy2,GUIMain*iep) {
547     color_t draw_color;
548     if (iep==NULL) {  // standard window
549         draw_color = ds->GetCompatibleColor(15);
550         ds->FillRect(Rect(xx1,yy1,xx2,yy2), draw_color);
551         draw_color = ds->GetCompatibleColor(16);
552         ds->DrawRect(Rect(xx1,yy1,xx2,yy2), draw_color);
553         /*    draw_color = ds->GetCompatibleColor(opts.tws.backcol); ds->FillRect(Rect(xx1,yy1,xx2,yy2);
554         draw_color = ds->GetCompatibleColor(opts.tws.ds->GetTextColor()); ds->DrawRect(Rect(xx1+1,yy1+1,xx2-1,yy2-1);*/
555     }
556     else {
557         if (loaded_game_file_version < kGameVersion_262) // < 2.62
558         {
559             // Color 0 wrongly shows as transparent instead of black
560             // From the changelog of 2.62:
561             //  - Fixed text windows getting a black background if colour 0 was
562             //    specified, rather than being transparent.
563             if (iep->BgColor == 0)
564                 iep->BgColor = 16;
565         }
566 
567         if (iep->BgColor >= 0) draw_color = ds->GetCompatibleColor(iep->BgColor);
568         else draw_color = ds->GetCompatibleColor(0); // black backrgnd behind picture
569 
570         if (iep->BgColor > 0)
571             ds->FillRect(Rect(xx1,yy1,xx2,yy2), draw_color);
572 
573         int leftRightWidth = spritewidth[get_but_pic(iep,4)];
574         int topBottomHeight = spriteheight[get_but_pic(iep,6)];
575         if (iep->BgImage>0) {
576             if ((loaded_game_file_version <= kGameVersion_272) // 2.xx
577                 && (spriteset[iep->BgImage]->GetWidth() == 1)
578                 && (spriteset[iep->BgImage]->GetHeight() == 1)
579                 && (*((unsigned int*)spriteset[iep->BgImage]->GetData()) == 0x00FF00FF))
580             {
581                 // Don't draw fully transparent dummy GUI backgrounds
582             }
583             else
584             {
585                 // offset the background image and clip it so that it is drawn
586                 // such that the border graphics can have a transparent outside
587                 // edge
588                 int bgoffsx = xx1 - leftRightWidth / 2;
589                 int bgoffsy = yy1 - topBottomHeight / 2;
590                 ds->SetClip(Rect(bgoffsx, bgoffsy, xx2 + leftRightWidth / 2, yy2 + topBottomHeight / 2));
591                 int bgfinishx = xx2;
592                 int bgfinishy = yy2;
593                 int bgoffsyStart = bgoffsy;
594                 while (bgoffsx <= bgfinishx)
595                 {
596                     bgoffsy = bgoffsyStart;
597                     while (bgoffsy <= bgfinishy)
598                     {
599                         draw_gui_sprite_v330(ds, iep->BgImage, bgoffsx, bgoffsy);
600                         bgoffsy += spriteheight[iep->BgImage];
601                     }
602                     bgoffsx += spritewidth[iep->BgImage];
603                 }
604                 // return to normal clipping rectangle
605                 ds->SetClip(Rect(0, 0, ds->GetWidth() - 1, ds->GetHeight() - 1));
606             }
607         }
608         int uu;
609         for (uu=yy1;uu <= yy2;uu+=spriteheight[get_but_pic(iep,4)]) {
610             do_corner(ds, get_but_pic(iep,4),xx1,uu,-1,0);   // left side
611             do_corner(ds, get_but_pic(iep,5),xx2+1,uu,0,0);  // right side
612         }
613         for (uu=xx1;uu <= xx2;uu+=spritewidth[get_but_pic(iep,6)]) {
614             do_corner(ds, get_but_pic(iep,6),uu,yy1,0,-1);  // top side
615             do_corner(ds, get_but_pic(iep,7),uu,yy2+1,0,0); // bottom side
616         }
617         do_corner(ds, get_but_pic(iep,0),xx1,yy1,-1,-1);  // top left
618         do_corner(ds, get_but_pic(iep,1),xx1,yy2+1,-1,0);  // bottom left
619         do_corner(ds, get_but_pic(iep,2),xx2+1,yy1,0,-1);  //  top right
620         do_corner(ds, get_but_pic(iep,3),xx2+1,yy2+1,0,0);  // bottom right
621     }
622 }
623 
624 // Calculate the width that the left and right border of the textwindow
625 // GUI take up
get_textwindow_border_width(int twgui)626 int get_textwindow_border_width (int twgui) {
627     if (twgui < 0)
628         return 0;
629 
630     if (!guis[twgui].IsTextWindow())
631         quit("!GUI set as text window but is not actually a text window GUI");
632 
633     int borwid = spritewidth[get_but_pic(&guis[twgui], 4)] +
634         spritewidth[get_but_pic(&guis[twgui], 5)];
635 
636     return borwid;
637 }
638 
639 // get the hegiht of the text window's top border
get_textwindow_top_border_height(int twgui)640 int get_textwindow_top_border_height (int twgui) {
641     if (twgui < 0)
642         return 0;
643 
644     if (!guis[twgui].IsTextWindow())
645         quit("!GUI set as text window but is not actually a text window GUI");
646 
647     return spriteheight[get_but_pic(&guis[twgui], 6)];
648 }
649 
650 // Get the padding for a text window
651 // -1 for the game's custom text window
get_textwindow_padding(int ifnum)652 int get_textwindow_padding(int ifnum) {
653     int result;
654 
655     if (ifnum < 0)
656         ifnum = game.options[OPT_TWCUSTOM];
657     if (ifnum > 0 && ifnum < game.numgui)
658         result = guis[ifnum].Padding;
659     else
660         result = TEXTWINDOW_PADDING_DEFAULT;
661 
662     return result;
663 }
664 
draw_text_window(Bitmap ** text_window_ds,bool should_free_ds,int * xins,int * yins,int * xx,int * yy,int * wii,color_t * set_text_color,int ovrheight,int ifnum)665 void draw_text_window(Bitmap **text_window_ds, bool should_free_ds,
666                       int*xins,int*yins,int*xx,int*yy,int*wii, color_t *set_text_color, int ovrheight, int ifnum) {
667 
668     Bitmap *ds = *text_window_ds;
669     if (ifnum < 0)
670         ifnum = game.options[OPT_TWCUSTOM];
671 
672     if (ifnum <= 0) {
673         if (ovrheight)
674             quit("!Cannot use QFG4 style options without custom text window");
675         draw_button_background(ds, 0,0,ds->GetWidth() - 1,ds->GetHeight() - 1,NULL);
676         if (set_text_color)
677             *set_text_color = ds->GetCompatibleColor(16);
678         xins[0]=3;
679         yins[0]=3;
680     }
681     else {
682         if (ifnum >= game.numgui)
683             quitprintf("!Invalid GUI %d specified as text window (total GUIs: %d)", ifnum, game.numgui);
684         if (!guis[ifnum].IsTextWindow())
685             quit("!GUI set as text window but is not actually a text window GUI");
686 
687         int tbnum = get_but_pic(&guis[ifnum], 0);
688 
689         wii[0] += get_textwindow_border_width (ifnum);
690         xx[0]-=spritewidth[tbnum];
691         yy[0]-=spriteheight[tbnum];
692         if (ovrheight == 0)
693             ovrheight = disp.fulltxtheight;
694 
695         if (should_free_ds)
696             delete *text_window_ds;
697         int padding = get_textwindow_padding(ifnum);
698         *text_window_ds = BitmapHelper::CreateTransparentBitmap(wii[0],ovrheight+(padding*2)+spriteheight[tbnum]*2,game.GetColorDepth());
699         ds = SetVirtualScreen(*text_window_ds);
700         int xoffs=spritewidth[tbnum],yoffs=spriteheight[tbnum];
701         draw_button_background(ds, xoffs,yoffs,(ds->GetWidth() - xoffs) - 1,(ds->GetHeight() - yoffs) - 1,&guis[ifnum]);
702         if (set_text_color)
703             *set_text_color = ds->GetCompatibleColor(guis[ifnum].FgColor);
704         xins[0]=xoffs+padding;
705         yins[0]=yoffs+padding;
706     }
707 
708 }
709 
draw_text_window_and_bar(Bitmap ** text_window_ds,bool should_free_ds,int * xins,int * yins,int * xx,int * yy,int * wii,color_t * set_text_color,int ovrheight,int ifnum)710 void draw_text_window_and_bar(Bitmap **text_window_ds, bool should_free_ds,
711                               int*xins,int*yins,int*xx,int*yy,int*wii,color_t *set_text_color,int ovrheight, int ifnum) {
712 
713     draw_text_window(text_window_ds, should_free_ds, xins, yins, xx, yy, wii, set_text_color, ovrheight, ifnum);
714 
715     if ((topBar.wantIt) && (text_window_ds && *text_window_ds)) {
716         // top bar on the dialog window with character's name
717         // create an enlarged window, then free the old one
718         Bitmap *ds = *text_window_ds;
719         Bitmap *newScreenop = BitmapHelper::CreateBitmap(ds->GetWidth(), ds->GetHeight() + topBar.height, game.GetColorDepth());
720         newScreenop->Blit(ds, 0, 0, 0, topBar.height, ds->GetWidth(), ds->GetHeight());
721         delete *text_window_ds;
722         *text_window_ds = newScreenop;
723         ds = SetVirtualScreen(*text_window_ds);
724 
725         // draw the top bar
726         color_t draw_color = ds->GetCompatibleColor(play.top_bar_backcolor);
727         ds->FillRect(Rect(0, 0, ds->GetWidth() - 1, topBar.height - 1), draw_color);
728         if (play.top_bar_backcolor != play.top_bar_bordercolor) {
729             // draw the border
730             draw_color = ds->GetCompatibleColor(play.top_bar_bordercolor);
731             for (int j = 0; j < multiply_up_coordinate(play.top_bar_borderwidth); j++)
732                 ds->DrawRect(Rect(j, j, ds->GetWidth() - (j + 1), topBar.height - (j + 1)), draw_color);
733         }
734 
735         // draw the text
736         int textx = (ds->GetWidth() / 2) - wgettextwidth_compensate(topBar.text, topBar.font) / 2;
737         color_t text_color = ds->GetCompatibleColor(play.top_bar_textcolor);
738         wouttext_outline(ds, textx, play.top_bar_borderwidth + get_fixed_pixel_size(1), topBar.font, text_color, topBar.text);
739 
740         // don't draw it next time
741         topBar.wantIt = 0;
742         // adjust the text Y position
743         yins[0] += topBar.height;
744     }
745     else if (topBar.wantIt)
746         topBar.wantIt = 0;
747 }
748