Skip to content

Commit

Permalink
Engine: added DisplayTextPosition to DisplayTextLooks
Browse files Browse the repository at this point in the history
This amends kDisplayTextStyle_Overchar with a more elaborate combination of settings.
Stop using negative x and y in display_main as a telling that the text should be centered.
(They are still used in display_speech though, since the script api supports this behavior, whether intentionally or by accident.)

Also fixes SayAt being clamped to the screen as if it's regular Say.
  • Loading branch information
ivan-mogilko committed Feb 26, 2025
1 parent a010ef7 commit 53da85f
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 90 deletions.
78 changes: 38 additions & 40 deletions Engine/ac/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ int Character_GetHasExplicitTint(CharacterInfo *ch)
}

void Character_Say(CharacterInfo *chaa, const char *text) {
_DisplaySpeechCore(chaa->index_id, text);
DisplaySpeechCore(chaa->index_id, text);
}

void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx) {
Expand Down Expand Up @@ -1035,7 +1035,7 @@ void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opaci
}

void Character_Think(CharacterInfo *chaa, const char *text) {
_DisplayThoughtCore(chaa->index_id, text);
DisplayThoughtCore(chaa->index_id, text);
}

void Character_UnlockView(CharacterInfo *chaa) {
Expand Down Expand Up @@ -2493,7 +2493,7 @@ int check_click_on_character(int xx,int yy,int mood) {
return 0;
}

void _DisplaySpeechCore(int chid, const char *displbuf) {
void DisplaySpeechCore(int chid, const char *displbuf) {
if (displbuf[0] == 0) {
// no text, just update the current character who's speaking
// this allows the portrait side to be switched with an empty
Expand All @@ -2511,30 +2511,25 @@ void _DisplaySpeechCore(int chid, const char *displbuf) {
DisplaySpeech(displbuf, chid);
}

void _DisplayThoughtCore(int chid, const char *displbuf) {
void DisplayThoughtCore(int chid, const char *displbuf) {
// adjust timing of text (so that DisplayThought("%s", str) pauses
// for the length of the string not 2 frames)
int len = (int)strlen(displbuf);
if (len > source_text_length + 3)
source_text_length = len;

int xpp = -1, ypp = -1, width = -1;

int width = -1;
if ((game.options[OPT_SPEECHTYPE] == 0) || (game.chars[chid].thinkview <= 0)) {
// lucasarts-style, so we want a speech bubble actually above
// their head (or if they have no think anim in Sierra-style)
width = data_to_game_coord(play.speech_bubble_width);
xpp = play.RoomToScreenX(data_to_game_coord(game.chars[chid].x)) - width / 2;
if (xpp < 0)
xpp = 0;
// -1 will automatically put it above the char's head
ypp = -1;
}

_displayspeech(displbuf, chid, xpp, ypp, width, 1);
display_speech(displbuf, chid, -1, -1, width, true /*auto-pos*/, true /* is thought */);
}

void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int isThought) {
void display_speech(const char *texx, int aschar, int xx, int yy, int widd, bool auto_position, bool is_thought)
{
if (!is_valid_character(aschar))
quit("!DisplaySpeech: invalid character");

Expand Down Expand Up @@ -2592,29 +2587,32 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
}

DisplayTextStyle disp_style = kDisplayTextStyle_Overchar;
// If the character is in this room, then default to aligning the speech
// to the character position; but if it's not then center the speech on screen
DisplayTextPosition disp_pos = auto_position ?
get_textpos_from_scriptcoords(xx, yy, (speakingChar->room == displayed_room)) :
kDisplayTextPos_Normal;
const color_t text_color = speakingChar->talkcolor;

Rect ui_view = play.GetUIViewport();
DisplayTextShrink allow_shrink = kDisplayTextShrink_None;
bool align_hcenter = false; // whether to align text by centering over position

const Rect ui_view = play.GetUIViewport();
int bwidth = widd;
if (bwidth < 0)
bwidth = ui_view.GetWidth()/2 + ui_view.GetWidth()/4;

set_our_eip(151);

int useview = speakingChar->talkview;
if (isThought) {
if (is_thought)
{
useview = speakingChar->thinkview;
// view 0 is not valid for think views
if (useview == 0)
useview = -1;
// speech bubble can shrink to fit
allow_shrink = kDisplayTextShrink_Left;
if (speakingChar->room != displayed_room) {
// not in room, centre it
xx = -1;
yy = -1;
}
}

if (useview >= game.numviews)
Expand All @@ -2633,7 +2631,7 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
stop_character_idling(speakingChar);
}

int tdxp = xx,tdyp = yy;
int tdxp = xx, tdyp = yy;
int oldview=-1, oldloop = -1;
int ovr_type = 0;
text_lips_offset = 0;
Expand Down Expand Up @@ -2685,17 +2683,16 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
tdxp = view->RoomToScreen(data_to_game_coord(speakingChar->x), 0).first.X;
if (tdxp < 2)
tdxp = 2;
// tell it to centre it (passing negative x coord further will be treated as a alignment instruction)
// FIXME: this is unreliable and bug prone, use a separate argument for alignment!
tdxp = -tdxp;
// tell it to align it by center
align_hcenter = auto_position && (xx < 0);

if (tdyp < 0)
{
int sppic = views[speakingChar->view].loops[speakingChar->loop].frames[0].pic;
int height = (charextra[aschar].height < 1) ? game.SpriteInfos[sppic].Height : charextra[aschar].height;
tdyp = view->RoomToScreen(0, data_to_game_coord(charextra[aschar].GetEffectiveY(speakingChar)) - height).first.Y
- get_fixed_pixel_size(5);
if (isThought) // if it's a thought, lift it a bit further up
if (is_thought) // if it's a thought, lift it a bit further up
tdyp -= get_fixed_pixel_size(10);
}
if (tdyp < 5)
Expand All @@ -2705,6 +2702,7 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i

if ((useview >= 0) && (game.options[OPT_SPEECHTYPE] > 0)) {
// Sierra-style close-up portrait
disp_pos = kDisplayTextPos_Normal;

if (play.swap_portrait_lastchar != aschar) {
// if the portraits are set to Alternate, OR they are
Expand Down Expand Up @@ -2900,10 +2898,10 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
facetalkloop = 0;
facetalkframe = 0;
facetalkwait = viptr->loops[0].frames[0].speed + GetCharacterSpeechAnimationDelay(speakingChar);
facetalkrepeat = (isThought) ? 0 : 1;
facetalkrepeat = (is_thought) ? 0 : 1;
facetalkBlinkLoop = 0;
facetalkAllowBlink = 1;
if ((isThought) && (speakingChar->flags & CHF_NOBLINKANDTHINK))
if ((is_thought) && (speakingChar->flags & CHF_NOBLINKANDTHINK))
facetalkAllowBlink = 0;
facetalkchar = &game.chars[aschar];
if (facetalkchar->blinktimer < 0)
Expand All @@ -2921,7 +2919,7 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
oldview = speakingChar->view;
oldloop = speakingChar->loop;

speakingChar->set_animating(!isThought, // only repeat if speech, not thought
speakingChar->set_animating(!is_thought, // only repeat if speech, not thought
true, // always forwards
GetCharacterSpeechAnimationDelay(speakingChar));

Expand Down Expand Up @@ -2953,33 +2951,32 @@ void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int i
if ((relx < ui_view.GetWidth() / 4) || (relx > ui_view.GetWidth() - (ui_view.GetWidth() / 4)))
bwidth -= ui_view.GetWidth() / 5;
}
/* this causes the text to bob up and down as they talk
tdxp = OVR_AUTOPLACE;
tdyp = aschar;*/
if (!isThought) // set up the lip sync if not thinking
if (!is_thought) // set up the lip sync if not thinking
char_speaking_anim = aschar;

}
}
else
{
// If the character is in another room, then center the speech on screen
allow_shrink = kDisplayTextShrink_Left;
}

// If initial argument was NOT requiring a autoposition,
// but further calculation set it to be centered, then make it so here
// (note: this assumes that a valid width is also passed)
if ((xx >= 0) && (tdxp < 0))
// (NOTE: this assumes that a valid width is also passed)
if ((xx >= 0) && align_hcenter)
tdxp -= widd / 2;

// if they used DisplaySpeechAt, then use the supplied width
if ((widd > 0) && (isThought == 0))
if ((widd > 0) && (!is_thought))
allow_shrink = kDisplayTextShrink_None;

if (isThought)
if (is_thought)
char_thinking = aschar;

set_our_eip(155);
display_main(tdxp, tdyp, bwidth, texx, nullptr, kDisplayText_Speech, 0 /* no overid */,
DisplayTextLooks(disp_style, isThought, allow_shrink), FONT_SPEECH, text_color, overlayPositionFixed);
DisplayTextLooks(disp_style, disp_pos, allow_shrink, is_thought), FONT_SPEECH, text_color, overlayPositionFixed);
set_our_eip(156);
if ((play.in_conversation > 0) && (game.options[OPT_SPEECHTYPE] == 3))
closeupface = nullptr;
Expand Down Expand Up @@ -3023,8 +3020,9 @@ int get_character_currently_talking() {
return -1;
}

void DisplaySpeech(const char*texx, int aschar) {
_displayspeech (texx, aschar, -1, -1, -1, 0);
void DisplaySpeech(const char *texx, int aschar)
{
display_speech(texx, aschar, -1, -1, -1, true /*auto-pos*/, false /* not thought */);
}

// Calculate which frame of the loop to use for this character of
Expand Down
15 changes: 8 additions & 7 deletions Engine/ac/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@
// https://opensource.org/license/artistic-2-0/
//
//=============================================================================
//
//
//
//=============================================================================
#ifndef __AGS_EE_AC__CHARACTER_H
#define __AGS_EE_AC__CHARACTER_H

#include "ac/characterinfo.h"
#include "ac/characterextras.h"
#include "ac/display.h"
#include "ac/dynobj/scriptobject.h"
#include "ac/dynobj/scriptinvitem.h"
#include "ac/dynobj/scriptoverlay.h"
Expand Down Expand Up @@ -212,9 +209,13 @@ int my_getpixel(Common::Bitmap *blk, int x, int y);
// X and Y co-ordinates must be in 320x200 format
int check_click_on_character(int xx,int yy,int mood);
int is_pos_on_character(int xx,int yy);
void _DisplaySpeechCore(int chid, const char *displbuf);
void _DisplayThoughtCore(int chid, const char *displbuf);
void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int isThought);
void DisplaySpeechCore(int chid, const char *displbuf);
void DisplayThoughtCore(int chid, const char *displbuf);
// Displays character speech or thought message.
// When auto-position flag is on, passing negative coordinates will request
// auto-position on the respective axis. The nature of auto-pos will depend
// on a speech style. E.g. LA speech aligns above character's head.
void display_speech(const char *texx, int aschar, int xx, int yy, int widd, bool auto_position, bool is_thought);
int get_character_currently_talking();
void DisplaySpeech(const char*texx, int aschar);
int update_lip_sync(int talkview, int talkloop, int *talkframeptr);
Expand Down
73 changes: 48 additions & 25 deletions Engine/ac/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,22 @@ class DisplayMessageState : public GameState
};


DisplayTextPosition get_textpos_from_scriptcoords(int x, int y, bool for_speech)
{
int text_pos = kDisplayTextPos_Normal;
if (for_speech)
{
if (x < 0) text_pos |= kDisplayTextPos_OvercharX;
if (y < 0) text_pos |= kDisplayTextPos_OvercharY;
}
else
{
if (x < 0) text_pos |= kDisplayTextPos_ScreenCenterX;
if (y < 0) text_pos |= kDisplayTextPos_ScreenCenterY;
}
return (DisplayTextPosition)text_pos;
}

// Generates a textual image and returns a disposable bitmap
// FIXME: for historical reasons this function also contains position adjustment;
// but this should not be done here at all.
// FIXME: xx is allowed to be passed as OVR_AUTOPLACE, which has special meaning,
// but that's a confusing use of this argument.
Bitmap *create_textual_image(const char *text, const DisplayTextLooks &look, color_t text_color,
int &xx, int &yy, int &adjustedXX, int &adjustedYY, int wii, int usingfont,
bool &alphaChannel, const TopBarSettings *topbar)
Expand Down Expand Up @@ -178,25 +188,37 @@ Bitmap *create_textual_image(const char *text, const DisplayTextLooks &look, col
get_font_linespacing(usingfont),
get_text_lines_surf_height(usingfont, Lines.Count()));

if (topbar) {
if (topbar)
{
// ensure that the window is wide enough to display any top bar text
int topBarWid = get_text_width_outlined(topbar->Text.GetCStr(), topbar->Font);
topBarWid += data_to_game_coord(play.top_bar_borderwidth + 2) * 2;
if (longestline < topBarWid)
longestline = topBarWid;
}

const bool auto_align_pos = xx < -1; // sic, it was suggesting x < -1
const Rect &ui_view = play.GetUIViewport();
if (xx == OVR_AUTOPLACE);
if (xx == OVR_AUTOPLACE) {} // FIXME: don't use OVR_AUTOPLACE here
// centre text in middle of screen
else if (yy<0) yy = ui_view.GetHeight() / 2 - disp.FullTextHeight / 2 - padding;
// speech, so it wants to be above the character's head
else if (look.Style == kDisplayTextStyle_Overchar) {
// If ordered to auto align, then first make sure that the position is on screen
if (auto_align_pos) {
yy = Math::Clamp(yy, disp.FullTextHeight + screen_padding, ui_view.GetHeight() - screen_padding);
}
else if ((look.Position & kDisplayTextPos_ScreenCenterY) != 0)
{
yy = ui_view.GetHeight() / 2 - disp.FullTextHeight / 2 - padding;
}
// LA-style speech, so it wants to be above the character's head
else if ((look.Position & kDisplayTextPos_OvercharY) != 0)
{
// Clamp text position to screen bounds, and align by the text's bottom
yy = Math::Clamp(yy, disp.FullTextHeight + screen_padding, ui_view.GetHeight() - screen_padding);
yy -= disp.FullTextHeight;
yy = adjust_y_for_guis(yy);
}
// NOTE: this is possibly an accidental mistake, but historically
// this Y pos fixup is also applied for SayAt, which results in
// text's origin being a left-bottom rather than left-top.
// Maybe this could be fixed in some future versions...
else if (look.Style == kDisplayTextStyle_Overchar)
{
yy = std::max(yy, disp.FullTextHeight + screen_padding);
yy -= disp.FullTextHeight;
yy = adjust_y_for_guis(yy);
}
Expand All @@ -213,15 +235,18 @@ Bitmap *create_textual_image(const char *text, const DisplayTextLooks &look, col
xx += (oldWid - wii);
}

// If ordered to center around the x pos, then do so, and fixup
if (auto_align_pos) {
xx = (-xx) - wii / 2;
if (xx == OVR_AUTOPLACE) {} // FIXME: don't use OVR_AUTOPLACE here
else if ((look.Position & kDisplayTextPos_ScreenCenterX) != 0)
{
xx = ui_view.GetWidth() / 2 - wii / 2;
}
// If ordered to center around the x pos, then do so, and clamp to the screen bounds
else if ((look.Position & kDisplayTextPos_OvercharX) != 0)
{
xx -= wii / 2;
xx = Math::Clamp(xx, screen_padding, ui_view.GetWidth() - screen_padding - wii);
xx = adjust_x_for_guis(xx, yy);
}
// FIXME: this is unreliable, will execute only of xx == -1,
// but what if it's +1 inverted for centering?
else if (xx<0) xx = ui_view.GetWidth() / 2 - wii / 2;

const int extraHeight = paddingDoubledScaled;
const int bmp_width = std::max(2, wii);
Expand Down Expand Up @@ -371,9 +396,6 @@ bool display_check_user_input(int skip)
return state_handled;
}

// Pass yy = -1 to find Y co-ord automatically
// allowShrink = 0 for none, 1 for leftwards, 2 for rightwards
// pass blocking=2 to create permanent overlay
ScreenOverlay *display_main(int xx, int yy, int wii, const char *text,
const TopBarSettings *topbar, DisplayTextType disp_type, int over_id,
const DisplayTextLooks &look, int usingfont, color_t text_color,
Expand Down Expand Up @@ -405,7 +427,8 @@ ScreenOverlay *display_main(int xx, int yy, int wii, const char *text,
disp_type = kDisplayText_Speech;
}

if ((look.Style == kDisplayTextStyle_Overchar) && (disp_type < kDisplayText_NormalOverlay))
if ((look.Style == kDisplayTextStyle_PlainText || look.Style == kDisplayTextStyle_Overchar)
&& (disp_type < kDisplayText_NormalOverlay))
{
// update the all_buttons_disabled variable in advance
// of the adjust_x/y_for_guis calls
Expand Down
Loading

0 comments on commit 53da85f

Please sign in to comment.