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 //=============================================================================
24 //
25 // AGS Character functions
26 //
27 //=============================================================================
28 
29 #include "ags/engine/ac/global_character.h"
30 #include "ags/shared/ac/common.h"
31 #include "ags/shared/ac/view.h"
32 #include "ags/engine/ac/character.h"
33 #include "ags/engine/ac/display.h"
34 #include "ags/engine/ac/draw.h"
35 #include "ags/engine/ac/event.h"
36 #include "ags/shared/ac/game_setup_struct.h"
37 #include "ags/engine/ac/game_state.h"
38 #include "ags/engine/ac/global_overlay.h"
39 #include "ags/engine/ac/global_translation.h"
40 #include "ags/engine/ac/object.h"
41 #include "ags/engine/ac/overlay.h"
42 #include "ags/engine/ac/properties.h"
43 #include "ags/engine/ac/screen_overlay.h"
44 #include "ags/engine/ac/string.h"
45 #include "ags/engine/debugging/debug_log.h"
46 #include "ags/shared/game/room_struct.h"
47 #include "ags/engine/main/game_run.h"
48 #include "ags/engine/script/script.h"
49 
50 namespace AGS3 {
51 
52 using namespace AGS::Shared;
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 // defined in character unit
63 
64 
65 
66 
67 
68 
StopMoving(int chaa)69 void StopMoving(int chaa) {
70 
71 	Character_StopMoving(&_GP(game).chars[chaa]);
72 }
73 
ReleaseCharacterView(int chat)74 void ReleaseCharacterView(int chat) {
75 	if (!is_valid_character(chat))
76 		quit("!ReleaseCahracterView: invalid character supplied");
77 
78 	Character_UnlockView(&_GP(game).chars[chat]);
79 }
80 
MoveToWalkableArea(int charid)81 void MoveToWalkableArea(int charid) {
82 	if (!is_valid_character(charid))
83 		quit("!MoveToWalkableArea: invalid character specified");
84 
85 	Character_PlaceOnWalkableArea(&_GP(game).chars[charid]);
86 }
87 
FaceLocation(int cha,int xx,int yy)88 void FaceLocation(int cha, int xx, int yy) {
89 	if (!is_valid_character(cha))
90 		quit("!FaceLocation: Invalid character specified");
91 
92 	Character_FaceLocation(&_GP(game).chars[cha], xx, yy, BLOCKING);
93 }
94 
FaceCharacter(int cha,int toface)95 void FaceCharacter(int cha, int toface) {
96 	if (!is_valid_character(cha))
97 		quit("!FaceCharacter: Invalid character specified");
98 	if (!is_valid_character(toface))
99 		quit("!FaceCharacter: invalid character specified");
100 
101 	Character_FaceCharacter(&_GP(game).chars[cha], &_GP(game).chars[toface], BLOCKING);
102 }
103 
104 
SetCharacterIdle(int who,int iview,int itime)105 void SetCharacterIdle(int who, int iview, int itime) {
106 	if (!is_valid_character(who))
107 		quit("!SetCharacterIdle: Invalid character specified");
108 
109 	Character_SetIdleView(&_GP(game).chars[who], iview, itime);
110 }
111 
112 
113 
GetCharacterWidth(int ww)114 int GetCharacterWidth(int ww) {
115 	CharacterInfo *char1 = &_GP(game).chars[ww];
116 
117 	if (_G(charextra)[ww].width < 1) {
118 		if ((char1->view < 0) ||
119 		        (char1->loop >= _G(views)[char1->view].numLoops) ||
120 		        (char1->frame >= _G(views)[char1->view].loops[char1->loop].numFrames)) {
121 			debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
122 			return data_to_game_coord(4);
123 		}
124 
125 		return _GP(game).SpriteInfos[_G(views)[char1->view].loops[char1->loop].frames[char1->frame].pic].Width;
126 	} else
127 		return _G(charextra)[ww].width;
128 }
129 
GetCharacterHeight(int charid)130 int GetCharacterHeight(int charid) {
131 	CharacterInfo *char1 = &_GP(game).chars[charid];
132 
133 	if (_G(charextra)[charid].height < 1) {
134 		if ((char1->view < 0) ||
135 		        (char1->loop >= _G(views)[char1->view].numLoops) ||
136 		        (char1->frame >= _G(views)[char1->view].loops[char1->loop].numFrames)) {
137 			debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
138 			return data_to_game_coord(2);
139 		}
140 
141 		return _GP(game).SpriteInfos[_G(views)[char1->view].loops[char1->loop].frames[char1->frame].pic].Height;
142 	} else
143 		return _G(charextra)[charid].height;
144 }
145 
146 
147 
SetCharacterBaseline(int obn,int basel)148 void SetCharacterBaseline(int obn, int basel) {
149 	if (!is_valid_character(obn)) quit("!SetCharacterBaseline: invalid object number specified");
150 
151 	Character_SetBaseline(&_GP(game).chars[obn], basel);
152 }
153 
154 // pass trans=0 for fully solid, trans=100 for fully transparent
SetCharacterTransparency(int obn,int trans)155 void SetCharacterTransparency(int obn, int trans) {
156 	if (!is_valid_character(obn))
157 		quit("!SetCharTransparent: invalid character number specified");
158 
159 	Character_SetTransparency(&_GP(game).chars[obn], trans);
160 }
161 
scAnimateCharacter(int chh,int loopn,int sppd,int rept)162 void scAnimateCharacter(int chh, int loopn, int sppd, int rept) {
163 	if (!is_valid_character(chh))
164 		quit("AnimateCharacter: invalid character");
165 
166 	animate_character(&_GP(game).chars[chh], loopn, sppd, rept);
167 }
168 
AnimateCharacterEx(int chh,int loopn,int sppd,int rept,int direction,int blocking)169 void AnimateCharacterEx(int chh, int loopn, int sppd, int rept, int direction, int blocking) {
170 	if ((direction < 0) || (direction > 1))
171 		quit("!AnimateCharacterEx: invalid direction");
172 	if (!is_valid_character(chh))
173 		quit("AnimateCharacter: invalid character");
174 
175 	if (direction)
176 		direction = BACKWARDS;
177 	else
178 		direction = FORWARDS;
179 
180 	if (blocking)
181 		blocking = BLOCKING;
182 	else
183 		blocking = IN_BACKGROUND;
184 
185 	Character_Animate(&_GP(game).chars[chh], loopn, sppd, rept, blocking, direction);
186 
187 }
188 
189 
SetPlayerCharacter(int newchar)190 void SetPlayerCharacter(int newchar) {
191 	if (!is_valid_character(newchar))
192 		quit("!SetPlayerCharacter: Invalid character specified");
193 
194 	Character_SetAsPlayer(&_GP(game).chars[newchar]);
195 }
196 
FollowCharacterEx(int who,int tofollow,int distaway,int eagerness)197 void FollowCharacterEx(int who, int tofollow, int distaway, int eagerness) {
198 	if (!is_valid_character(who))
199 		quit("!FollowCharacter: Invalid character specified");
200 	CharacterInfo *chtofollow = nullptr;
201 	if (tofollow != -1) {
202 		if (!is_valid_character(tofollow))
203 			quit("!FollowCharacterEx: invalid character to follow");
204 		else
205 			chtofollow = &_GP(game).chars[tofollow];
206 	}
207 
208 	Character_FollowCharacter(&_GP(game).chars[who], chtofollow, distaway, eagerness);
209 }
210 
FollowCharacter(int who,int tofollow)211 void FollowCharacter(int who, int tofollow) {
212 	FollowCharacterEx(who, tofollow, 10, 97);
213 }
214 
SetCharacterIgnoreLight(int who,int yesorno)215 void SetCharacterIgnoreLight(int who, int yesorno) {
216 	if (!is_valid_character(who))
217 		quit("!SetCharacterIgnoreLight: Invalid character specified");
218 
219 	Character_SetIgnoreLighting(&_GP(game).chars[who], yesorno);
220 }
221 
222 
223 
224 
MoveCharacter(int cc,int xx,int yy)225 void MoveCharacter(int cc, int xx, int yy) {
226 	walk_character(cc, xx, yy, 0, true);
227 }
MoveCharacterDirect(int cc,int xx,int yy)228 void MoveCharacterDirect(int cc, int xx, int yy) {
229 	walk_character(cc, xx, yy, 1, true);
230 }
MoveCharacterStraight(int cc,int xx,int yy)231 void MoveCharacterStraight(int cc, int xx, int yy) {
232 	if (!is_valid_character(cc))
233 		quit("!MoveCharacterStraight: invalid character specified");
234 
235 	Character_WalkStraight(&_GP(game).chars[cc], xx, yy, IN_BACKGROUND);
236 }
237 
238 // Append to character path
MoveCharacterPath(int chac,int tox,int toy)239 void MoveCharacterPath(int chac, int tox, int toy) {
240 	if (!is_valid_character(chac))
241 		quit("!MoveCharacterPath: invalid character specified");
242 
243 	Character_AddWaypoint(&_GP(game).chars[chac], tox, toy);
244 }
245 
246 
GetPlayerCharacter()247 int GetPlayerCharacter() {
248 	return _GP(game).playercharacter;
249 }
250 
SetCharacterSpeedEx(int chaa,int xspeed,int yspeed)251 void SetCharacterSpeedEx(int chaa, int xspeed, int yspeed) {
252 	if (!is_valid_character(chaa))
253 		quit("!SetCharacterSpeedEx: invalid character");
254 
255 	Character_SetSpeed(&_GP(game).chars[chaa], xspeed, yspeed);
256 
257 }
258 
SetCharacterSpeed(int chaa,int nspeed)259 void SetCharacterSpeed(int chaa, int nspeed) {
260 	SetCharacterSpeedEx(chaa, nspeed, nspeed);
261 }
262 
SetTalkingColor(int chaa,int ncol)263 void SetTalkingColor(int chaa, int ncol) {
264 	if (!is_valid_character(chaa)) quit("!SetTalkingColor: invalid character");
265 
266 	Character_SetSpeechColor(&_GP(game).chars[chaa], ncol);
267 }
268 
SetCharacterSpeechView(int chaa,int vii)269 void SetCharacterSpeechView(int chaa, int vii) {
270 	if (!is_valid_character(chaa))
271 		quit("!SetCharacterSpeechView: invalid character specified");
272 
273 	Character_SetSpeechView(&_GP(game).chars[chaa], vii);
274 }
275 
SetCharacterBlinkView(int chaa,int vii,int intrv)276 void SetCharacterBlinkView(int chaa, int vii, int intrv) {
277 	if (!is_valid_character(chaa))
278 		quit("!SetCharacterBlinkView: invalid character specified");
279 
280 	Character_SetBlinkView(&_GP(game).chars[chaa], vii);
281 	Character_SetBlinkInterval(&_GP(game).chars[chaa], intrv);
282 }
283 
SetCharacterView(int chaa,int vii)284 void SetCharacterView(int chaa, int vii) {
285 	if (!is_valid_character(chaa))
286 		quit("!SetCharacterView: invalid character specified");
287 
288 	Character_LockView(&_GP(game).chars[chaa], vii);
289 }
290 
SetCharacterFrame(int chaa,int view,int loop,int frame)291 void SetCharacterFrame(int chaa, int view, int loop, int frame) {
292 
293 	Character_LockViewFrame(&_GP(game).chars[chaa], view, loop, frame);
294 }
295 
296 // similar to SetCharView, but aligns the frame to make it line up
SetCharacterViewEx(int chaa,int vii,int loop,int align)297 void SetCharacterViewEx(int chaa, int vii, int loop, int align) {
298 
299 	Character_LockViewAligned(&_GP(game).chars[chaa], vii, loop, align);
300 }
301 
SetCharacterViewOffset(int chaa,int vii,int xoffs,int yoffs)302 void SetCharacterViewOffset(int chaa, int vii, int xoffs, int yoffs) {
303 
304 	Character_LockViewOffset(&_GP(game).chars[chaa], vii, xoffs, yoffs);
305 }
306 
307 
ChangeCharacterView(int chaa,int vii)308 void ChangeCharacterView(int chaa, int vii) {
309 	if (!is_valid_character(chaa))
310 		quit("!ChangeCharacterView: invalid character specified");
311 
312 	Character_ChangeView(&_GP(game).chars[chaa], vii);
313 }
314 
SetCharacterClickable(int cha,int clik)315 void SetCharacterClickable(int cha, int clik) {
316 	if (!is_valid_character(cha))
317 		quit("!SetCharacterClickable: Invalid character specified");
318 	// make the character clicklabe (reset "No interaction" bit)
319 	_GP(game).chars[cha].flags &= ~CHF_NOINTERACT;
320 	// if they don't want it clickable, set the relevant bit
321 	if (clik == 0)
322 		_GP(game).chars[cha].flags |= CHF_NOINTERACT;
323 }
324 
SetCharacterIgnoreWalkbehinds(int cha,int clik)325 void SetCharacterIgnoreWalkbehinds(int cha, int clik) {
326 	if (!is_valid_character(cha))
327 		quit("!SetCharacterIgnoreWalkbehinds: Invalid character specified");
328 
329 	Character_SetIgnoreWalkbehinds(&_GP(game).chars[cha], clik);
330 }
331 
332 
MoveCharacterToObject(int chaa,int obbj)333 void MoveCharacterToObject(int chaa, int obbj) {
334 	// invalid object, do nothing
335 	// this allows MoveCharacterToObject(EGO, GetObjectAt(...));
336 	if (!is_valid_object(obbj))
337 		return;
338 
339 	walk_character(chaa, _G(objs)[obbj].x + 5, _G(objs)[obbj].y + 6, 0, true);
340 
341 	GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
342 }
343 
MoveCharacterToHotspot(int chaa,int hotsp)344 void MoveCharacterToHotspot(int chaa, int hotsp) {
345 	if ((hotsp < 0) || (hotsp >= MAX_ROOM_HOTSPOTS))
346 		quit("!MovecharacterToHotspot: invalid hotspot");
347 	if (_GP(thisroom).Hotspots[hotsp].WalkTo.X < 1) return;
348 	walk_character(chaa, _GP(thisroom).Hotspots[hotsp].WalkTo.X, _GP(thisroom).Hotspots[hotsp].WalkTo.Y, 0, true);
349 
350 	GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
351 }
352 
MoveCharacterBlocking(int chaa,int xx,int yy,int direct)353 int MoveCharacterBlocking(int chaa, int xx, int yy, int direct) {
354 	if (!is_valid_character(chaa))
355 		quit("!MoveCharacterBlocking: invalid character");
356 
357 	// check if they try to move the player when Hide Player Char is
358 	// ticked -- otherwise this will hang the game
359 	if (_GP(game).chars[chaa].on != 1) {
360 		debug_script_warn("MoveCharacterBlocking: character is turned off (is Hide Player Character selected?) and cannot be moved");
361 		return 0;
362 	}
363 
364 	if (direct)
365 		MoveCharacterDirect(chaa, xx, yy);
366 	else
367 		MoveCharacter(chaa, xx, yy);
368 
369 	GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
370 
371 	return -1; // replicates legacy engine effect
372 }
373 
GetCharacterSpeechAnimationDelay(CharacterInfo * cha)374 int GetCharacterSpeechAnimationDelay(CharacterInfo *cha) {
375 	if ((_G(loaded_game_file_version) < kGameVersion_312) && (_GP(game).options[OPT_SPEECHTYPE] != 0)) {
376 		// legacy versions of AGS assigned a fixed delay to Sierra-style speech only
377 		return 5;
378 	}
379 	if (_GP(game).options[OPT_GLOBALTALKANIMSPD] != 0)
380 		return _GP(play).talkanim_speed;
381 	else
382 		return cha->speech_anim_speed;
383 }
384 
RunCharacterInteraction(int cc,int mood)385 void RunCharacterInteraction(int cc, int mood) {
386 	if (!is_valid_character(cc))
387 		quit("!RunCharacterInteraction: invalid character");
388 
389 	int passon = -1, cdata = -1;
390 	if (mood == MODE_LOOK) passon = 0;
391 	else if (mood == MODE_HAND) passon = 1;
392 	else if (mood == MODE_TALK) passon = 2;
393 	else if (mood == MODE_USE) {
394 		passon = 3;
395 		cdata = _G(playerchar)->activeinv;
396 		_GP(play).usedinv = cdata;
397 	} else if (mood == MODE_PICKUP) passon = 5;
398 	else if (mood == MODE_CUSTOM1) passon = 6;
399 	else if (mood == MODE_CUSTOM2) passon = 7;
400 
401 	_G(evblockbasename) = "character%d";
402 	_G(evblocknum) = cc;
403 	if (_G(loaded_game_file_version) > kGameVersion_272) {
404 		if (passon >= 0)
405 			run_interaction_script(_GP(game).charScripts[cc].get(), passon, 4, (passon == 3));
406 		run_interaction_script(_GP(game).charScripts[cc].get(), 4);  // any click on char
407 	} else {
408 		if (passon >= 0)
409 			run_interaction_event(_GP(game).intrChar[cc].get(), passon, 4, (passon == 3));
410 		run_interaction_event(_GP(game).intrChar[cc].get(), 4);  // any click on char
411 	}
412 }
413 
AreCharObjColliding(int charid,int objid)414 int AreCharObjColliding(int charid, int objid) {
415 	if (!is_valid_character(charid))
416 		quit("!AreCharObjColliding: invalid character");
417 	if (!is_valid_object(objid))
418 		quit("!AreCharObjColliding: invalid object number");
419 
420 	return Character_IsCollidingWithObject(&_GP(game).chars[charid], &_G(scrObj)[objid]);
421 }
422 
AreCharactersColliding(int cchar1,int cchar2)423 int AreCharactersColliding(int cchar1, int cchar2) {
424 	if (!is_valid_character(cchar1))
425 		quit("!AreCharactersColliding: invalid char1");
426 	if (!is_valid_character(cchar2))
427 		quit("!AreCharactersColliding: invalid char2");
428 
429 	return Character_IsCollidingWithChar(&_GP(game).chars[cchar1], &_GP(game).chars[cchar2]);
430 }
431 
GetCharacterProperty(int cha,const char * property)432 int GetCharacterProperty(int cha, const char *property) {
433 	if (!is_valid_character(cha))
434 		quit("!GetCharacterProperty: invalid character");
435 	return get_int_property(_GP(game).charProps[cha], _GP(play).charProps[cha], property);
436 }
437 
SetCharacterProperty(int who,int flag,int yesorno)438 void SetCharacterProperty(int who, int flag, int yesorno) {
439 	if (!is_valid_character(who))
440 		quit("!SetCharacterProperty: Invalid character specified");
441 
442 	Character_SetOption(&_GP(game).chars[who], flag, yesorno);
443 }
444 
GetCharacterPropertyText(int item,const char * property,char * bufer)445 void GetCharacterPropertyText(int item, const char *property, char *bufer) {
446 	get_text_property(_GP(game).charProps[item], _GP(play).charProps[item], property, bufer);
447 }
448 
GetCharIDAtScreen(int xx,int yy)449 int GetCharIDAtScreen(int xx, int yy) {
450 	VpPoint vpt = _GP(play).ScreenToRoomDivDown(xx, yy);
451 	if (vpt.second < 0)
452 		return -1;
453 	return is_pos_on_character(vpt.first.X, vpt.first.Y);
454 }
455 
SetActiveInventory(int iit)456 void SetActiveInventory(int iit) {
457 
458 	ScriptInvItem *tosend = nullptr;
459 	if ((iit > 0) && (iit < _GP(game).numinvitems))
460 		tosend = &_G(scrInv)[iit];
461 	else if (iit != -1)
462 		quitprintf("!SetActiveInventory: invalid inventory number %d", iit);
463 
464 	Character_SetActiveInventory(_G(playerchar), tosend);
465 }
466 
update_invorder()467 void update_invorder() {
468 	for (int cc = 0; cc < _GP(game).numcharacters; cc++) {
469 		_G(charextra)[cc].invorder_count = 0;
470 		int ff, howmany;
471 		// Iterate through all inv items, adding them once (or multiple
472 		// times if requested) to the list.
473 		for (ff = 0; ff < _GP(game).numinvitems; ff++) {
474 			howmany = _GP(game).chars[cc].inv[ff];
475 			if ((_GP(game).options[OPT_DUPLICATEINV] == 0) && (howmany > 1))
476 				howmany = 1;
477 
478 			for (int ts = 0; ts < howmany; ts++) {
479 				if (_G(charextra)[cc].invorder_count >= MAX_INVORDER)
480 					quit("!Too many inventory items to display: 500 max");
481 
482 				_G(charextra)[cc].invorder[_G(charextra)[cc].invorder_count] = ff;
483 				_G(charextra)[cc].invorder_count++;
484 			}
485 		}
486 	}
487 	// backwards compatibility
488 	_GP(play).obsolete_inv_numorder = _G(charextra)[_GP(game).playercharacter].invorder_count;
489 	GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
490 }
491 
add_inventory(int inum)492 void add_inventory(int inum) {
493 	if ((inum < 0) || (inum >= MAX_INV))
494 		quit("!AddInventory: invalid inventory number");
495 
496 	Character_AddInventory(_G(playerchar), &_G(scrInv)[inum], SCR_NO_VALUE);
497 
498 	_GP(play).obsolete_inv_numorder = _G(charextra)[_GP(game).playercharacter].invorder_count;
499 }
500 
lose_inventory(int inum)501 void lose_inventory(int inum) {
502 	if ((inum < 0) || (inum >= MAX_INV))
503 		quit("!LoseInventory: invalid inventory number");
504 
505 	Character_LoseInventory(_G(playerchar), &_G(scrInv)[inum]);
506 
507 	_GP(play).obsolete_inv_numorder = _G(charextra)[_GP(game).playercharacter].invorder_count;
508 }
509 
AddInventoryToCharacter(int charid,int inum)510 void AddInventoryToCharacter(int charid, int inum) {
511 	if (!is_valid_character(charid))
512 		quit("!AddInventoryToCharacter: invalid character specified");
513 	if ((inum < 1) || (inum >= _GP(game).numinvitems))
514 		quit("!AddInventory: invalid inv item specified");
515 
516 	Character_AddInventory(&_GP(game).chars[charid], &_G(scrInv)[inum], SCR_NO_VALUE);
517 }
518 
LoseInventoryFromCharacter(int charid,int inum)519 void LoseInventoryFromCharacter(int charid, int inum) {
520 	if (!is_valid_character(charid))
521 		quit("!LoseInventoryFromCharacter: invalid character specified");
522 	if ((inum < 1) || (inum >= _GP(game).numinvitems))
523 		quit("!AddInventory: invalid inv item specified");
524 
525 	Character_LoseInventory(&_GP(game).chars[charid], &_G(scrInv)[inum]);
526 }
527 
DisplayThought(int chid,const char * text)528 void DisplayThought(int chid, const char *text) {
529 	if ((chid < 0) || (chid >= _GP(game).numcharacters))
530 		quit("!DisplayThought: invalid character specified");
531 
532 	_DisplayThoughtCore(chid, text);
533 }
534 
__sc_displayspeech(int chid,const char * text)535 void __sc_displayspeech(int chid, const char *text) {
536 	if ((chid < 0) || (chid >= _GP(game).numcharacters))
537 		quit("!DisplaySpeech: invalid character specified");
538 
539 	_DisplaySpeechCore(chid, text);
540 }
541 
542 // **** THIS IS UNDOCUMENTED BECAUSE IT DOESN'T WORK PROPERLY
543 // **** AT 640x400 AND DOESN'T USE THE RIGHT SPEECH STYLE
DisplaySpeechAt(int xx,int yy,int wii,int aschar,const char * spch)544 void DisplaySpeechAt(int xx, int yy, int wii, int aschar, const char *spch) {
545 	data_to_game_coords(&xx, &yy);
546 	wii = data_to_game_coord(wii);
547 	_displayspeech(get_translation(spch), aschar, xx, yy, wii, 0);
548 }
549 
DisplaySpeechBackground(int charid,const char * speel)550 int DisplaySpeechBackground(int charid, const char *speel) {
551 	// remove any previous background speech for this character
552 	int cc;
553 	for (cc = 0; cc < _G(numscreenover); cc++) {
554 		if (_G(screenover)[cc].bgSpeechForChar == charid) {
555 			remove_screen_overlay_index(cc);
556 			cc--;
557 		}
558 	}
559 
560 	int ovrl = CreateTextOverlay(OVR_AUTOPLACE, charid, _GP(play).GetUIViewport().GetWidth() / 2, FONT_SPEECH,
561 	                             -_GP(game).chars[charid].talkcolor, get_translation(speel), DISPLAYTEXT_NORMALOVERLAY);
562 
563 	int scid = find_overlay_of_type(ovrl);
564 	_G(screenover)[scid].bgSpeechForChar = charid;
565 	_G(screenover)[scid].timeout = GetTextDisplayTime(speel, 1);
566 	return ovrl;
567 }
568 
569 } // namespace AGS3
570