mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
This fixes a mistake introduced in commit 4be1556, which in turn followed b02814b
The problem is, that CCStaticArray does not work as intended, and seems won't (at
least not without some extra workarounds).
When the script compiler is told to get an Nth element of a global struct array, such
as character[n], it calculates the memory address as
Character* ptr = (array address + sizeof(Character) * n).
If this address is used for the read/write operations, these ops can be intercepted by
interpreter and remapped into the real fields (see IScriptObject::ReadN, WriteN
interface).
But if this address is used IN POINTER COMPARISON BY VALUE, then we cannot do
anything. And if our real struct in the engine is stored on a different relative memory
offset than one expected by compiler, then this pointer comparison will fail.
This breaks script expressions like e.g.:
if (player == character[n])
From upstream 97ed18e95b587544ae47497f0a5f1d1ba00a7e29
566 lines
18 KiB
C++
566 lines
18 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
//=============================================================================
|
|
//
|
|
// AGS Character functions
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "ags/engine/ac/global_character.h"
|
|
#include "ags/shared/ac/common.h"
|
|
#include "ags/shared/ac/view.h"
|
|
#include "ags/engine/ac/character.h"
|
|
#include "ags/engine/ac/display.h"
|
|
#include "ags/engine/ac/draw.h"
|
|
#include "ags/engine/ac/event.h"
|
|
#include "ags/shared/ac/game_setup_struct.h"
|
|
#include "ags/engine/ac/game_state.h"
|
|
#include "ags/engine/ac/global_overlay.h"
|
|
#include "ags/engine/ac/global_translation.h"
|
|
#include "ags/engine/ac/object.h"
|
|
#include "ags/engine/ac/overlay.h"
|
|
#include "ags/engine/ac/properties.h"
|
|
#include "ags/engine/ac/screen_overlay.h"
|
|
#include "ags/engine/ac/string.h"
|
|
#include "ags/engine/ac/dynobj/cc_character.h"
|
|
#include "ags/engine/debugging/debug_log.h"
|
|
#include "ags/shared/game/room_struct.h"
|
|
#include "ags/engine/main/game_run.h"
|
|
#include "ags/engine/script/script.h"
|
|
#include "ags/globals.h"
|
|
|
|
namespace AGS3 {
|
|
|
|
using namespace AGS::Shared;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// defined in character unit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void StopMoving(int chaa) {
|
|
|
|
Character_StopMoving(&_GP(game).chars[chaa]);
|
|
}
|
|
|
|
void ReleaseCharacterView(int chat) {
|
|
if (!is_valid_character(chat))
|
|
quit("!ReleaseCahracterView: invalid character supplied");
|
|
|
|
Character_UnlockView(&_GP(game).chars[chat]);
|
|
}
|
|
|
|
void MoveToWalkableArea(int charid) {
|
|
if (!is_valid_character(charid))
|
|
quit("!MoveToWalkableArea: invalid character specified");
|
|
|
|
Character_PlaceOnWalkableArea(&_GP(game).chars[charid]);
|
|
}
|
|
|
|
void FaceLocation(int cha, int xx, int yy) {
|
|
if (!is_valid_character(cha))
|
|
quit("!FaceLocation: Invalid character specified");
|
|
|
|
Character_FaceLocation(&_GP(game).chars[cha], xx, yy, BLOCKING);
|
|
}
|
|
|
|
void FaceCharacter(int cha, int toface) {
|
|
if (!is_valid_character(cha))
|
|
quit("!FaceCharacter: Invalid character specified");
|
|
if (!is_valid_character(toface))
|
|
quit("!FaceCharacter: invalid character specified");
|
|
|
|
Character_FaceCharacter(&_GP(game).chars[cha], &_GP(game).chars[toface], BLOCKING);
|
|
}
|
|
|
|
|
|
void SetCharacterIdle(int who, int iview, int itime) {
|
|
if (!is_valid_character(who))
|
|
quit("!SetCharacterIdle: Invalid character specified");
|
|
|
|
Character_SetIdleView(&_GP(game).chars[who], iview, itime);
|
|
}
|
|
|
|
|
|
|
|
int GetCharacterWidth(int ww) {
|
|
CharacterInfo *char1 = &_GP(game).chars[ww];
|
|
|
|
if (_GP(charextra)[ww].width < 1) {
|
|
if ((char1->view < 0) ||
|
|
(char1->loop >= _GP(views)[char1->view].numLoops) ||
|
|
(char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
|
|
debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame);
|
|
return data_to_game_coord(4);
|
|
}
|
|
|
|
return _GP(game).SpriteInfos[_GP(views)[char1->view].loops[char1->loop].frames[char1->frame].pic].Width;
|
|
} else
|
|
return _GP(charextra)[ww].width;
|
|
}
|
|
|
|
int GetCharacterHeight(int charid) {
|
|
CharacterInfo *char1 = &_GP(game).chars[charid];
|
|
|
|
if (_GP(charextra)[charid].height < 1) {
|
|
if ((char1->view < 0) ||
|
|
(char1->loop >= _GP(views)[char1->view].numLoops) ||
|
|
(char1->frame >= _GP(views)[char1->view].loops[char1->loop].numFrames)) {
|
|
debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d",
|
|
char1->scrname, char1->view + 1, char1->loop, char1->frame);
|
|
return data_to_game_coord(2);
|
|
}
|
|
|
|
return _GP(game).SpriteInfos[_GP(views)[char1->view].loops[char1->loop].frames[char1->frame].pic].Height;
|
|
} else
|
|
return _GP(charextra)[charid].height;
|
|
}
|
|
|
|
|
|
|
|
void SetCharacterBaseline(int obn, int basel) {
|
|
if (!is_valid_character(obn)) quit("!SetCharacterBaseline: invalid object number specified");
|
|
|
|
Character_SetBaseline(&_GP(game).chars[obn], basel);
|
|
}
|
|
|
|
// pass trans=0 for fully solid, trans=100 for fully transparent
|
|
void SetCharacterTransparency(int obn, int trans) {
|
|
if (!is_valid_character(obn))
|
|
quit("!SetCharTransparent: invalid character number specified");
|
|
|
|
Character_SetTransparency(&_GP(game).chars[obn], trans);
|
|
}
|
|
|
|
void AnimateCharacter4(int chh, int loopn, int sppd, int rept) {
|
|
AnimateCharacter6(chh, loopn, sppd, rept, FORWARDS, IN_BACKGROUND);
|
|
}
|
|
|
|
void AnimateCharacter6(int chh, int loopn, int sppd, int rept, int direction, int blocking) {
|
|
if (!is_valid_character(chh))
|
|
quit("AnimateCharacter: invalid character");
|
|
|
|
Character_Animate5(&_GP(game).chars[chh], loopn, sppd, rept, blocking, direction);
|
|
}
|
|
|
|
void SetPlayerCharacter(int newchar) {
|
|
if (!is_valid_character(newchar))
|
|
quit("!SetPlayerCharacter: Invalid character specified");
|
|
|
|
Character_SetAsPlayer(&_GP(game).chars[newchar]);
|
|
}
|
|
|
|
void FollowCharacterEx(int who, int tofollow, int distaway, int eagerness) {
|
|
if (!is_valid_character(who))
|
|
quit("!FollowCharacter: Invalid character specified");
|
|
CharacterInfo *chtofollow = nullptr;
|
|
if (tofollow != -1) {
|
|
if (!is_valid_character(tofollow))
|
|
quit("!FollowCharacterEx: invalid character to follow");
|
|
else
|
|
chtofollow = &_GP(game).chars[tofollow];
|
|
}
|
|
|
|
Character_FollowCharacter(&_GP(game).chars[who], chtofollow, distaway, eagerness);
|
|
}
|
|
|
|
void FollowCharacter(int who, int tofollow) {
|
|
FollowCharacterEx(who, tofollow, 10, 97);
|
|
}
|
|
|
|
void SetCharacterIgnoreLight(int who, int yesorno) {
|
|
if (!is_valid_character(who))
|
|
quit("!SetCharacterIgnoreLight: Invalid character specified");
|
|
|
|
Character_SetIgnoreLighting(&_GP(game).chars[who], yesorno);
|
|
}
|
|
|
|
|
|
|
|
|
|
void MoveCharacter(int cc, int xx, int yy) {
|
|
walk_character(cc, xx, yy, 0, true);
|
|
}
|
|
void MoveCharacterDirect(int cc, int xx, int yy) {
|
|
walk_character(cc, xx, yy, 1, true);
|
|
}
|
|
void MoveCharacterStraight(int cc, int xx, int yy) {
|
|
if (!is_valid_character(cc))
|
|
quit("!MoveCharacterStraight: invalid character specified");
|
|
|
|
Character_WalkStraight(&_GP(game).chars[cc], xx, yy, IN_BACKGROUND);
|
|
}
|
|
|
|
// Append to character path
|
|
void MoveCharacterPath(int chac, int tox, int toy) {
|
|
if (!is_valid_character(chac))
|
|
quit("!MoveCharacterPath: invalid character specified");
|
|
|
|
Character_AddWaypoint(&_GP(game).chars[chac], tox, toy);
|
|
}
|
|
|
|
|
|
int GetPlayerCharacter() {
|
|
return _GP(game).playercharacter;
|
|
}
|
|
|
|
void SetCharacterSpeedEx(int chaa, int xspeed, int yspeed) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!SetCharacterSpeedEx: invalid character");
|
|
|
|
Character_SetSpeed(&_GP(game).chars[chaa], xspeed, yspeed);
|
|
|
|
}
|
|
|
|
void SetCharacterSpeed(int chaa, int nspeed) {
|
|
SetCharacterSpeedEx(chaa, nspeed, nspeed);
|
|
}
|
|
|
|
void SetTalkingColor(int chaa, int ncol) {
|
|
if (!is_valid_character(chaa)) quit("!SetTalkingColor: invalid character");
|
|
|
|
Character_SetSpeechColor(&_GP(game).chars[chaa], ncol);
|
|
}
|
|
|
|
void SetCharacterSpeechView(int chaa, int vii) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!SetCharacterSpeechView: invalid character specified");
|
|
|
|
Character_SetSpeechView(&_GP(game).chars[chaa], vii);
|
|
}
|
|
|
|
void SetCharacterBlinkView(int chaa, int vii, int intrv) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!SetCharacterBlinkView: invalid character specified");
|
|
|
|
Character_SetBlinkView(&_GP(game).chars[chaa], vii);
|
|
Character_SetBlinkInterval(&_GP(game).chars[chaa], intrv);
|
|
}
|
|
|
|
void SetCharacterView(int chaa, int vii) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!SetCharacterView: invalid character specified");
|
|
|
|
Character_LockView(&_GP(game).chars[chaa], vii);
|
|
}
|
|
|
|
void SetCharacterFrame(int chaa, int view, int loop, int frame) {
|
|
|
|
Character_LockViewFrame(&_GP(game).chars[chaa], view, loop, frame);
|
|
}
|
|
|
|
// similar to SetCharView, but aligns the frame to make it line up
|
|
void SetCharacterViewEx(int chaa, int vii, int loop, int align) {
|
|
|
|
Character_LockViewAligned(&_GP(game).chars[chaa], vii, loop, align);
|
|
}
|
|
|
|
void SetCharacterViewOffset(int chaa, int vii, int xoffs, int yoffs) {
|
|
|
|
Character_LockViewOffset(&_GP(game).chars[chaa], vii, xoffs, yoffs);
|
|
}
|
|
|
|
|
|
void ChangeCharacterView(int chaa, int vii) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!ChangeCharacterView: invalid character specified");
|
|
|
|
Character_ChangeView(&_GP(game).chars[chaa], vii);
|
|
}
|
|
|
|
void SetCharacterClickable(int cha, int clik) {
|
|
if (!is_valid_character(cha))
|
|
quit("!SetCharacterClickable: Invalid character specified");
|
|
// make the character clicklabe (reset "No interaction" bit)
|
|
_GP(game).chars[cha].flags &= ~CHF_NOINTERACT;
|
|
// if they don't want it clickable, set the relevant bit
|
|
if (clik == 0)
|
|
_GP(game).chars[cha].flags |= CHF_NOINTERACT;
|
|
}
|
|
|
|
void SetCharacterIgnoreWalkbehinds(int cha, int clik) {
|
|
if (!is_valid_character(cha))
|
|
quit("!SetCharacterIgnoreWalkbehinds: Invalid character specified");
|
|
|
|
Character_SetIgnoreWalkbehinds(&_GP(game).chars[cha], clik);
|
|
}
|
|
|
|
|
|
void MoveCharacterToObject(int chaa, int obbj) {
|
|
// invalid object, do nothing
|
|
// this allows MoveCharacterToObject(EGO, GetObjectAt(...));
|
|
if (!is_valid_object(obbj))
|
|
return;
|
|
|
|
walk_character(chaa, _G(objs)[obbj].x + 5, _G(objs)[obbj].y + 6, 0, true);
|
|
|
|
GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
|
|
}
|
|
|
|
void MoveCharacterToHotspot(int chaa, int hotsp) {
|
|
if ((hotsp < 0) || (hotsp >= MAX_ROOM_HOTSPOTS))
|
|
quit("!MovecharacterToHotspot: invalid hotspot");
|
|
if (_GP(thisroom).Hotspots[hotsp].WalkTo.X < 1) return;
|
|
walk_character(chaa, _GP(thisroom).Hotspots[hotsp].WalkTo.X, _GP(thisroom).Hotspots[hotsp].WalkTo.Y, 0, true);
|
|
|
|
GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
|
|
}
|
|
|
|
int MoveCharacterBlocking(int chaa, int xx, int yy, int direct) {
|
|
if (!is_valid_character(chaa))
|
|
quit("!MoveCharacterBlocking: invalid character");
|
|
|
|
// check if they try to move the player when Hide Player Char is
|
|
// ticked -- otherwise this will hang the game
|
|
if (_GP(game).chars[chaa].on != 1) {
|
|
debug_script_warn("MoveCharacterBlocking: character is turned off (is Hide Player Character selected?) and cannot be moved");
|
|
return 0;
|
|
}
|
|
|
|
if (direct)
|
|
MoveCharacterDirect(chaa, xx, yy);
|
|
else
|
|
MoveCharacter(chaa, xx, yy);
|
|
|
|
GameLoopUntilNotMoving(&_GP(game).chars[chaa].walking);
|
|
|
|
return -1; // replicates legacy engine effect
|
|
}
|
|
|
|
int GetCharacterSpeechAnimationDelay(CharacterInfo *cha) {
|
|
if ((_G(loaded_game_file_version) < kGameVersion_312) && (_GP(game).options[OPT_SPEECHTYPE] != 0)) {
|
|
// legacy versions of AGS assigned a fixed delay to Sierra-style speech only
|
|
return 5;
|
|
}
|
|
if (_GP(game).options[OPT_GLOBALTALKANIMSPD] != 0)
|
|
return _GP(play).talkanim_speed;
|
|
else
|
|
return cha->speech_anim_speed;
|
|
}
|
|
|
|
void RunCharacterInteraction(int cc, int mood) {
|
|
if (!is_valid_character(cc))
|
|
quit("!RunCharacterInteraction: invalid character");
|
|
|
|
// convert cursor mode to event index (in character event table)
|
|
// TODO: probably move this conversion table elsewhere? should be a global info
|
|
int evnt;
|
|
switch (mood) {
|
|
case MODE_LOOK: evnt = 0; break;
|
|
case MODE_HAND: evnt = 1; break;
|
|
case MODE_TALK: evnt = 2; break;
|
|
case MODE_USE: evnt = 3; break;
|
|
case MODE_PICKUP: evnt = 5; break;
|
|
case MODE_CUSTOM1: evnt = 6; break;
|
|
case MODE_CUSTOM2: evnt = 7; break;
|
|
default: evnt = -1; break;
|
|
}
|
|
const int anyclick_evt = 4; // TODO: make global constant (character any-click evt)
|
|
|
|
// For USE verb: remember active inventory
|
|
if (mood == MODE_USE) {
|
|
_GP(play).usedinv = _G(playerchar)->activeinv;
|
|
}
|
|
|
|
const auto obj_evt = ObjectEvent("character%d", cc,
|
|
RuntimeScriptValue().SetScriptObject(&_GP(game).chars[cc], &_GP(ccDynamicCharacter)), mood);
|
|
if (_G(loaded_game_file_version) > kGameVersion_272) {
|
|
if ((evnt >= 0) &&
|
|
run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), evnt, anyclick_evt) < 0)
|
|
return; // game state changed, don't do "any click"
|
|
run_interaction_script(obj_evt, _GP(game).charScripts[cc].get(), anyclick_evt); // any click on char
|
|
} else {
|
|
if ((evnt >= 0) &&
|
|
run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), evnt, anyclick_evt, (mood == MODE_USE)) < 0)
|
|
return; // game state changed, don't do "any click"
|
|
run_interaction_event(obj_evt, _GP(game).intrChar[cc].get(), anyclick_evt); // any click on char
|
|
}
|
|
}
|
|
|
|
int AreCharObjColliding(int charid, int objid) {
|
|
if (!is_valid_character(charid))
|
|
quit("!AreCharObjColliding: invalid character");
|
|
if (!is_valid_object(objid))
|
|
quit("!AreCharObjColliding: invalid object number");
|
|
|
|
return Character_IsCollidingWithObject(&_GP(game).chars[charid], &_G(scrObj)[objid]);
|
|
}
|
|
|
|
int AreCharactersColliding(int cchar1, int cchar2) {
|
|
if (!is_valid_character(cchar1))
|
|
quit("!AreCharactersColliding: invalid char1");
|
|
if (!is_valid_character(cchar2))
|
|
quit("!AreCharactersColliding: invalid char2");
|
|
|
|
return Character_IsCollidingWithChar(&_GP(game).chars[cchar1], &_GP(game).chars[cchar2]);
|
|
}
|
|
|
|
int GetCharacterProperty(int cha, const char *property) {
|
|
if (!is_valid_character(cha))
|
|
quit("!GetCharacterProperty: invalid character");
|
|
return get_int_property(_GP(game).charProps[cha], _GP(play).charProps[cha], property);
|
|
}
|
|
|
|
void SetCharacterProperty(int who, int flag, int yesorno) {
|
|
if (!is_valid_character(who))
|
|
quit("!SetCharacterProperty: Invalid character specified");
|
|
|
|
Character_SetOption(&_GP(game).chars[who], flag, yesorno);
|
|
}
|
|
|
|
void GetCharacterPropertyText(int item, const char *property, char *bufer) {
|
|
get_text_property(_GP(game).charProps[item], _GP(play).charProps[item], property, bufer);
|
|
}
|
|
|
|
int GetCharIDAtScreen(int xx, int yy) {
|
|
VpPoint vpt = _GP(play).ScreenToRoomDivDown(xx, yy);
|
|
if (vpt.second < 0)
|
|
return -1;
|
|
return is_pos_on_character(vpt.first.X, vpt.first.Y);
|
|
}
|
|
|
|
void SetActiveInventory(int iit) {
|
|
|
|
ScriptInvItem *tosend = nullptr;
|
|
if ((iit > 0) && (iit < _GP(game).numinvitems))
|
|
tosend = &_G(scrInv)[iit];
|
|
else if (iit != -1)
|
|
quitprintf("!SetActiveInventory: invalid inventory number %d", iit);
|
|
|
|
Character_SetActiveInventory(_G(playerchar), tosend);
|
|
}
|
|
|
|
void update_invorder() {
|
|
for (int cc = 0; cc < _GP(game).numcharacters; cc++) {
|
|
_GP(charextra)[cc].invorder_count = 0;
|
|
int ff, howmany;
|
|
// Iterate through all inv items, adding them once (or multiple
|
|
// times if requested) to the list.
|
|
for (ff = 0; ff < _GP(game).numinvitems; ff++) {
|
|
howmany = _GP(game).chars[cc].inv[ff];
|
|
if ((_GP(game).options[OPT_DUPLICATEINV] == 0) && (howmany > 1))
|
|
howmany = 1;
|
|
|
|
for (int ts = 0; ts < howmany; ts++) {
|
|
if (_GP(charextra)[cc].invorder_count >= MAX_INVORDER)
|
|
quit("!Too many inventory items to display: 500 max");
|
|
|
|
_GP(charextra)[cc].invorder[_GP(charextra)[cc].invorder_count] = ff;
|
|
_GP(charextra)[cc].invorder_count++;
|
|
}
|
|
}
|
|
}
|
|
// backwards compatibility
|
|
_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
|
|
GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
|
|
}
|
|
|
|
void add_inventory(int inum) {
|
|
if ((inum < 0) || (inum >= MAX_INV))
|
|
quit("!AddInventory: invalid inventory number");
|
|
|
|
Character_AddInventory(_G(playerchar), &_G(scrInv)[inum], SCR_NO_VALUE);
|
|
|
|
_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
|
|
}
|
|
|
|
void lose_inventory(int inum) {
|
|
if ((inum < 0) || (inum >= MAX_INV))
|
|
quit("!LoseInventory: invalid inventory number");
|
|
|
|
Character_LoseInventory(_G(playerchar), &_G(scrInv)[inum]);
|
|
|
|
_GP(play).inv_numorder = _GP(charextra)[_GP(game).playercharacter].invorder_count;
|
|
}
|
|
|
|
void AddInventoryToCharacter(int charid, int inum) {
|
|
if (!is_valid_character(charid))
|
|
quit("!AddInventoryToCharacter: invalid character specified");
|
|
if ((inum < 1) || (inum >= _GP(game).numinvitems))
|
|
quit("!AddInventory: invalid inv item specified");
|
|
|
|
Character_AddInventory(&_GP(game).chars[charid], &_G(scrInv)[inum], SCR_NO_VALUE);
|
|
}
|
|
|
|
void LoseInventoryFromCharacter(int charid, int inum) {
|
|
if (!is_valid_character(charid))
|
|
quit("!LoseInventoryFromCharacter: invalid character specified");
|
|
if ((inum < 1) || (inum >= _GP(game).numinvitems))
|
|
quit("!AddInventory: invalid inv item specified");
|
|
|
|
Character_LoseInventory(&_GP(game).chars[charid], &_G(scrInv)[inum]);
|
|
}
|
|
|
|
void DisplayThought(int chid, const char *text) {
|
|
if ((chid < 0) || (chid >= _GP(game).numcharacters))
|
|
quit("!DisplayThought: invalid character specified");
|
|
|
|
_DisplayThoughtCore(chid, text);
|
|
}
|
|
|
|
void __sc_displayspeech(int chid, const char *text) {
|
|
if ((chid < 0) || (chid >= _GP(game).numcharacters))
|
|
quit("!DisplaySpeech: invalid character specified");
|
|
|
|
_DisplaySpeechCore(chid, text);
|
|
}
|
|
|
|
// **** THIS IS UNDOCUMENTED BECAUSE IT DOESN'T WORK PROPERLY
|
|
// **** AT 640x400 AND DOESN'T USE THE RIGHT SPEECH STYLE
|
|
void DisplaySpeechAt(int xx, int yy, int wii, int aschar, const char *spch) {
|
|
data_to_game_coords(&xx, &yy);
|
|
wii = data_to_game_coord(wii);
|
|
_displayspeech(get_translation(spch), aschar, xx, yy, wii, 0);
|
|
}
|
|
|
|
int DisplaySpeechBackground(int charid, const char *speel) {
|
|
// remove any previous background speech for this character
|
|
// TODO: have a map character -> bg speech over?
|
|
const auto &overs = get_overlays();
|
|
for (size_t i = 0; i < overs.size(); ++i) {
|
|
if (overs[i].bgSpeechForChar == charid) {
|
|
remove_screen_overlay(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ovrl = CreateTextOverlay(OVR_AUTOPLACE, charid, _GP(play).GetUIViewport().GetWidth() / 2, FONT_SPEECH,
|
|
-_GP(game).chars[charid].talkcolor, get_translation(speel), DISPLAYTEXT_NORMALOVERLAY);
|
|
|
|
auto *over = get_overlay(ovrl);
|
|
over->bgSpeechForChar = charid;
|
|
over->timeout = GetTextDisplayTime(speel, 1);
|
|
return ovrl;
|
|
}
|
|
|
|
} // namespace AGS3
|