Files
scummvm-cursorfix/engines/ags/engine/ac/character.cpp
2026-02-02 04:50:13 +01:00

3837 lines
136 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/character.h"
#include "ags/shared/ac/common.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/shared/ac/view.h"
#include "ags/engine/ac/display.h"
#include "ags/engine/ac/draw.h"
#include "ags/engine/ac/event.h"
#include "ags/engine/ac/game.h"
#include "ags/engine/ac/global_audio.h"
#include "ags/engine/ac/global_character.h"
#include "ags/engine/ac/global_game.h"
#include "ags/engine/ac/global_object.h"
#include "ags/engine/ac/global_region.h"
#include "ags/engine/ac/global_room.h"
#include "ags/engine/ac/global_translation.h"
#include "ags/engine/ac/gui.h"
#include "ags/engine/ac/lip_sync.h"
#include "ags/engine/ac/mouse.h"
#include "ags/engine/ac/object.h"
#include "ags/engine/ac/overlay.h"
#include "ags/engine/ac/properties.h"
#include "ags/engine/ac/room.h"
#include "ags/engine/ac/screen_overlay.h"
#include "ags/engine/ac/string.h"
#include "ags/engine/ac/system.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/engine/ac/walkable_area.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/engine/ac/route_finder.h"
#include "ags/engine/ac/game_state.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/engine/main/game_run.h"
#include "ags/engine/main/update.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/shared/util/string_compat.h"
#include "ags/engine/gfx/graphics_driver.h"
#include "ags/engine/script/runtime_script_value.h"
#include "ags/engine/ac/dynobj/cc_character.h"
#include "ags/engine/ac/dynobj/cc_inventory.h"
#include "ags/engine/ac/dynobj/dynobj_manager.h"
#include "ags/engine/script/script_runtime.h"
#include "ags/shared/gfx/gfx_def.h"
#include "ags/engine/media/audio/audio_system.h"
#include "ags/engine/ac/move_list.h"
#include "ags/shared/debugging/out.h"
#include "ags/engine/script/script_api.h"
#include "ags/engine/script/script_runtime.h"
#include "ags/engine/ac/dynobj/script_string.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
bool is_valid_character(int char_id) {
return ((char_id >= 0) && (char_id < _GP(game).numcharacters));
}
// Checks if character is currently playing idle anim, and reset it
static void stop_character_idling(CharacterInfo *chi) {
if (chi->idleleft < 0) {
Character_UnlockView(chi);
chi->idleleft = chi->idletime;
}
}
bool AssertCharacter(const char *apiname, int char_id) {
if ((char_id >= 0) && (char_id < _GP(game).numcharacters))
return true;
debug_script_warn("%s: invalid character id %d (range is 0..%d)", apiname, char_id, _GP(game).numcharacters - 1);
return false;
}
void Character_AddInventory(CharacterInfo *chaa, ScriptInvItem *invi, int addIndex) {
int ee;
if (invi == nullptr)
quit("!AddInventoryToCharacter: invalid inventory number");
int inum = invi->id;
if (chaa->inv[inum] >= 32000)
quit("!AddInventory: cannot carry more than 32000 of one inventory item");
chaa->inv[inum]++;
int charid = chaa->index_id;
if (_GP(game).options[OPT_DUPLICATEINV] == 0) {
// Ensure it is only in the list once
for (ee = 0; ee < _GP(charextra)[charid].invorder_count; ee++) {
if (_GP(charextra)[charid].invorder[ee] == inum) {
// They already have the item, so don't add it to the list
if (chaa == _G(playerchar))
run_on_event(GE_ADD_INV, RuntimeScriptValue().SetInt32(inum));
return;
}
}
}
if (_GP(charextra)[charid].invorder_count >= MAX_INVORDER)
quit("!Too many inventory items added, max 500 display at one time");
if ((addIndex == SCR_NO_VALUE) ||
(addIndex >= _GP(charextra)[charid].invorder_count) ||
(addIndex < 0)) {
// add new item at end of list
_GP(charextra)[charid].invorder[_GP(charextra)[charid].invorder_count] = inum;
} else {
// insert new item at index
for (ee = _GP(charextra)[charid].invorder_count - 1; ee >= addIndex; ee--)
_GP(charextra)[charid].invorder[ee + 1] = _GP(charextra)[charid].invorder[ee];
_GP(charextra)[charid].invorder[addIndex] = inum;
}
_GP(charextra)[charid].invorder_count++;
GUI::MarkInventoryForUpdate(charid, charid == _GP(game).playercharacter);
if (chaa == _G(playerchar))
run_on_event(GE_ADD_INV, RuntimeScriptValue().SetInt32(inum));
}
void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) {
if (chaa->room != _G(displayed_room))
quitprintf("!MoveCharacterPath: character %s is not in current room %d (it is in room %d)",
chaa->scrname, _G(displayed_room), chaa->room);
// not already walking, so just do a normal move
if (chaa->walking <= 0) {
Character_Walk(chaa, x, y, IN_BACKGROUND, ANYWHERE);
return;
}
MoveList *cmls = &_GP(mls)[chaa->walking % TURNING_AROUND];
if (cmls->numstage >= MAXNEEDSTAGES) {
debug_script_warn("Character::AddWaypoint: move is too complex, cannot add any further paths");
return;
}
// They're already walking there anyway
const Point &last_pos = cmls->GetLastPos();
if (last_pos == Point(x, y))
return;
int move_speed_x, move_speed_y;
chaa->get_effective_walkspeeds(move_speed_x, move_speed_y);
if ((move_speed_x == 0) && (move_speed_y == 0)) {
debug_script_warn("Character::AddWaypoint: called for '%s' with walk speed 0", chaa->scrname);
}
// There's an issue: the existing movelist is converted to room resolution,
// so we do this trick: convert last step to mask resolution, before calling
// a pathfinder api, and then we'll convert old and new last step back.
// TODO: figure out a better way of processing this!
const int last_stage = cmls->numstage - 1;
cmls->pos[last_stage] = { room_to_mask_coord(cmls->pos[last_stage].X), room_to_mask_coord(cmls->pos[last_stage].Y) };
const int dst_x = room_to_mask_coord(x);
const int dst_y = room_to_mask_coord(y);
if(add_waypoint_direct(cmls, dst_x, dst_y, move_speed_x, move_speed_y))
convert_move_path_to_room_resolution(cmls, last_stage, last_stage + 1);
}
void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat,
int blocking, int direction, int sframe, int volume) {
// If idle view in progress for the character, stop the idle anim;
// do this prior to the loop check, as the view may switch back to defview here
stop_character_idling(chaa);
ValidateViewAnimVLF("Character.Animate", chaa->view, loop, sframe);
ValidateViewAnimParams("Character.Animate", repeat, blocking, direction);
animate_character(chaa, loop, delay, repeat, direction, sframe, volume);
if (blocking != 0)
GameLoopUntilValueIsZero(&chaa->animating);
}
void Character_Animate5(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) {
Character_Animate(chaa, loop, delay, repeat, blocking, direction, 0 /* first frame */, 100 /* full volume */);
}
void Character_Animate6(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction, int sframe) {
Character_Animate(chaa, loop, delay, repeat, blocking, direction, sframe, 100 /* full volume */);
}
void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos) {
if (chaa->index_id != _GP(game).playercharacter) {
quit("!Character.ChangeRoomAutoPosition can only be used with the player character.");
}
_G(new_room_pos) = newPos;
if (_G(new_room_pos) == 0) {
// auto place on other side of screen
if (chaa->x <= _GP(thisroom).Edges.Left + 10)
_G(new_room_pos) = 2000;
else if (chaa->x >= _GP(thisroom).Edges.Right - 10)
_G(new_room_pos) = 1000;
else if (chaa->y <= _GP(thisroom).Edges.Top + 10)
_G(new_room_pos) = 3000;
else if (chaa->y >= _GP(thisroom).Edges.Bottom - 10)
_G(new_room_pos) = 4000;
if (_G(new_room_pos) < 3000)
_G(new_room_pos) += chaa->y;
else
_G(new_room_pos) += chaa->x;
}
NewRoom(room);
}
void Character_ChangeRoom(CharacterInfo *chaa, int room, int x, int y) {
Character_ChangeRoomSetLoop(chaa, room, x, y, SCR_NO_VALUE);
}
void Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, int direction) {
if (chaa->index_id != _GP(game).playercharacter) {
// NewRoomNPC
if ((x != SCR_NO_VALUE) && (y != SCR_NO_VALUE)) {
chaa->x = x;
chaa->y = y;
if (direction != SCR_NO_VALUE && direction >= 0) chaa->loop = direction;
}
chaa->prevroom = chaa->room;
chaa->room = room;
debug_script_log("%s moved to room %d, location %d,%d, loop %d",
chaa->scrname, room, chaa->x, chaa->y, chaa->loop);
return;
}
if ((x != SCR_NO_VALUE) && (y != SCR_NO_VALUE)) {
// We cannot set character position right away,
// because room switch will occur only after the script end,
// and character position may be still changing meanwhile.
_G(new_room_pos) = 0;
// don't check X or Y bounds, so that they can do a
// walk-in animation if they want
_G(new_room_x) = x;
_G(new_room_y) = y;
if (direction != SCR_NO_VALUE)
_G(new_room_loop) = direction;
}
NewRoom(room);
}
void Character_ChangeView(CharacterInfo *chap, int vii) {
vii--;
if ((vii < 0) || (vii >= _GP(game).numviews))
quit("!ChangeCharacterView: invalid view number specified");
// if animating, but not idle view, give warning message
if ((chap->flags & CHF_FIXVIEW) && (chap->idleleft >= 0))
debug_script_warn("Warning: ChangeCharacterView was used while the view was fixed - call ReleaseCharView first");
// if the idle animation is playing we should release the view
stop_character_idling(chap);
debug_script_log("%s: Change view to %d", chap->scrname, vii + 1);
chap->defview = vii;
chap->view = vii;
stop_character_anim(chap);
chap->frame = 0;
chap->wait = 0;
chap->walkwait = 0;
_GP(charextra)[chap->index_id].animwait = 0;
FindReasonableLoopForCharacter(chap);
}
enum DirectionalLoop {
kDirLoop_Down = 0,
kDirLoop_Left = 1,
kDirLoop_Right = 2,
kDirLoop_Up = 3,
kDirLoop_DownRight = 4,
kDirLoop_UpRight = 5,
kDirLoop_DownLeft = 6,
kDirLoop_UpLeft = 7,
kDirLoop_Default = kDirLoop_Down,
kDirLoop_LastOrthogonal = kDirLoop_Up,
kDirLoop_Last = kDirLoop_UpLeft,
};
// Internal direction-facing functions
DirectionalLoop GetDirectionalLoop(CharacterInfo *chinfo, int x_diff, int y_diff) {
DirectionalLoop next_loop = kDirLoop_Left; // NOTE: default loop was Left for some reason
const ViewStruct &chview = _GP(views)[chinfo->view];
const bool new_version = _G(loaded_game_file_version) > kGameVersion_272;
const bool has_down_loop = ((chview.numLoops > kDirLoop_Down) && (chview.loops[kDirLoop_Down].numFrames > 0));
const bool has_up_loop = ((chview.numLoops > kDirLoop_Up) && (chview.loops[kDirLoop_Up].numFrames > 0));
// NOTE: 3.+ games required left & right loops to be present at all times
const bool has_left_loop = new_version ||
((chview.numLoops > kDirLoop_Left) && (chview.loops[kDirLoop_Left].numFrames > 0));
const bool has_right_loop = new_version ||
((chview.numLoops > kDirLoop_Right) && (chview.loops[kDirLoop_Right].numFrames > 0));
const bool has_diagonal_loops = useDiagonal(chinfo) == 0; // NOTE: useDiagonal returns 0 for "true"
const bool want_horizontal = (abs(y_diff) < abs(x_diff)) ||
(new_version && (!has_down_loop || !has_up_loop)) ||
// NOTE: <= 2.72 games switch to horizontal loops only if both vertical ones are missing
(!new_version && (!has_down_loop && !has_up_loop));
if (want_horizontal) {
const bool want_diagonal = has_diagonal_loops && (abs(y_diff) > abs(x_diff) / 2);
if (!has_left_loop && !has_right_loop) {
next_loop = kDirLoop_Down;
} else if (has_right_loop && (x_diff > 0)) {
next_loop = want_diagonal ? (y_diff < 0 ? kDirLoop_UpRight : kDirLoop_DownRight) :
kDirLoop_Right;
} else if (has_left_loop && (x_diff <= 0)) {
next_loop = want_diagonal ? (y_diff < 0 ? kDirLoop_UpLeft : kDirLoop_DownLeft) :
kDirLoop_Left;
}
} else {
const bool want_diagonal = has_diagonal_loops && (abs(x_diff) > abs(y_diff) / 2);
if (y_diff > 0 || !has_up_loop) {
next_loop = want_diagonal ? (x_diff < 0 ? kDirLoop_DownLeft : kDirLoop_DownRight) :
kDirLoop_Down;
} else {
next_loop = want_diagonal ? (x_diff < 0 ? kDirLoop_UpLeft : kDirLoop_UpRight) :
kDirLoop_Up;
}
}
return next_loop;
}
void FaceDirectionalLoop(CharacterInfo *char1, int direction, int blockingStyle) {
// Change facing only if the desired direction is different
if (direction != char1->loop) {
if ((_GP(game).options[OPT_TURNTOFACELOC] != 0) &&
(_G(in_enters_screen) == 0)) {
const int no_diagonal = useDiagonal(char1);
const int highestLoopForTurning = no_diagonal != 1 ? kDirLoop_Last : kDirLoop_LastOrthogonal;
if ((char1->loop <= highestLoopForTurning)) {
// Turn to face new direction
Character_StopMoving(char1);
if (char1->on == 1) {
// only do the turning if the character is not hidden
// (otherwise GameLoopUntilNotMoving will never return)
start_character_turning(char1, direction, no_diagonal);
if ((blockingStyle == BLOCKING) || (blockingStyle == 1))
GameLoopUntilNotMoving(&char1->walking);
} else
char1->loop = direction;
} else
char1->loop = direction;
} else
char1->loop = direction;
}
char1->frame = 0;
}
void FaceLocationXY(CharacterInfo *char1, int xx, int yy, int blockingStyle) {
debug_script_log("%s: Face location %d,%d", char1->scrname, xx, yy);
const int diffrx = xx - char1->x;
const int diffry = yy - char1->y;
if ((diffrx == 0) && (diffry == 0)) {
// FaceLocation called on their current position - do nothing
return;
}
FaceDirectionalLoop(char1, GetDirectionalLoop(char1, diffrx, diffry), blockingStyle);
}
// External direction-facing functions with validation
void Character_FaceDirection(CharacterInfo *char1, int direction, int blockingStyle) {
if (char1 == nullptr)
quit("!FaceDirection: invalid character specified");
if (direction != SCR_NO_VALUE) {
if (direction < 0 || direction > kDirLoop_Last)
quit("!FaceDirection: invalid direction specified");
FaceDirectionalLoop(char1, direction, blockingStyle);
}
}
void Character_FaceLocation(CharacterInfo *char1, int xx, int yy, int blockingStyle) {
if (char1 == nullptr)
quit("!FaceLocation: invalid character specified");
FaceLocationXY(char1, xx, yy, blockingStyle);
}
void Character_FaceObject(CharacterInfo *char1, ScriptObject *obj, int blockingStyle) {
if (obj == nullptr)
quit("!FaceObject: invalid object specified");
FaceLocationXY(char1, _G(objs)[obj->id].x, _G(objs)[obj->id].y, blockingStyle);
}
void Character_FaceCharacter(CharacterInfo *char1, CharacterInfo *char2, int blockingStyle) {
if (char2 == nullptr)
quit("!FaceCharacter: invalid character specified");
if (char1->room != char2->room)
quitprintf("!FaceCharacter: characters %s and %s are in different rooms (room %d and room %d respectively)",
char1->scrname, char2->scrname, char1->room, char2->room);
FaceLocationXY(char1, char2->x, char2->y, blockingStyle);
}
void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int distaway, int eagerness) {
if ((eagerness < 0) || (eagerness > 250))
quit("!FollowCharacterEx: invalid eagerness: must be 0-250");
if ((chaa->index_id == _GP(game).playercharacter) && (tofollow != nullptr) &&
(tofollow->room != chaa->room))
quitprintf("!FollowCharacterEx: you cannot tell the player character %s, who is in room %d, to follow a character %s who is in another room %d",
chaa->scrname, chaa->room, tofollow->scrname, tofollow->room);
if (tofollow != nullptr) {
debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname, tofollow->scrname, distaway, eagerness);
} else {
debug_script_log("%s: Stop following other character", chaa->scrname);
}
if ((chaa->following >= 0) &&
(chaa->followinfo == FOLLOW_ALWAYSONTOP)) {
// if this character was following always-on-top, its baseline will
// have been changed, so release it.
chaa->baseline = -1;
}
if (tofollow == nullptr)
chaa->following = -1;
else
chaa->following = tofollow->index_id;
chaa->followinfo = (distaway << 8) | eagerness;
chaa->flags &= ~CHF_BEHINDSHEPHERD;
// special case for Always On Other Character
if (distaway == FOLLOW_ALWAYSONTOP) {
chaa->followinfo = FOLLOW_ALWAYSONTOP;
if (eagerness == 1)
chaa->flags |= CHF_BEHINDSHEPHERD;
}
if (chaa->animating & CHANIM_REPEAT)
debug_script_warn("Warning: FollowCharacter called but the sheep is currently animating looped. It may never start to follow.");
}
int Character_IsCollidingWithChar(CharacterInfo *char1, CharacterInfo *char2) {
if (char2 == nullptr)
quit("!AreCharactersColliding: invalid char2");
if (char1->room != char2->room)
return 0; // not colliding
if ((char1->y > char2->y - 5) && (char1->y < char2->y + 5))
;
else
return 0;
int w1 = game_to_data_coord(GetCharacterWidth(char1->index_id));
int w2 = game_to_data_coord(GetCharacterWidth(char2->index_id));
int xps1 = char1->x - w1 / 2;
int xps2 = char2->x - w2 / 2;
if ((xps1 >= xps2 - w1) && (xps1 <= xps2 + w2))
return 1;
return 0;
}
int Character_IsCollidingWithObject(CharacterInfo *chin, ScriptObject *objid) {
if (objid == nullptr)
quit("!AreCharObjColliding: invalid object number");
if (chin->room != _G(displayed_room))
return 0;
if (_G(objs)[objid->id].on != 1)
return 0;
Bitmap *checkblk = GetObjectImage(objid->id);
int objWidth = checkblk->GetWidth();
int objHeight = checkblk->GetHeight();
int o1x = _G(objs)[objid->id].x;
int o1y = _G(objs)[objid->id].y - game_to_data_coord(objHeight);
Bitmap *charpic = GetCharacterImage(chin->index_id);
int charWidth = charpic->GetWidth();
int charHeight = charpic->GetHeight();
int o2x = chin->x - game_to_data_coord(charWidth) / 2;
int o2y = _GP(charextra)[chin->index_id].GetEffectiveY(chin) - 5; // only check feet
if ((o2x >= o1x - game_to_data_coord(charWidth)) &&
(o2x <= o1x + game_to_data_coord(objWidth)) &&
(o2y >= o1y - 8) &&
(o2y <= o1y + game_to_data_coord(objHeight))) {
// the character's feet are on the object
if (_GP(game).options[OPT_PIXPERFECT] == 0)
return 1;
// check if they're on a transparent bit of the object
int stxp = data_to_game_coord(o2x - o1x);
int styp = data_to_game_coord(o2y - o1y);
int maskcol = checkblk->GetMaskColor();
int maskcolc = charpic->GetMaskColor();
int thispix, thispixc;
// check each pixel of the object along the char's feet
for (int i = 0; i < charWidth; i += get_fixed_pixel_size(1)) {
for (int j = 0; j < get_fixed_pixel_size(6); j += get_fixed_pixel_size(1)) {
thispix = my_getpixel(checkblk, i + stxp, j + styp);
thispixc = my_getpixel(charpic, i, j + (charHeight - get_fixed_pixel_size(5)));
if ((thispix != -1) && (thispix != maskcol) &&
(thispixc != -1) && (thispixc != maskcolc))
return 1;
}
}
}
return 0;
}
bool Character_IsInteractionAvailable(CharacterInfo *cchar, int mood) {
_GP(play).check_interaction_only = 1;
RunCharacterInteraction(cchar->index_id, mood);
int ciwas = _GP(play).check_interaction_only;
_GP(play).check_interaction_only = 0;
return (ciwas == 2);
}
void Character_LockView(CharacterInfo *chap, int vii) {
Character_LockViewEx(chap, vii, STOP_MOVING);
}
void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) {
vii--; // convert to 0-based
AssertView("SetCharacterView", vii);
stop_character_idling(chap);
if (stopMoving != KEEP_MOVING) {
Character_StopMoving(chap);
}
chap->view = vii;
stop_character_anim(chap);
FindReasonableLoopForCharacter(chap);
chap->frame = 0;
chap->wait = 0;
chap->flags |= CHF_FIXVIEW;
chap->pic_xoffs = 0;
chap->pic_yoffs = 0;
debug_script_log("%s: View locked to %d", chap->scrname, vii + 1);
}
void Character_LockViewAligned_Old(CharacterInfo *chap, int vii, int loop, int align) {
Character_LockViewAlignedEx(chap, vii, loop, ConvertLegacyScriptAlignment((LegacyScriptAlignment)align), STOP_MOVING);
}
void Character_LockViewAlignedEx_Old(CharacterInfo *chap, int vii, int loop, int align, int stopMoving) {
Character_LockViewAlignedEx(chap, vii, loop, ConvertLegacyScriptAlignment((LegacyScriptAlignment)align), stopMoving);
}
void Character_LockViewAligned(CharacterInfo *chap, int vii, int loop, int align) {
Character_LockViewAlignedEx(chap, vii, loop, align, STOP_MOVING);
}
void Character_LockViewAlignedEx(CharacterInfo *chap, int vii, int loop, int align, int stopMoving) {
if (chap->view < 0)
quit("!SetCharacterLoop: character has invalid old view number");
int sppic = _GP(views)[chap->view].loops[chap->loop].frames[chap->frame].pic;
int leftSide = data_to_game_coord(chap->x) - _GP(game).SpriteInfos[sppic].Width / 2;
Character_LockViewEx(chap, vii, stopMoving);
AssertLoop("SetCharacterViewEx", chap->view, loop);
chap->loop = loop;
chap->frame = 0;
int newpic = _GP(views)[chap->view].loops[chap->loop].frames[chap->frame].pic;
int newLeft = data_to_game_coord(chap->x) - _GP(game).SpriteInfos[newpic].Width / 2;
int xdiff = 0;
if (align & kMAlignLeft)
xdiff = leftSide - newLeft;
else if (align & kMAlignHCenter)
xdiff = 0;
else if (align & kMAlignRight)
xdiff = (leftSide + _GP(game).SpriteInfos[sppic].Width) - (newLeft + _GP(game).SpriteInfos[newpic].Width);
else
quit("!SetCharacterViewEx: invalid alignment type specified");
chap->pic_xoffs = xdiff;
chap->pic_yoffs = 0;
}
void Character_LockViewFrame(CharacterInfo *chaa, int view, int loop, int frame) {
Character_LockViewFrameEx(chaa, view, loop, frame, STOP_MOVING);
}
void Character_LockViewFrameEx(CharacterInfo *chaa, int view, int loop, int frame, int stopMoving) {
Character_LockViewEx(chaa, view, stopMoving);
AssertFrame("SetCharacterFrame", view - 1, loop, frame);
chaa->loop = loop;
chaa->frame = frame;
}
void Character_LockViewOffset(CharacterInfo *chap, int vii, int xoffs, int yoffs) {
Character_LockViewOffsetEx(chap, vii, xoffs, yoffs, STOP_MOVING);
}
void Character_LockViewOffsetEx(CharacterInfo *chap, int vii, int xoffs, int yoffs, int stopMoving) {
Character_LockViewEx(chap, vii, stopMoving);
// This function takes offsets in real game coordinates as opposed to script coordinates
defgame_to_finalgame_coords(xoffs, yoffs);
chap->pic_xoffs = xoffs;
chap->pic_yoffs = yoffs;
}
void Character_LoseInventory(CharacterInfo *chap, ScriptInvItem *invi) {
if (invi == nullptr)
quit("!LoseInventoryFromCharacter: invalid inventory number");
int inum = invi->id;
if (chap->inv[inum] > 0)
chap->inv[inum]--;
if ((chap->activeinv == inum) && (chap->inv[inum] < 1)) {
chap->activeinv = -1;
if ((chap == _G(playerchar)) && (GetCursorMode() == MODE_USE))
set_cursor_mode(0);
}
int charid = chap->index_id;
if ((chap->inv[inum] == 0) || (_GP(game).options[OPT_DUPLICATEINV] > 0)) {
int xx, tt;
for (xx = 0; xx < _GP(charextra)[charid].invorder_count; xx++) {
if (_GP(charextra)[charid].invorder[xx] == inum) {
_GP(charextra)[charid].invorder_count--;
for (tt = xx; tt < _GP(charextra)[charid].invorder_count; tt++)
_GP(charextra)[charid].invorder[tt] = _GP(charextra)[charid].invorder[tt + 1];
break;
}
}
}
GUI::MarkInventoryForUpdate(charid, charid == _GP(game).playercharacter);
if (chap == _G(playerchar))
run_on_event(GE_LOSE_INV, RuntimeScriptValue().SetInt32(inum));
}
void Character_PlaceOnWalkableArea(CharacterInfo *chap) {
if (_G(displayed_room) < 0)
quit("!Character.PlaceOnWalkableArea: no room is currently loaded");
find_nearest_walkable_area(&chap->x, &chap->y);
}
void Character_RemoveTint(CharacterInfo *chaa) {
if (chaa->flags & (CHF_HASTINT | CHF_HASLIGHT)) {
debug_script_log("Un-tint %s", chaa->scrname);
chaa->flags &= ~(CHF_HASTINT | CHF_HASLIGHT);
} else {
debug_script_warn("Character.RemoveTint called but character was not tinted");
}
}
int Character_GetHasExplicitTint_Old(CharacterInfo *ch) {
return ch->has_explicit_tint() || ch->has_explicit_light();
}
int Character_GetHasExplicitTint(CharacterInfo *ch) {
return ch->has_explicit_tint();
}
void Character_Say(CharacterInfo *chaa, const char *text) {
_DisplaySpeechCore(chaa->index_id, text);
}
void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx) {
DisplaySpeechAt(x, y, width, chaa->index_id, texx);
}
ScriptOverlay *Character_SayBackground(CharacterInfo *chaa, const char *texx) {
int ovltype = DisplaySpeechBackground(chaa->index_id, texx);
auto *over = get_overlay(ovltype);
if (!over)
quit("!SayBackground internal error: no overlay");
// Create script object with an internal ref, keep at least until internal timeout
return create_scriptoverlay(*over, true);
}
void Character_SetAsPlayer(CharacterInfo *chaa) {
// Set to same character, so ignore.
// But only on versions > 2.61. The relevant entry in the 2.62 changelog is:
// - Fixed SetPlayerCharacter to do nothing at all if you pass the current
// player character to it (previously it was resetting the inventory layout)
if ((_G(loaded_game_file_version) > kGameVersion_261) && (_GP(game).playercharacter == chaa->index_id))
return;
setup_player_character(chaa->index_id);
GUI::MarkInventoryForUpdate(_GP(game).playercharacter, true);
debug_script_log("%s is new player character", _G(playerchar)->scrname);
// Within game_start, return now
if (_G(displayed_room) < 0)
return;
// Ignore invalid room numbers for the character and just place him in
// the current room for 2.x. Following script calls to NewRoom() will
// make sure this still works as intended.
if ((_G(loaded_game_file_version) <= kGameVersion_272) && (_G(playerchar)->room < 0))
_G(playerchar)->room = _G(displayed_room);
if (_G(displayed_room) != _G(playerchar)->room)
NewRoom(_G(playerchar)->room);
else // make sure it doesn't run the region interactions
_GP(play).player_on_region = GetRegionIDAtRoom(_G(playerchar)->x, _G(playerchar)->y);
if ((_G(playerchar)->activeinv >= 0) && (_G(playerchar)->inv[_G(playerchar)->activeinv] < 1))
_G(playerchar)->activeinv = -1;
// They had inv selected, so change the cursor
if (_G(cur_mode) == MODE_USE) {
if (_G(playerchar)->activeinv < 0)
SetNextCursor();
else
SetActiveInventory(_G(playerchar)->activeinv);
}
}
void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime) {
if (iview == 1)
quit("!SetCharacterIdle: view 1 cannot be used as an idle view, sorry.");
// if an idle anim is currently playing, release it
stop_character_idling(chaa);
chaa->idleview = iview - 1;
// make sure they don't appear idle while idle anim is disabled
if (iview < 1)
itime = 10;
chaa->idletime = itime;
chaa->idleleft = itime;
// if not currently animating, reset the wait counter
if ((chaa->animating == 0) && (chaa->walking == 0))
chaa->wait = 0;
if (iview >= 1) {
debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname, iview, itime);
} else {
debug_script_log("%s idle view disabled", chaa->scrname);
}
if (chaa->flags & CHF_FIXVIEW) {
debug_script_warn("SetCharacterIdle called while character view locked with SetCharacterView; idle ignored");
debug_script_log("View locked, idle will not kick in until Released");
}
// if they switch to a swimming animation, kick it off immediately
if (itime == 0)
_GP(charextra)[chaa->index_id].process_idle_this_time = 1;
}
bool Character_GetHasExplicitLight(CharacterInfo *ch) {
return ch->has_explicit_light();
}
int Character_GetLightLevel(CharacterInfo *ch) {
return ch->has_explicit_light() ? _GP(charextra)[ch->index_id].tint_light : 0;
}
void Character_SetLightLevel(CharacterInfo *chaa, int light_level) {
light_level = Math::Clamp(light_level, -100, 100);
_GP(charextra)[chaa->index_id].tint_light = light_level;
chaa->flags &= ~CHF_HASTINT;
chaa->flags |= CHF_HASLIGHT;
}
int Character_GetTintRed(CharacterInfo *ch) {
return ch->has_explicit_tint() ? _GP(charextra)[ch->index_id].tint_r : 0;
}
int Character_GetTintGreen(CharacterInfo *ch) {
return ch->has_explicit_tint() ? _GP(charextra)[ch->index_id].tint_g : 0;
}
int Character_GetTintBlue(CharacterInfo *ch) {
return ch->has_explicit_tint() ? _GP(charextra)[ch->index_id].tint_b : 0;
}
int Character_GetTintSaturation(CharacterInfo *ch) {
return ch->has_explicit_tint() ? _GP(charextra)[ch->index_id].tint_level : 0;
}
int Character_GetTintLuminance(CharacterInfo *ch) {
return ch->has_explicit_tint() ? ((_GP(charextra)[ch->index_id].tint_light * 10) / 25) : 0;
}
void Character_SetOption(CharacterInfo *chaa, int flag, int yesorno) {
if ((yesorno < 0) || (yesorno > 1))
quit("!SetCharacterProperty: last parameter must be 0 or 1");
if (flag & CHF_MANUALSCALING) {
// backwards compatibility fix
Character_SetIgnoreScaling(chaa, yesorno);
} else {
chaa->flags &= ~flag;
if (yesorno)
chaa->flags |= flag;
}
}
void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed) {
if ((xspeed == 0) || (yspeed == 0))
quit("!SetCharacterSpeedEx: invalid speed value");
if ((chaa->walking > 0) && (_G(loaded_game_file_version) < kGameVersion_361)) {
debug_script_warn("Character_SetSpeed: cannot change speed while walking");
return;
}
xspeed = Math::Clamp(xspeed, (int)INT16_MIN, (int)INT16_MAX);
yspeed = Math::Clamp(yspeed, (int)INT16_MIN, (int)INT16_MAX);
uint16_t old_speedx = chaa->walkspeed;
uint16_t old_speedy = ((chaa->walkspeed_y == UNIFORM_WALK_SPEED) ? chaa->walkspeed : chaa->walkspeed_y);
chaa->walkspeed = xspeed;
if (yspeed == xspeed)
chaa->walkspeed_y = UNIFORM_WALK_SPEED;
else
chaa->walkspeed_y = yspeed;
if (chaa->walking > 0 && (old_speedx != xspeed || old_speedy != yspeed)) {
recalculate_move_speeds(&_GP(mls)[chaa->walking % TURNING_AROUND], old_speedx, old_speedy, xspeed, yspeed);
}
}
void Character_StopMoving(CharacterInfo *charp) {
int chaa = charp->index_id;
if (chaa == _GP(play).skip_until_char_stops)
EndSkippingUntilCharStops();
if (_GP(charextra)[chaa].xwas != INVALID_X) {
charp->x = _GP(charextra)[chaa].xwas;
charp->y = _GP(charextra)[chaa].ywas;
_GP(charextra)[chaa].xwas = INVALID_X;
}
if ((charp->walking > 0) && (charp->walking < TURNING_AROUND)) {
// if it's not a MoveCharDirect, make sure they end up on a walkable area
if ((_GP(mls)[charp->walking].direct == 0) && (charp->room == _G(displayed_room)))
Character_PlaceOnWalkableArea(charp);
debug_script_log("%s: stop moving", charp->scrname);
charp->idleleft = charp->idletime;
// restart the idle animation straight away
_GP(charextra)[chaa].process_idle_this_time = 1;
}
if (charp->walking) {
// If the character is currently moving, stop them and reset their frame
charp->walking = 0;
if ((charp->flags & CHF_MOVENOTWALK) == 0)
charp->frame = 0;
}
}
void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opacity, int luminance) {
if ((red < 0) || (green < 0) || (blue < 0) ||
(red > 255) || (green > 255) || (blue > 255) ||
(opacity < 0) || (opacity > 100) ||
(luminance < 0) || (luminance > 100))
quit("!Character.Tint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100");
debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname, red, green, blue, opacity);
_GP(charextra)[chaa->index_id].tint_r = red;
_GP(charextra)[chaa->index_id].tint_g = green;
_GP(charextra)[chaa->index_id].tint_b = blue;
_GP(charextra)[chaa->index_id].tint_level = opacity;
_GP(charextra)[chaa->index_id].tint_light = (luminance * 25) / 10;
chaa->flags &= ~CHF_HASLIGHT;
chaa->flags |= CHF_HASTINT;
}
void Character_Think(CharacterInfo *chaa, const char *text) {
_DisplayThoughtCore(chaa->index_id, text);
}
void Character_UnlockView(CharacterInfo *chaa) {
Character_UnlockViewEx(chaa, STOP_MOVING);
}
void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) {
if (chaa->flags & CHF_FIXVIEW) {
debug_script_log("%s: Released view back to default", chaa->scrname);
}
chaa->flags &= ~CHF_FIXVIEW;
chaa->view = chaa->defview;
chaa->frame = 0;
if (stopMoving != KEEP_MOVING) {
Character_StopMoving(chaa);
}
if (chaa->view >= 0) {
int maxloop = _GP(views)[chaa->view].numLoops;
if (((chaa->flags & CHF_NODIAGONAL) != 0) && (maxloop > 4))
maxloop = 4;
FindReasonableLoopForCharacter(chaa);
}
stop_character_anim(chaa);
chaa->idleleft = chaa->idletime;
chaa->pic_xoffs = 0;
chaa->pic_yoffs = 0;
// restart the idle animation straight away
_GP(charextra)[chaa->index_id].process_idle_this_time = 1;
}
void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int direct) {
walk_or_move_character(chaa, x, y, blocking, direct, true);
}
void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct) {
walk_or_move_character(chaa, x, y, blocking, direct, false);
}
void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking) {
if (chaa->room != _G(displayed_room))
quitprintf("!MoveCharacterStraight: character %s is not in current room %d (it is in room %d)",
chaa->scrname, _G(displayed_room), chaa->room);
int movetox = xx, movetoy = yy;
set_wallscreen(prepare_walkable_areas(chaa->index_id));
int fromXLowres = room_to_mask_coord(chaa->x);
int fromYLowres = room_to_mask_coord(chaa->y);
int toXLowres = room_to_mask_coord(xx);
int toYLowres = room_to_mask_coord(yy);
if (!can_see_from(fromXLowres, fromYLowres, toXLowres, toYLowres)) {
int lastcx, lastcy;
get_lastcpos(lastcx, lastcy);
movetox = mask_to_room_coord(lastcx);
movetoy = mask_to_room_coord(lastcy);
}
walk_or_move_character(chaa, movetox, movetoy, blocking, 1 /* use ANYWHERE */, true);
}
void Character_RunInteraction(CharacterInfo *chaa, int mood) {
RunCharacterInteraction(chaa->index_id, mood);
}
// **** CHARACTER: PROPERTIES ****
int Character_GetProperty(CharacterInfo *chaa, const char *property) {
if (!AssertCharacter("Character.GetProperty", chaa->index_id))
return 0;
return get_int_property(_GP(game).charProps[chaa->index_id], _GP(play).charProps[chaa->index_id], property);
}
void Character_GetPropertyText(CharacterInfo *chaa, const char *property, char *bufer) {
if (!AssertCharacter("Character.GetPropertyText", chaa->index_id))
return;
get_text_property(_GP(game).charProps[chaa->index_id], _GP(play).charProps[chaa->index_id], property, bufer);
}
const char *Character_GetTextProperty(CharacterInfo *chaa, const char *property) {
if (!AssertCharacter("Character.GetTextProperty", chaa->index_id))
return nullptr;
return get_text_property_dynamic_string(_GP(game).charProps[chaa->index_id], _GP(play).charProps[chaa->index_id], property);
}
bool Character_SetProperty(CharacterInfo *chaa, const char *property, int value) {
if (!AssertCharacter("Character.SetProperty", chaa->index_id))
return false;
return set_int_property(_GP(play).charProps[chaa->index_id], property, value);
}
bool Character_SetTextProperty(CharacterInfo *chaa, const char *property, const char *value) {
if (!AssertCharacter("Character.SetTextProperty", chaa->index_id))
return false;
return set_text_property(_GP(play).charProps[chaa->index_id], property, value);
}
ScriptInvItem *Character_GetActiveInventory(CharacterInfo *chaa) {
if (chaa->activeinv <= 0)
return nullptr;
return &_G(scrInv)[chaa->activeinv];
}
void Character_SetActiveInventory(CharacterInfo *chaa, ScriptInvItem *iit) {
if (iit == nullptr) {
chaa->activeinv = -1;
if (chaa->index_id == _GP(game).playercharacter) {
if (GetCursorMode() == MODE_USE)
set_cursor_mode(0);
}
GUI::MarkInventoryForUpdate(chaa->index_id, chaa->index_id == _GP(game).playercharacter);
return;
}
if (chaa->inv[iit->id] < 1) {
debug_script_warn("SetActiveInventory: character doesn't have any of that inventory");
return;
}
chaa->activeinv = iit->id;
if (chaa->index_id == _GP(game).playercharacter) {
// if it's the player character, update mouse cursor
update_inv_cursor(iit->id);
set_cursor_mode(MODE_USE);
}
GUI::MarkInventoryForUpdate(chaa->index_id, chaa->index_id == _GP(game).playercharacter);
}
int Character_GetAnimating(CharacterInfo *chaa) {
if (chaa->animating)
return 1;
return 0;
}
int Character_GetAnimationSpeed(CharacterInfo *chaa) {
return chaa->animspeed;
}
void Character_SetAnimationSpeed(CharacterInfo *chaa, int newval) {
chaa->animspeed = newval;
if (_G(loaded_game_file_version) < kGameVersion_360_16)
chaa->idle_anim_speed = chaa->animspeed + 5;
}
int Character_GetAnimationVolume(CharacterInfo *chaa) {
return _GP(charextra)[chaa->index_id].anim_volume;
}
void Character_SetAnimationVolume(CharacterInfo *chaa, int newval) {
_GP(charextra)[chaa->index_id].anim_volume = Math::Clamp(newval, 0, 100);
}
int Character_GetBaseline(CharacterInfo *chaa) {
if (chaa->baseline < 1)
return 0;
return chaa->baseline;
}
void Character_SetBaseline(CharacterInfo *chaa, int basel) {
chaa->baseline = basel;
}
int Character_GetBlinkInterval(CharacterInfo *chaa) {
return chaa->blinkinterval;
}
void Character_SetBlinkInterval(CharacterInfo *chaa, int interval) {
if (interval < 0)
quit("!SetCharacterBlinkView: invalid blink interval");
chaa->blinkinterval = interval;
if (chaa->blinktimer > 0)
chaa->blinktimer = chaa->blinkinterval;
}
int Character_GetBlinkView(CharacterInfo *chaa) {
return chaa->blinkview + 1;
}
void Character_SetBlinkView(CharacterInfo *chaa, int vii) {
if (((vii < 2) || (vii > _GP(game).numviews)) && (vii != -1))
quit("!SetCharacterBlinkView: invalid view number");
chaa->blinkview = vii - 1;
}
int Character_GetBlinkWhileThinking(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOBLINKANDTHINK)
return 0;
return 1;
}
void Character_SetBlinkWhileThinking(CharacterInfo *chaa, int yesOrNo) {
chaa->flags &= ~CHF_NOBLINKANDTHINK;
if (yesOrNo == 0)
chaa->flags |= CHF_NOBLINKANDTHINK;
}
int Character_GetBlockingHeight(CharacterInfo *chaa) {
return chaa->blocking_height;
}
void Character_SetBlockingHeight(CharacterInfo *chaa, int hit) {
chaa->blocking_height = hit;
}
int Character_GetBlockingWidth(CharacterInfo *chaa) {
return chaa->blocking_width;
}
void Character_SetBlockingWidth(CharacterInfo *chaa, int wid) {
chaa->blocking_width = wid;
}
int Character_GetDiagonalWalking(CharacterInfo *chaa) {
if (chaa->flags & CHF_NODIAGONAL)
return 0;
return 1;
}
void Character_SetDiagonalWalking(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_NODIAGONAL;
if (!yesorno)
chaa->flags |= CHF_NODIAGONAL;
}
int Character_GetClickable(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOINTERACT)
return 0;
return 1;
}
void Character_SetClickable(CharacterInfo *chaa, int clik) {
chaa->flags &= ~CHF_NOINTERACT;
// if they don't want it clickable, set the relevant bit
if (clik == 0)
chaa->flags |= CHF_NOINTERACT;
}
int Character_GetID(CharacterInfo *chaa) {
return chaa->index_id;
}
const char *Character_GetScriptName(CharacterInfo *chin) {
return CreateNewScriptString(_GP(game).chars2[chin->index_id].scrname_new);
}
int Character_GetFrame(CharacterInfo *chaa) {
return chaa->frame;
}
void Character_SetFrame(CharacterInfo *chaa, int newval) {
chaa->frame = newval;
}
int Character_GetIdleView(CharacterInfo *chaa) {
if (chaa->idleview < 1)
return -1;
return chaa->idleview + 1;
}
int Character_GetIInventoryQuantity(CharacterInfo *chaa, int index) {
if ((index < 1) || (index >= _GP(game).numinvitems))
quitprintf("!Character.InventoryQuantity: invalid inventory index %d", index);
return chaa->inv[index];
}
int Character_HasInventory(CharacterInfo *chaa, ScriptInvItem *invi) {
if (invi == nullptr)
quit("!Character.HasInventory: NULL inventory item supplied");
return (chaa->inv[invi->id] > 0) ? 1 : 0;
}
void Character_SetIInventoryQuantity(CharacterInfo *chaa, int index, int quant) {
if ((index < 1) || (index >= _GP(game).numinvitems))
quitprintf("!Character.InventoryQuantity: invalid inventory index %d", index);
if ((quant < 0) || (quant > 32000))
quitprintf("!Character.InventoryQuantity: invalid quantity %d", quant);
chaa->inv[index] = quant;
}
int Character_GetIgnoreLighting(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOLIGHTING)
return 1;
return 0;
}
void Character_SetIgnoreLighting(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_NOLIGHTING;
if (yesorno)
chaa->flags |= CHF_NOLIGHTING;
}
int Character_GetIgnoreScaling(CharacterInfo *chaa) {
if (chaa->flags & CHF_MANUALSCALING)
return 1;
return 0;
}
void Character_SetIgnoreScaling(CharacterInfo *chaa, int yesorno) {
if (yesorno) {
// when setting IgnoreScaling to 1, should reset zoom level
// like it used to in pre-2.71
_GP(charextra)[chaa->index_id].zoom = 100;
}
Character_SetManualScaling(chaa, yesorno);
}
void Character_SetManualScaling(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_MANUALSCALING;
if (yesorno)
chaa->flags |= CHF_MANUALSCALING;
}
int Character_GetIgnoreWalkbehinds(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOWALKBEHINDS)
return 1;
return 0;
}
void Character_SetIgnoreWalkbehinds(CharacterInfo *chaa, int yesorno) {
if (_GP(game).options[OPT_BASESCRIPTAPI] >= kScriptAPI_v350)
debug_script_warn("IgnoreWalkbehinds is not recommended for use, consider other solutions");
chaa->flags &= ~CHF_NOWALKBEHINDS;
if (yesorno)
chaa->flags |= CHF_NOWALKBEHINDS;
}
int Character_GetMovementLinkedToAnimation(CharacterInfo *chaa) {
if (chaa->flags & CHF_ANTIGLIDE)
return 1;
return 0;
}
void Character_SetMovementLinkedToAnimation(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_ANTIGLIDE;
if (yesorno)
chaa->flags |= CHF_ANTIGLIDE;
}
int Character_GetLoop(CharacterInfo *chaa) {
return chaa->loop;
}
void Character_SetLoop(CharacterInfo *chaa, int newval) {
AssertLoop("Character.Loop", chaa->view, newval);
chaa->loop = newval;
if (chaa->frame >= _GP(views)[chaa->view].loops[chaa->loop].numFrames)
chaa->frame = 0;
}
int Character_GetMoving(CharacterInfo *chaa) {
if (chaa->walking)
return 1;
return 0;
}
int Character_GetDestinationX(CharacterInfo *chaa) {
if (chaa->walking) {
MoveList *cmls = &_GP(mls)[chaa->walking % TURNING_AROUND];
return cmls->pos[cmls->numstage - 1].X;
} else
return chaa->x;
}
int Character_GetDestinationY(CharacterInfo *chaa) {
if (chaa->walking) {
MoveList *cmls = &_GP(mls)[chaa->walking % TURNING_AROUND];
return cmls->pos[cmls->numstage - 1].Y;
} else
return chaa->y;
}
const char *Character_GetName(CharacterInfo *chaa) {
return CreateNewScriptString(_GP(game).chars2[chaa->index_id].name_new.GetCStr());
}
void Character_SetName(CharacterInfo *chaa, const char *newName) {
_GP(game).chars2[chaa->index_id].name_new = newName;
// Fill legacy name fields, for compatibility with old scripts and plugins
snprintf(chaa->name, LEGACY_MAX_CHAR_NAME_LEN, "%s", newName);
GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Overhotspot);
}
int Character_GetNormalView(CharacterInfo *chaa) {
return chaa->defview + 1;
}
int Character_GetPreviousRoom(CharacterInfo *chaa) {
return chaa->prevroom;
}
int Character_GetRoom(CharacterInfo *chaa) {
return chaa->room;
}
int Character_GetScaleMoveSpeed(CharacterInfo *chaa) {
if (chaa->flags & CHF_SCALEMOVESPEED)
return 1;
return 0;
}
void Character_SetScaleMoveSpeed(CharacterInfo *chaa, int yesorno) {
if ((yesorno < 0) || (yesorno > 1))
quit("Character.ScaleMoveSpeed: value must be true or false (1 or 0)");
chaa->flags &= ~CHF_SCALEMOVESPEED;
if (yesorno)
chaa->flags |= CHF_SCALEMOVESPEED;
}
int Character_GetScaleVolume(CharacterInfo *chaa) {
if (chaa->flags & CHF_SCALEVOLUME)
return 1;
return 0;
}
void Character_SetScaleVolume(CharacterInfo *chaa, int yesorno) {
if ((yesorno < 0) || (yesorno > 1))
quit("Character.ScaleVolume: value must be true or false (1 or 0)");
chaa->flags &= ~CHF_SCALEVOLUME;
if (yesorno)
chaa->flags |= CHF_SCALEVOLUME;
}
int Character_GetScaling(CharacterInfo *chaa) {
return _GP(charextra)[chaa->index_id].zoom;
}
void Character_SetScaling(CharacterInfo *chaa, int zoomlevel) {
if ((chaa->flags & CHF_MANUALSCALING) == 0) {
debug_script_warn("Character.Scaling: cannot set property unless ManualScaling is enabled");
return;
}
int zoom_fixed = Math::Clamp(zoomlevel, 1, (int)(INT16_MAX)); // CharacterExtras.zoom is int16
if (zoomlevel != zoom_fixed)
debug_script_warn("Character.Scaling: scaling level must be between 1 and %d%%, asked for: %d",
(int)(INT16_MAX), zoomlevel);
_GP(charextra)[chaa->index_id].zoom = zoom_fixed;
}
int Character_GetSolid(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOBLOCKING)
return 0;
return 1;
}
void Character_SetSolid(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_NOBLOCKING;
if (!yesorno)
chaa->flags |= CHF_NOBLOCKING;
}
int Character_GetSpeaking(CharacterInfo *chaa) {
if (get_character_currently_talking() == chaa->index_id)
return 1;
return 0;
}
int Character_GetSpeechColor(CharacterInfo *chaa) {
return chaa->talkcolor;
}
void Character_SetSpeechColor(CharacterInfo *chaa, int ncol) {
chaa->talkcolor = ncol;
}
void Character_SetSpeechAnimationDelay(CharacterInfo *chaa, int newDelay) {
if (_GP(game).options[OPT_GLOBALTALKANIMSPD] != 0) {
debug_script_warn("Character.SpeechAnimationDelay cannot be set when global speech animation speed is enabled");
return;
}
chaa->speech_anim_speed = newDelay;
}
int Character_GetIdleAnimationDelay(CharacterInfo *chaa) {
return chaa->idle_anim_speed;
}
void Character_SetIdleAnimationDelay(CharacterInfo *chaa, int newDelay) {
chaa->idle_anim_speed = newDelay;
}
int Character_GetSpeechView(CharacterInfo *chaa) {
return chaa->talkview + 1;
}
void Character_SetSpeechView(CharacterInfo *chaa, int vii) {
if (vii == -1) {
chaa->talkview = -1;
return;
}
if ((vii < 1) || (vii > _GP(game).numviews))
quit("!SetCharacterSpeechView: invalid view number");
chaa->talkview = vii - 1;
}
bool Character_GetThinking(CharacterInfo *chaa) {
return _G(char_thinking) == chaa->index_id;
}
int Character_GetThinkingFrame(CharacterInfo *chaa) {
if (_G(char_thinking) == chaa->index_id)
return chaa->thinkview > 0 ? chaa->frame : -1;
debug_script_warn("Character.ThinkingFrame: character is not currently thinking");
return -1;
}
int Character_GetThinkView(CharacterInfo *chaa) {
return chaa->thinkview + 1;
}
void Character_SetThinkView(CharacterInfo *chaa, int vii) {
if (((vii < 2) || (vii > _GP(game).numviews)) && (vii != -1))
quit("!SetCharacterThinkView: invalid view number");
chaa->thinkview = vii - 1;
}
int Character_GetTransparency(CharacterInfo *chaa) {
return GfxDef::LegacyTrans255ToTrans100(chaa->transparency);
}
void Character_SetTransparency(CharacterInfo *chaa, int trans) {
if ((trans < 0) || (trans > 100))
quit("!SetCharTransparent: transparency value must be between 0 and 100");
chaa->transparency = GfxDef::Trans100ToLegacyTrans255(trans);
}
int Character_GetTurnBeforeWalking(CharacterInfo *chaa) {
if (chaa->flags & CHF_NOTURNING)
return 0;
return 1;
}
void Character_SetTurnBeforeWalking(CharacterInfo *chaa, int yesorno) {
chaa->flags &= ~CHF_NOTURNING;
if (!yesorno)
chaa->flags |= CHF_NOTURNING;
}
int Character_GetView(CharacterInfo *chaa) {
return chaa->view + 1;
}
int Character_GetWalkSpeedX(CharacterInfo *chaa) {
return chaa->walkspeed;
}
int Character_GetWalkSpeedY(CharacterInfo *chaa) {
if (chaa->walkspeed_y != UNIFORM_WALK_SPEED)
return chaa->walkspeed_y;
return chaa->walkspeed;
}
int Character_GetX(CharacterInfo *chaa) {
return chaa->x;
}
void Character_SetX(CharacterInfo *chaa, int newval) {
chaa->x = newval;
}
int Character_GetY(CharacterInfo *chaa) {
return chaa->y;
}
void Character_SetY(CharacterInfo *chaa, int newval) {
chaa->y = newval;
}
int Character_GetZ(CharacterInfo *chaa) {
return chaa->z;
}
void Character_SetZ(CharacterInfo *chaa, int newval) {
chaa->z = newval;
}
int Character_GetSpeakingFrame(CharacterInfo *chaa) {
if ((_G(face_talking) >= 0) && (_G(facetalkrepeat))) {
if (_G(facetalkchar)->index_id == chaa->index_id) {
return _G(facetalkframe);
}
} else if (_G(char_speaking) >= 0) {
if (_G(char_speaking) == chaa->index_id) {
return chaa->frame;
}
}
debug_script_warn("Character.SpeakingFrame: character is not currently speaking");
return -1;
}
//=============================================================================
// order of loops to turn character in circle from down to down
int turnlooporder[8] = {0, 6, 1, 7, 3, 5, 2, 4};
void walk_character(int chac, int tox, int toy, int ignwal, bool autoWalkAnims) {
CharacterInfo *chin = &_GP(game).chars[chac];
if (chin->room != _G(displayed_room))
quitprintf("!MoveCharacter: character %s is not in current room %d (it is in room %d)",
chin->scrname, _G(displayed_room), chin->room);
chin->flags &= ~CHF_MOVENOTWALK;
if ((tox == chin->x) && (toy == chin->y)) {
StopMoving(chac);
debug_script_log("%s already at destination, not moving", chin->scrname);
return;
}
if ((chin->animating) && (autoWalkAnims))
stop_character_anim(chin);
// Stop idling anim
stop_character_idling(chin);
// stop them to make sure they're on a walkable area
// but save their frame first so that if they're already
// moving it looks smoother
int oldframe = chin->frame;
int waitWas = 0, animWaitWas = 0;
float wasStepFrac = 0.f;
// if they are currently walking, save the current Wait
if (chin->walking) {
waitWas = chin->walkwait;
animWaitWas = _GP(charextra)[chac].animwait;
const auto &movelist = _GP(mls)[chin->walking % TURNING_AROUND];
// We set (fraction + 1), because movelist is always +1 ahead of current character pos;
if (movelist.onpart > 0.f)
wasStepFrac = movelist.GetPixelUnitFraction() + movelist.GetStepLength();
}
StopMoving(chac);
chin->frame = oldframe;
// use toxPassedIn cached variable so the hi-res co-ordinates
// are still displayed as such
debug_script_log("%s: Start move to %d,%d", chin->scrname, tox, toy);
int move_speed_x, move_speed_y;
chin->get_effective_walkspeeds(move_speed_x, move_speed_y);
if ((move_speed_x == 0) && (move_speed_y == 0)) {
debug_script_warn("MoveCharacter: called for '%s' with walk speed 0", chin->scrname);
}
// Convert src and dest coords to the mask resolution, for pathfinder
const int src_x = room_to_mask_coord(chin->x);
const int src_y = room_to_mask_coord(chin->y);
const int dst_x = room_to_mask_coord(tox);
const int dst_y = room_to_mask_coord(toy);
int mslot = find_route(src_x, src_y, dst_x, dst_y, move_speed_x, move_speed_y, prepare_walkable_areas(chac), chac + CHMLSOFFS, 1, ignwal);
if (mslot > 0) {
chin->walking = mslot;
_GP(mls)[mslot].direct = ignwal;
convert_move_path_to_room_resolution(&_GP(mls)[mslot]);
// NOTE: unfortunately, some old game scripts might break because of smooth walk transition
if (wasStepFrac > 0.f && (_G(loaded_game_file_version) >= kGameVersion_361)) {
_GP(mls)[mslot].SetPixelUnitFraction(wasStepFrac);
}
// cancel any pending waits on current animations
// or if they were already moving, keep the current wait -
// this prevents a glitch if MoveCharacter is called when they
// are already moving
if (autoWalkAnims) {
chin->walkwait = waitWas;
_GP(charextra)[chac].animwait = animWaitWas;
if (_GP(mls)[mslot].pos[0] != _GP(mls)[mslot].pos[1]) {
fix_player_sprite(&_GP(mls)[mslot], chin);
}
} else
chin->flags |= CHF_MOVENOTWALK;
} else if (autoWalkAnims) // pathfinder couldn't get a route, stand them still
chin->frame = 0;
}
int find_looporder_index(int curloop) {
int rr;
for (rr = 0; rr < 8; rr++) {
if (turnlooporder[rr] == curloop)
return rr;
}
return 0;
}
// returns 0 to use diagonal, 1 to not
int useDiagonal(CharacterInfo *char1) {
if ((_GP(views)[char1->view].numLoops < 8) || ((char1->flags & CHF_NODIAGONAL) != 0))
return 1;
// If they have just provided standing frames for loops 4-7, to
// provide smoother turning
if (_GP(views)[char1->view].loops[4].numFrames < 2)
return 2;
return 0;
}
// returns 1 normally, or 0 if they only have horizontal animations
int hasUpDownLoops(CharacterInfo *char1) {
// if no loops in the Down animation
// or no loops in the Up animation
if ((_GP(views)[char1->view].loops[0].numFrames < 1) ||
(_GP(views)[char1->view].numLoops < 4) ||
(_GP(views)[char1->view].loops[3].numFrames < 1)) {
return 0;
}
return 1;
}
void start_character_turning(CharacterInfo *chinf, int useloop, int no_diagonal) {
// work out how far round they have to turn
int fromidx = find_looporder_index(chinf->loop);
int toidx = find_looporder_index(useloop);
//Display("Curloop: %d, needloop: %d",chinf->loop, useloop);
int ii, go_anticlock = 0;
// work out whether anticlockwise is quicker or not
if ((toidx > fromidx) && ((toidx - fromidx) > 4))
go_anticlock = 1;
if ((toidx < fromidx) && ((fromidx - toidx) < 4))
go_anticlock = 1;
// strip any current turning_around stages
chinf->walking = chinf->walking % TURNING_AROUND;
if (go_anticlock)
chinf->walking += TURNING_BACKWARDS;
else
go_anticlock = -1;
// Allow the diagonal frames just for turning
if (no_diagonal == 2)
no_diagonal = 0;
for (ii = fromidx; ii != toidx; ii -= go_anticlock) {
// Wrap the loop order into range [0-7]
if (ii < 0)
ii = 7;
if (ii >= 8)
ii = 0;
if (ii == toidx)
break;
if ((turnlooporder[ii] >= 4) && (no_diagonal > 0))
continue; // there are no diagonal loops
if (turnlooporder[ii] >= _GP(views)[chinf->view].numLoops)
continue; // no such loop
if (_GP(views)[chinf->view].loops[turnlooporder[ii]].numFrames < 1)
continue; // no frames in such loop
chinf->walking += TURNING_AROUND;
}
}
void fix_player_sprite(MoveList *cmls, CharacterInfo *chinf) {
const fixed xpmove = cmls->xpermove[cmls->onstage];
const fixed ypmove = cmls->ypermove[cmls->onstage];
// if not moving, do nothing
if ((xpmove == 0) && (ypmove == 0))
return;
const int useloop = GetDirectionalLoop(chinf, xpmove, ypmove);
if ((_GP(game).options[OPT_ROTATECHARS] == 0) || ((chinf->flags & CHF_NOTURNING) != 0)) {
chinf->loop = useloop;
return;
}
if ((chinf->loop > kDirLoop_LastOrthogonal) && ((chinf->flags & CHF_NODIAGONAL) != 0)) {
// They've just been playing an animation with an extended loop number,
// so don't try and rotate using it
chinf->loop = useloop;
return;
}
if ((chinf->loop >= _GP(views)[chinf->view].numLoops) ||
(_GP(views)[chinf->view].loops[chinf->loop].numFrames < 1) ||
(hasUpDownLoops(chinf) == 0)) {
// Character is not currently on a valid loop, so don't try to rotate
// eg. left/right only view, but current loop 0
chinf->loop = useloop;
return;
}
const int no_diagonal = useDiagonal(chinf);
start_character_turning(chinf, useloop, no_diagonal);
}
// Check whether two characters have walked into each other
int has_hit_another_character(int sourceChar) {
// if the character who's moving doesn't block, don't bother checking
if (_GP(game).chars[sourceChar].flags & CHF_NOBLOCKING)
return -1;
for (int ww = 0; ww < _GP(game).numcharacters; ww++) {
if (_GP(game).chars[ww].on != 1) continue;
if (_GP(game).chars[ww].room != _G(displayed_room)) continue;
if (ww == sourceChar) continue;
if (_GP(game).chars[ww].flags & CHF_NOBLOCKING) continue;
if (is_char_in_blocking_rect(sourceChar, ww, nullptr, nullptr)) {
// we are now overlapping character 'ww'
if ((_GP(game).chars[ww].walking) &&
((_GP(game).chars[ww].flags & CHF_AWAITINGMOVE) == 0))
return ww;
}
}
return -1;
}
// Does the next move from the character's movelist.
// Returns 1 if they are now waiting for another char to move,
// otherwise returns 0
int doNextCharMoveStep(CharacterInfo *chi, int &char_index, CharacterExtras *chex) {
int ntf = 0, xwas = chi->x, ywas = chi->y;
if (do_movelist_move(chi->walking, chi->x, chi->y) == 2) {
if ((chi->flags & CHF_MOVENOTWALK) == 0)
fix_player_sprite(&_GP(mls)[chi->walking], chi);
}
ntf = has_hit_another_character(char_index);
if (ntf >= 0) {
chi->walkwait = 30;
if (_GP(game).chars[ntf].walkspeed < 5)
chi->walkwait += (5 - _GP(game).chars[ntf].walkspeed) * 5;
// we are now waiting for the other char to move, so
// make sure he doesn't stop for us too
chi->flags |= CHF_AWAITINGMOVE;
if ((chi->flags & CHF_MOVENOTWALK) == 0) {
chi->frame = 0;
chex->animwait = chi->walkwait;
}
if ((chi->walking < 1) || (chi->walking >= TURNING_AROUND)) ;
else if (_GP(mls)[chi->walking].onpart > 0.f) {
_GP(mls)[chi->walking].onpart -= 1.f;
chi->x = xwas;
chi->y = ywas;
}
debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname, _GP(game).chars[ntf].scrname);
return 1;
}
return 0;
}
bool is_char_walking_ndirect(CharacterInfo *chi) {
return ((chi->walking > 0) && (chi->walking < TURNING_AROUND)) &&
(_GP(mls)[chi->walking].direct == 0);
}
int find_nearest_walkable_area_within(int *xx, int *yy, int range, int step) {
int ex, ey, nearest = 99999, thisis, nearx = 0, neary = 0;
int startx = 0, starty = 14;
int roomWidthLowRes = room_to_mask_coord(_GP(thisroom).Width);
int roomHeightLowRes = room_to_mask_coord(_GP(thisroom).Height);
int xwidth = roomWidthLowRes, yheight = roomHeightLowRes;
int xLowRes = room_to_mask_coord(xx[0]);
int yLowRes = room_to_mask_coord(yy[0]);
int rightEdge = room_to_mask_coord(_GP(thisroom).Edges.Right);
int leftEdge = room_to_mask_coord(_GP(thisroom).Edges.Left);
int topEdge = room_to_mask_coord(_GP(thisroom).Edges.Top);
int bottomEdge = room_to_mask_coord(_GP(thisroom).Edges.Bottom);
// tweak because people forget to move the edges sometimes
// if the player is already over the edge, ignore it
if (xLowRes >= rightEdge) rightEdge = roomWidthLowRes;
if (xLowRes <= leftEdge) leftEdge = 0;
if (yLowRes >= bottomEdge) bottomEdge = roomHeightLowRes;
if (yLowRes <= topEdge) topEdge = 0;
if (range > 0) {
startx = xLowRes - range;
starty = yLowRes - range;
xwidth = startx + range * 2;
yheight = starty + range * 2;
if (startx < 0) startx = 0;
if (starty < 10) starty = 10;
if (xwidth > roomWidthLowRes) xwidth = roomWidthLowRes;
if (yheight > roomHeightLowRes) yheight = roomHeightLowRes;
}
for (ex = startx; ex < xwidth; ex += step) {
for (ey = starty; ey < yheight; ey += step) {
// non-walkalbe, so don't go here
if (_GP(thisroom).WalkAreaMask->GetPixel(ex, ey) == 0) continue;
// off a screen edge, don't move them there
if ((ex <= leftEdge) || (ex >= rightEdge) ||
(ey <= topEdge) || (ey >= bottomEdge))
continue;
// otherwise, calculate distance from target
thisis = (int) ::sqrt((double)((ex - xLowRes) * (ex - xLowRes) + (ey - yLowRes) * (ey - yLowRes)));
if (thisis < nearest) {
nearest = thisis;
nearx = ex;
neary = ey;
}
}
}
if (nearest < 90000) {
xx[0] = mask_to_room_coord(nearx);
yy[0] = mask_to_room_coord(neary);
return 1;
}
return 0;
}
void find_nearest_walkable_area(int *xx, int *yy) {
int pixValue = _GP(thisroom).WalkAreaMask->GetPixel(room_to_mask_coord(xx[0]), room_to_mask_coord(yy[0]));
// only fix this code if the game was built with 2.61 or above
if (pixValue == 0 || (_G(loaded_game_file_version) >= kGameVersion_261 && pixValue < 1)) {
// First, check every 2 pixels within immediate area
if (!find_nearest_walkable_area_within(xx, yy, 20, 2)) {
// If not, check whole screen at 5 pixel intervals
find_nearest_walkable_area_within(xx, yy, -1, 5);
}
}
}
void FindReasonableLoopForCharacter(CharacterInfo *chap) {
if (chap->loop >= _GP(views)[chap->view].numLoops)
chap->loop = kDirLoop_Default;
if (_GP(views)[chap->view].numLoops < 1)
quitprintf("!View %d does not have any loops", chap->view + 1);
// if the current loop has no frames, find one that does
if (_GP(views)[chap->view].loops[chap->loop].numFrames < 1) {
for (int i = 0; i < _GP(views)[chap->view].numLoops; i++) {
if (_GP(views)[chap->view].loops[i].numFrames > 0) {
chap->loop = i;
break;
}
}
}
}
void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk) {
if (chaa->on != 1) {
debug_script_warn("MoveCharacterBlocking: character is turned off and cannot be moved");
return;
}
if ((direct == ANYWHERE) || (direct == 1))
walk_character(chaa->index_id, x, y, 1, isWalk);
else if ((direct == WALKABLE_AREAS) || (direct == 0))
walk_character(chaa->index_id, x, y, 0, isWalk);
else
quit("!Character.Walk: Direct must be ANYWHERE or WALKABLE_AREAS");
if ((blocking == BLOCKING) || (blocking == 1))
GameLoopUntilNotMoving(&chaa->walking);
else if ((blocking != IN_BACKGROUND) && (blocking != 0))
quit("!Character.Walk: Blocking must be BLOCKING or IN_BACKGROUND");
}
int wantMoveNow(CharacterInfo *chi, CharacterExtras *chex) {
// check most likely case first
if ((chex->zoom == 100) || ((chi->flags & CHF_SCALEMOVESPEED) == 0))
return 1;
// the % checks don't work when the counter is negative, so once
// it wraps round, correct it
while (chi->walkwaitcounter < 0) {
chi->walkwaitcounter += 12000;
}
// scaling 170-200%, move 175% speed
if (chex->zoom >= 170) {
if ((chi->walkwaitcounter % 4) >= 1)
return 2;
else
return 1;
}
// scaling 140-170%, move 150% speed
else if (chex->zoom >= 140) {
if ((chi->walkwaitcounter % 2) == 1)
return 2;
else
return 1;
}
// scaling 115-140%, move 125% speed
else if (chex->zoom >= 115) {
if ((chi->walkwaitcounter % 4) >= 3)
return 2;
else
return 1;
}
// scaling 80-120%, normal speed
else if (chex->zoom >= 80)
return 1;
// scaling 60-80%, move 75% speed
if (chex->zoom >= 60) {
if ((chi->walkwaitcounter % 4) >= 1)
return -1;
else if (chex->xwas != INVALID_X) {
// move the second half of the movement to make it smoother
chi->x = chex->xwas;
chi->y = chex->ywas;
chex->xwas = INVALID_X;
}
}
// scaling 30-60%, move 50% speed
else if (chex->zoom >= 30) {
if ((chi->walkwaitcounter % 2) == 1)
return -1;
else if (chex->xwas != INVALID_X) {
// move the second half of the movement to make it smoother
chi->x = chex->xwas;
chi->y = chex->ywas;
chex->xwas = INVALID_X;
}
}
// scaling 0-30%, move 25% speed
else {
if ((chi->walkwaitcounter % 4) >= 3)
return -1;
if (((chi->walkwaitcounter % 4) == 1) && (chex->xwas != INVALID_X)) {
// move the second half of the movement to make it smoother
chi->x = chex->xwas;
chi->y = chex->ywas;
chex->xwas = INVALID_X;
}
}
return 0;
}
void setup_player_character(int charid) {
_GP(game).playercharacter = charid;
_G(playerchar) = &_GP(game).chars[charid];
_G(sc_PlayerCharPtr) = ccGetObjectHandleFromAddress(_G(playerchar));
if (_G(loaded_game_file_version) < kGameVersion_270) {
ccAddExternalScriptObject("player", _G(playerchar), &_GP(ccDynamicCharacter));
}
}
// Animate character internal implementation;
// this function may be called by the game logic too, so we assume
// the arguments must be correct, and do not fix them up as we do for API functions.
void animate_character(CharacterInfo *chap, int loopn, int sppd, int rept, int direction, int sframe, int volume) {
if ((chap->view < 0) || (chap->view > _GP(game).numviews) ||
(loopn < 0) || (loopn >= _GP(views)[chap->view].numLoops)) {
quitprintf("!AnimateCharacter: invalid view and/or loop\n"
"(trying to animate '%s' using view %d (range is 1..%d) and loop %d (view has %d loops)).",
chap->scrname, chap->view + 1, _GP(game).numviews, loopn, _GP(views)[chap->view].numLoops);
}
// NOTE: there's always frame 0 allocated for safety
sframe = std::max(0, std::min(sframe, _GP(views)[chap->view].loops[loopn].numFrames - 1));
debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d",
chap->scrname, chap->view + 1, loopn, sppd, rept, sframe);
Character_StopMoving(chap);
chap->set_animating(rept != 0, direction == 0, sppd);
chap->loop = loopn;
chap->frame = SetFirstAnimFrame(chap->view, loopn, sframe, direction);
chap->wait = sppd + _GP(views)[chap->view].loops[loopn].frames[chap->frame].speed;
_GP(charextra)[chap->index_id].cur_anim_volume = Math::Clamp(volume, 0, 100);
_GP(charextra)[chap->index_id].CheckViewFrame(chap);
}
void stop_character_anim(CharacterInfo *chap) { // TODO: may expand with resetting more properties,
// but have to be careful to not break logic somewhere
chap->animating = 0;
_GP(charextra)[chap->index_id].cur_anim_volume = 100;
}
int GetCharacterFrameVolume(CharacterInfo *chi) {
// We view the audio property relation as the relation of the entities:
// system -> audio type -> audio emitter (character) -> animation's audio
// therefore the sound volume is a multiplication of factors.
int frame_vol = 100; // default to full volume
// Try the active animation volume
if (_GP(charextra)[chi->index_id].cur_anim_volume >= 0)
frame_vol = _GP(charextra)[chi->index_id].cur_anim_volume;
// Try the character's own animation volume property
if (_GP(charextra)[chi->index_id].anim_volume >= 0)
frame_vol = frame_vol * _GP(charextra)[chi->index_id].anim_volume / 100;
// Try the character's zoom volume scaling (optional)
// NOTE: historically scales only in 0-100 range :/
if (chi->flags & CHF_SCALEVOLUME) {
int zoom_level = _GP(charextra)[chi->index_id].zoom;
if (zoom_level <= 0)
zoom_level = 100;
else
zoom_level = std::min(zoom_level, 100);
frame_vol = frame_vol * zoom_level / 100;
}
return frame_vol;
}
Bitmap *GetCharacterImage(int charid, bool *is_original) {
// NOTE: the cached image will only be present in software render mode
Bitmap *actsp = get_cached_character_image(charid);
if (is_original)
*is_original = !actsp; // no cached means we use original sprite
if (actsp)
return actsp;
CharacterInfo *chin = &_GP(game).chars[charid];
int sppic = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
return _GP(spriteset)[sppic];
}
CharacterInfo *GetCharacterAtScreen(int xx, int yy) {
int hsnum = GetCharIDAtScreen(xx, yy);
if (hsnum < 0)
return nullptr;
return &_GP(game).chars[hsnum];
}
CharacterInfo *GetCharacterAtRoom(int x, int y) {
int hsnum = is_pos_on_character(x, y);
if (hsnum < 0)
return nullptr;
return &_GP(game).chars[hsnum];
}
void update_character_scale(int charid) {
// Test for valid view and loop
CharacterInfo &chin = _GP(game).chars[charid];
if (chin.on == 0 || chin.room != _G(displayed_room))
return; // not enabled, or in a different room
CharacterExtras &chex = _GP(charextra)[charid];
if (chin.view < 0) {
quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.",
chin.scrname, _G(displayed_room));
}
if (chin.loop >= _GP(views)[chin.view].numLoops) {
quitprintf("!The character '%s' could not be displayed because there was no loop %d of view %d.",
chin.scrname, chin.loop, chin.view + 1);
}
// If frame is too high -- fallback to the frame 0;
// there's always at least 1 dummy frame at index 0
if (chin.frame >= _GP(views)[chin.view].loops[chin.loop].numFrames) {
chin.frame = 0;
}
int zoom, zoom_offs, scale_width, scale_height;
update_object_scale(zoom, scale_width, scale_height,
chin.x, chin.y, _GP(views)[chin.view].loops[chin.loop].frames[chin.frame].pic,
chex.zoom, (chin.flags & CHF_MANUALSCALING) == 0);
zoom_offs = (_GP(game).options[OPT_SCALECHAROFFSETS] != 0) ? zoom : 100;
// Calculate the X & Y co-ordinates of where the sprite will be;
// for the character sprite's origin is at the bottom-mid of a sprite.
const int atxp = (data_to_game_coord(chin.x)) - scale_width / 2;
const int atyp = (data_to_game_coord(chin.y) - scale_height)
// adjust the Y positioning for the character's Z co-ord
- (data_to_game_coord(chin.z) * zoom_offs / 100);
// Save calculated properties
chex.width = scale_width;
chex.height = scale_height;
chin.actx = atxp;
chin.acty = atyp;
chex.zoom = zoom;
chex.zoom_offs = zoom_offs;
}
int is_pos_on_character(int xx, int yy) {
int cc, sppic, lowestyp = 0, lowestwas = -1;
for (cc = 0; cc < _GP(game).numcharacters; cc++) {
if (_GP(game).chars[cc].room != _G(displayed_room)) continue;
if (_GP(game).chars[cc].on == 0) continue;
if (_GP(game).chars[cc].flags & CHF_NOINTERACT) continue;
if (_GP(game).chars[cc].view < 0) continue;
CharacterInfo *chin = &_GP(game).chars[cc];
if ((chin->view < 0) ||
(chin->loop >= _GP(views)[chin->view].numLoops) ||
(chin->frame >= _GP(views)[chin->view].loops[chin->loop].numFrames)) {
continue;
}
sppic = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].pic;
int usewid = _GP(charextra)[cc].width;
int usehit = _GP(charextra)[cc].height;
if (usewid == 0) usewid = _GP(game).SpriteInfos[sppic].Width;
if (usehit == 0) usehit = _GP(game).SpriteInfos[sppic].Height;
int xxx = chin->x - game_to_data_coord(usewid) / 2;
int yyy = _GP(charextra)[cc].GetEffectiveY(chin) - game_to_data_coord(usehit);
int mirrored = _GP(views)[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE;
bool is_original;
Bitmap *theImage = GetCharacterImage(cc, &is_original);
if (!is_original)
mirrored = 0; // transformed image is already flipped
if (is_pos_in_sprite(xx, yy, xxx, yyy, theImage,
game_to_data_coord(usewid),
game_to_data_coord(usehit), mirrored, is_original) == FALSE)
continue;
int use_base = chin->get_baseline();
if (use_base < lowestyp) continue;
lowestyp = use_base;
lowestwas = cc;
}
_G(char_lowest_yp) = lowestyp;
return lowestwas;
}
void get_char_blocking_rect(int charid, int *x1, int *y1, int *width, int *y2) {
CharacterInfo *char1 = &_GP(game).chars[charid];
int cwidth, fromx;
if (char1->blocking_width < 1)
cwidth = game_to_data_coord(GetCharacterWidth(charid)) - 4;
else
cwidth = char1->blocking_width;
fromx = char1->x - cwidth / 2;
if (fromx < 0) {
cwidth += fromx;
fromx = 0;
}
if (fromx + cwidth >= mask_to_room_coord(_G(walkable_areas_temp)->GetWidth()))
cwidth = mask_to_room_coord(_G(walkable_areas_temp)->GetWidth()) - fromx;
if (x1)
*x1 = fromx;
if (width)
*width = cwidth;
if (y1)
*y1 = char1->get_blocking_top();
if (y2)
*y2 = char1->get_blocking_bottom();
}
// Check whether the source char is standing inside otherChar's blocking rectangle
int is_char_in_blocking_rect(int sourceChar, int otherChar, int *fromxptr, int *cwidptr) {
int fromx, cwidth;
int y1, y2;
get_char_blocking_rect(otherChar, &fromx, &y1, &cwidth, &y2);
if (fromxptr)
fromxptr[0] = fromx;
if (cwidptr)
cwidptr[0] = cwidth;
// if the character trying to move is already on top of
// this char somehow, allow them through
if ((sourceChar >= 0) &&
// x/width are left and width co-ords, so they need >= and <
(_GP(game).chars[sourceChar].x >= fromx) &&
(_GP(game).chars[sourceChar].x < fromx + cwidth) &&
// y1/y2 are the top/bottom co-ords, so they need >= / <=
(_GP(game).chars[sourceChar].y >= y1) &&
(_GP(game).chars[sourceChar].y <= y2))
return 1;
return 0;
}
int my_getpixel(Bitmap *blk, int x, int y) {
if ((x < 0) || (y < 0) || (x >= blk->GetWidth()) || (y >= blk->GetHeight()))
return -1;
// strip the alpha channel
// TODO: is there a way to do this vtable thing with Bitmap?
BITMAP *al_bmp = (BITMAP *)blk->GetAllegroBitmap();
return al_bmp->getpixel(x, y) & 0x00ffffff;
}
int check_click_on_character(int xx, int yy, int mood) {
int lowestwas = is_pos_on_character(xx, yy);
if (lowestwas >= 0) {
RunCharacterInteraction(lowestwas, mood);
return 1;
}
return 0;
}
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
// speech line
_GP(play).swap_portrait_lastchar = chid;
return;
}
// adjust timing of text (so that DisplaySpeech("%s", str) pauses
// for the length of the string not 2 frames)
int len = (int)strlen(displbuf);
if (len > _G(source_text_length) + 3)
_G(source_text_length) = len;
DisplaySpeech(displbuf, chid);
}
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 > _G(source_text_length) + 3)
_G(source_text_length) = len;
int xpp = -1, ypp = -1, width = -1;
if ((_GP(game).options[OPT_SPEECHTYPE] == 0) || (_GP(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(_GP(play).speech_bubble_width);
xpp = _GP(play).RoomToScreenX(data_to_game_coord(_GP(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);
}
void _displayspeech(const char *texx, int aschar, int xx, int yy, int widd, int isThought) {
if (!is_valid_character(aschar))
quit("!DisplaySpeech: invalid character");
CharacterInfo *speakingChar = &_GP(game).chars[aschar];
if ((speakingChar->view < 0) || (speakingChar->view >= _GP(game).numviews))
quit("!DisplaySpeech: character has invalid view");
if (_GP(play).screen_is_faded_out > 0)
debug_script_warn("Warning: blocking Say call during fade-out.");
if (_GP(play).text_overlay_on > 0) {
debug_script_warn("DisplaySpeech: speech was already displayed (nested DisplaySpeech, perhaps room script and global script conflict?)");
return;
}
EndSkippingUntilCharStops();
_G(said_speech_line) = 1;
if (_GP(play).bgspeech_stay_on_display == 0) {
// remove any background speech
auto &overs = get_overlays();
for (auto &over : overs) {
if (over.timeout > 0)
remove_screen_overlay(over.type);
}
}
_G(said_text) = 1;
// the strings are pre-translated
//texx = get_translation(texx);
set_our_eip(150);
int isPause = 1;
// if the message is all .'s, don't display anything
for (size_t aa = 0; texx[aa] != 0; aa++) {
if (texx[aa] != '.') {
isPause = 0;
break;
}
}
_GP(play).messagetime = GetTextDisplayTime(texx);
_GP(play).speech_in_post_state = false;
if (isPause) {
postpone_scheduled_music_update_by(std::chrono::milliseconds(_GP(play).messagetime * 1000 / _G(frames_per_second)));
// Set a post-state right away, as we only need to wait for a messagetime timer
_GP(play).speech_in_post_state = true;
GameLoopUntilValueIsNegative(&_GP(play).messagetime);
post_display_cleanup();
return;
}
int textcol = speakingChar->talkcolor;
// if it's 0, it won't be recognised as speech
if (textcol == 0)
textcol = 16;
Rect ui_view = _GP(play).GetUIViewport();
int allowShrink = 0;
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) {
useview = speakingChar->thinkview;
// view 0 is not valid for think views
if (useview == 0)
useview = -1;
// speech bubble can shrink to fit
allowShrink = 1;
if (speakingChar->room != _G(displayed_room)) {
// not in room, centre it
xx = -1;
yy = -1;
}
}
if (useview >= _GP(game).numviews)
quitprintf("!Character.Say: attempted to use view %d for animation, but it does not exist", useview + 1);
if (_GP(game).options[OPT_SPEECHTYPE] == 3)
remove_screen_overlay(OVER_COMPLETE);
set_our_eip(1500);
if (_GP(game).options[OPT_SPEECHTYPE] == 0)
allowShrink = 1;
// If has a valid speech view, and idle anim in progress for the character, then stop it
if (useview >= 0) {
stop_character_idling(speakingChar);
}
int tdxp = xx, tdyp = yy;
int oldview = -1, oldloop = -1;
int ovr_type = 0;
_G(text_lips_offset) = 0;
_G(text_lips_text) = texx;
Bitmap *closeupface = nullptr;
bool overlayPositionFixed = false;
int charFrameWas = 0;
int viewWasLocked = 0;
if (speakingChar->flags & CHF_FIXVIEW)
viewWasLocked = 1;
// Start voice-over, if requested by the tokens in speech text
try_auto_play_speech(texx, texx, aschar);
if (speakingChar->room == _G(displayed_room)) {
// If the character is in this room, go for it - otherwise
// run the "else" clause which does text in the middle of
// the screen.
set_our_eip(1501);
if (speakingChar->walking)
StopMoving(aschar);
// save the frame we need to go back to
// if they were moving, this will be 0 (because we just called
// StopMoving); otherwise, it might be a specific animation
// frame which we should return to
if (viewWasLocked)
charFrameWas = speakingChar->frame;
if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
quitprintf("!Character %s current view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
// If current view is missing a loop - use loop 0
if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
debug_script_warn("WARNING: Character %s current view %d does not have necessary loop %d; switching to loop 0.",
speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
speakingChar->loop = 0;
}
set_our_eip(1504);
// Calculate speech position based on character's position on screen
auto view = FindNearestViewport(aschar);
if (tdxp < 0)
tdxp = view->RoomToScreen(data_to_game_coord(speakingChar->x), 0).first.X;
if (tdxp < 2)
tdxp = 2;
tdxp = -tdxp; // tell it to centre it ([ikm] not sure what's going on here... wrong comment?)
if (tdyp < 0) {
int sppic = _GP(views)[speakingChar->view].loops[speakingChar->loop].frames[0].pic;
int height = (_GP(charextra)[aschar].height < 1) ? _GP(game).SpriteInfos[sppic].Height : _GP(charextra)[aschar].height;
tdyp = view->RoomToScreen(0, data_to_game_coord(_GP(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
tdyp -= get_fixed_pixel_size(10);
}
if (tdyp < 5)
tdyp = 5;
set_our_eip(152);
if ((useview >= 0) && (_GP(game).options[OPT_SPEECHTYPE] > 0)) {
// Sierra-style close-up portrait
if (_GP(play).swap_portrait_lastchar != aschar) {
// if the portraits are set to Alternate, OR they are
// set to Left but swap_portrait has been set to 1 (the old
// method for enabling it), then swap them round
if ((_GP(game).options[OPT_PORTRAITSIDE] == PORTRAIT_ALTERNATE) ||
((_GP(game).options[OPT_PORTRAITSIDE] == 0) &&
(_GP(play).swap_portrait_side > 0))) {
if (_GP(play).swap_portrait_side == 2)
_GP(play).swap_portrait_side = 1;
else
_GP(play).swap_portrait_side = 2;
}
if (_GP(game).options[OPT_PORTRAITSIDE] == PORTRAIT_XPOSITION) {
// Portrait side based on character X-positions
if (_GP(play).swap_portrait_lastchar < 0) {
// No previous character been spoken to
// therefore, assume it's the player
if (_GP(game).playercharacter != aschar && _GP(game).chars[_GP(game).playercharacter].room == speakingChar->room && _GP(game).chars[_GP(game).playercharacter].on == 1)
_GP(play).swap_portrait_lastchar = _GP(game).playercharacter;
else
// The player's not here. Find another character in this room
// that it could be
for (int ce = 0; ce < _GP(game).numcharacters; ce++) {
if ((_GP(game).chars[ce].room == speakingChar->room) &&
(_GP(game).chars[ce].on == 1) &&
(ce != aschar)) {
_GP(play).swap_portrait_lastchar = ce;
break;
}
}
}
if (_GP(play).swap_portrait_lastchar >= 0) {
// if this character is right of the one before, put the
// portrait on the right
if (speakingChar->x > _GP(game).chars[_GP(play).swap_portrait_lastchar].x)
_GP(play).swap_portrait_side = -1;
else
_GP(play).swap_portrait_side = 0;
}
}
_GP(play).swap_portrait_lastlastchar = _GP(play).swap_portrait_lastchar;
_GP(play).swap_portrait_lastchar = aschar;
} else
// If the portrait side is based on the character's X position and the same character is
// speaking, compare against the previous *previous* character to see where the speech should be
if (_GP(game).options[OPT_PORTRAITSIDE] == PORTRAIT_XPOSITION && _GP(play).swap_portrait_lastlastchar >= 0) {
if (speakingChar->x > _GP(game).chars[_GP(play).swap_portrait_lastlastchar].x)
_GP(play).swap_portrait_side = -1;
else
_GP(play).swap_portrait_side = 0;
}
// Determine whether to display the portrait on the left or right
int portrait_on_right = 0;
if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
} // always on left with QFG-style speech
else if ((_GP(play).swap_portrait_side == 1) ||
(_GP(play).swap_portrait_side == -1) ||
(_GP(game).options[OPT_PORTRAITSIDE] == PORTRAIT_RIGHT))
portrait_on_right = 1;
int bigx = 0, bigy = 0, kk;
ViewStruct *viptr = &_GP(views)[useview];
for (kk = 0; kk < viptr->loops[0].numFrames; kk++) {
int tw = _GP(game).SpriteInfos[viptr->loops[0].frames[kk].pic].Width;
if (tw > bigx) bigx = tw;
tw = _GP(game).SpriteInfos[viptr->loops[0].frames[kk].pic].Height;
if (tw > bigy) bigy = tw;
}
// if they accidentally used a large full-screen image as the sierra-style
// talk view, correct it
if ((_GP(game).options[OPT_SPEECHTYPE] != 3) && (bigx > ui_view.GetWidth() - get_fixed_pixel_size(50)))
bigx = ui_view.GetWidth() - get_fixed_pixel_size(50);
if (widd > 0)
bwidth = widd - bigx;
set_our_eip(153);
int ovr_yp = get_fixed_pixel_size(20);
int view_frame_x = 0;
int view_frame_y = 0;
_G(facetalk_qfg4_override_placement_x) = false;
_G(facetalk_qfg4_override_placement_y) = false;
if (_GP(game).options[OPT_SPEECHTYPE] == 3) {
// QFG4-style whole screen picture
closeupface = BitmapHelper::CreateBitmap(ui_view.GetWidth(), ui_view.GetHeight());
closeupface->Clear(0);
if (xx < 0 && _GP(play).speech_portrait_placement) {
_G(facetalk_qfg4_override_placement_x) = true;
view_frame_x = _GP(play).speech_portrait_x;
}
if (yy < 0 && _GP(play).speech_portrait_placement) {
_G(facetalk_qfg4_override_placement_y) = true;
view_frame_y = _GP(play).speech_portrait_y;
} else {
view_frame_y = ui_view.GetHeight() / 2 - _GP(game).SpriteInfos[viptr->loops[0].frames[0].pic].Height / 2;
}
bigx = ui_view.GetWidth() / 2 - get_fixed_pixel_size(20);
ovr_type = OVER_COMPLETE;
ovr_yp = 0;
tdyp = -1; // center vertically
} else {
// KQ6-style close-up face picture
if (yy < 0 && _GP(play).speech_portrait_placement) {
ovr_yp = _GP(play).speech_portrait_y;
} else if (yy < 0)
ovr_yp = adjust_y_for_guis(ovr_yp, true /* displayspeech is always blocking */);
else
ovr_yp = yy;
closeupface = BitmapHelper::CreateTransparentBitmap(bigx + 1, bigy + 1);
ovr_type = OVER_PICTURE;
if (yy < 0)
tdyp = ovr_yp + get_textwindow_top_border_height(_GP(play).speech_textwindow_gui);
}
const ViewFrame *vf = &viptr->loops[0].frames[0];
const bool closeupface_has_alpha = (_GP(game).SpriteInfos[vf->pic].Flags & SPF_ALPHACHANNEL) != 0;
DrawViewFrame(closeupface, vf, view_frame_x, view_frame_y);
int overlay_x = get_fixed_pixel_size(10);
if (xx < 0) {
tdxp = bigx + get_textwindow_border_width(_GP(play).speech_textwindow_gui) / 2;
if (_GP(play).speech_portrait_placement) {
overlay_x = _GP(play).speech_portrait_x;
tdxp += overlay_x + get_fixed_pixel_size(6);
} else {
tdxp += get_fixed_pixel_size(16);
}
int maxWidth = (ui_view.GetWidth() - tdxp) - get_fixed_pixel_size(5) -
get_textwindow_border_width(_GP(play).speech_textwindow_gui) / 2;
if (bwidth > maxWidth)
bwidth = maxWidth;
} else {
tdxp = xx + bigx + get_fixed_pixel_size(8);
overlay_x = xx;
}
// allow the text box to be shrunk to fit the text
allowShrink = 1;
// if the portrait's on the right, swap it round
if (portrait_on_right) {
if ((xx < 0) || (widd < 0)) {
tdxp = get_fixed_pixel_size(9);
if (_GP(play).speech_portrait_placement) {
overlay_x = (ui_view.GetWidth() - bigx) - _GP(play).speech_portrait_x;
int maxWidth = overlay_x - tdxp - get_fixed_pixel_size(9) -
get_textwindow_border_width(_GP(play).speech_textwindow_gui) / 2;
if (bwidth > maxWidth)
bwidth = maxWidth;
} else {
overlay_x = (ui_view.GetWidth() - bigx) - get_fixed_pixel_size(5);
}
} else {
overlay_x = (xx + widd - bigx) - get_fixed_pixel_size(5);
tdxp = xx;
}
tdxp += get_textwindow_border_width(_GP(play).speech_textwindow_gui) / 2;
allowShrink = 2;
}
if (_GP(game).options[OPT_SPEECHTYPE] == 3)
overlay_x = 0;
_G(face_talking) = add_screen_overlay(false, overlay_x, ovr_yp, ovr_type, closeupface, closeupface_has_alpha);
_G(facetalkview) = useview;
_G(facetalkloop) = 0;
_G(facetalkframe) = 0;
_G(facetalkwait) = viptr->loops[0].frames[0].speed + GetCharacterSpeechAnimationDelay(speakingChar);
_G(facetalkrepeat) = (isThought) ? 0 : 1;
_G(facetalkBlinkLoop) = 0;
_G(facetalkAllowBlink) = 1;
if ((isThought) && (speakingChar->flags & CHF_NOBLINKANDTHINK))
_G(facetalkAllowBlink) = 0;
_G(facetalkchar) = &_GP(game).chars[aschar];
if (_G(facetalkchar)->blinktimer < 0)
_G(facetalkchar)->blinktimer = _G(facetalkchar)->blinkinterval;
textcol = -textcol;
overlayPositionFixed = true;
// Process the first portrait view frame
const int frame_vol = _GP(charextra)[_G(facetalkchar)->index_id].GetFrameSoundVolume(_G(facetalkchar));
CheckViewFrame(_G(facetalkview), _G(facetalkloop), _G(facetalkframe), frame_vol);
} else if (useview >= 0) {
// Lucasarts-style speech
set_our_eip(154);
oldview = speakingChar->view;
oldloop = speakingChar->loop;
speakingChar->set_animating(!isThought, // only repeat if speech, not thought
true, // always forwards
GetCharacterSpeechAnimationDelay(speakingChar));
speakingChar->view = useview;
speakingChar->frame = 0;
speakingChar->flags |= CHF_FIXVIEW;
if ((speakingChar->view < 0) || _GP(views)[speakingChar->view].numLoops == 0)
quitprintf("!Character %s speech view %d is invalid, or has no loops.", speakingChar->scrname, speakingChar->view + 1);
// If speech view is missing a loop - use loop 0
if (speakingChar->loop >= _GP(views)[speakingChar->view].numLoops) {
debug_script_warn("WARNING: Character %s speech view %d does not have necessary loop %d; switching to loop 0.",
speakingChar->scrname, speakingChar->view + 1, speakingChar->loop);
speakingChar->loop = 0;
}
_G(facetalkBlinkLoop) = speakingChar->loop;
// set up the speed of the first frame
speakingChar->wait = GetCharacterSpeechAnimationDelay(speakingChar) +
_GP(views)[speakingChar->view].loops[speakingChar->loop].frames[0].speed;
if (widd < 0) {
bwidth = ui_view.GetWidth() / 2 + ui_view.GetWidth() / 6;
// If they are close to the screen edge, make the text narrower
int relx = _GP(play).RoomToScreenX(data_to_game_coord(speakingChar->x));
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
_G(char_speaking) = aschar;
}
} else
allowShrink = 1;
// it wants the centred position, so make it so
if ((xx >= 0) && (tdxp < 0))
tdxp -= widd / 2;
// if they used DisplaySpeechAt, then use the supplied width
if ((widd > 0) && (isThought == 0))
allowShrink = 0;
if (isThought)
_G(char_thinking) = aschar;
set_our_eip(155);
display_main(tdxp, tdyp, bwidth, texx, DISPLAYTEXT_SPEECH, FONT_SPEECH, textcol, isThought, allowShrink, overlayPositionFixed);
if (_G(abort_engine))
return;
set_our_eip(156);
if ((_GP(play).in_conversation > 0) && (_GP(game).options[OPT_SPEECHTYPE] == 3))
closeupface = nullptr;
if (closeupface != nullptr)
remove_screen_overlay(ovr_type);
mark_screen_dirty();
_G(face_talking) = -1;
_G(facetalkchar) = nullptr;
set_our_eip(157);
if (oldview >= 0) {
speakingChar->flags &= ~CHF_FIXVIEW;
if (viewWasLocked)
speakingChar->flags |= CHF_FIXVIEW;
speakingChar->view = oldview;
// Don't reset the loop in 2.x games
if (_G(loaded_game_file_version) > kGameVersion_272)
speakingChar->loop = oldloop;
stop_character_anim(speakingChar);
speakingChar->frame = charFrameWas;
speakingChar->wait = 0;
speakingChar->idleleft = speakingChar->idletime;
// restart the idle animation straight away
_GP(charextra)[aschar].process_idle_this_time = 1;
}
_G(char_speaking) = -1;
_G(char_thinking) = -1;
// Stop any blocking voice-over, if was started by this function
if (_GP(play).IsBlockingVoiceSpeech())
stop_voice_speech();
}
int get_character_currently_talking() {
if ((_G(face_talking) >= 0) && (_G(facetalkrepeat)))
return _G(facetalkchar)->index_id;
else if (_G(char_speaking) >= 0)
return _G(char_speaking);
return -1;
}
void DisplaySpeech(const char *texx, int aschar) {
_displayspeech(texx, aschar, -1, -1, -1, 0);
}
// Calculate which frame of the loop to use for this character of
// speech
int GetLipSyncFrame(const char *curtex, int *stroffs) {
/*char *frameletters[MAXLIPSYNCFRAMES] =
{"./,/ ", "A", "O", "F/V", "D/N/G/L/R", "B/P/M",
"Y/H/K/Q/C", "I/T/E/X/th", "U/W", "S/Z/J/ch", NULL,
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};*/
int bestfit_len = 0, bestfit = _GP(game).default_lipsync_frame;
for (int aa = 0; aa < MAXLIPSYNCFRAMES; aa++) {
char *tptr = _GP(game).lipSyncFrameLetters[aa];
while (tptr[0] != 0) {
int lenthisbit = strlen(tptr);
if (strchr(tptr, '/'))
lenthisbit = strchr(tptr, '/') - tptr;
if ((ags_strnicmp(curtex, tptr, lenthisbit) == 0) && (lenthisbit > bestfit_len)) {
bestfit = aa;
bestfit_len = lenthisbit;
}
tptr += lenthisbit;
while (tptr[0] == '/')
tptr++;
}
}
// If it's an unknown character, use the default frame
if (bestfit_len == 0)
bestfit_len = 1;
*stroffs += bestfit_len;
return bestfit;
}
int update_lip_sync(int talkview, int talkloop, int *talkframeptr) {
int talkframe = talkframeptr[0];
int talkwait = 0;
// lip-sync speech
const char *nowsaying = &_G(text_lips_text)[_G(text_lips_offset)];
// if it's an apostraphe, skip it (we'll, I'll, etc)
if (nowsaying[0] == '\'') {
_G(text_lips_offset)++;
nowsaying++;
}
if (_G(text_lips_offset) >= (int)strlen(_G(text_lips_text)))
talkframe = 0;
else {
talkframe = GetLipSyncFrame(nowsaying, &_G(text_lips_offset));
if (talkframe >= _GP(views)[talkview].loops[talkloop].numFrames)
talkframe = 0;
}
talkwait = _G(loops_per_character) + _GP(views)[talkview].loops[talkloop].frames[talkframe].speed;
talkframeptr[0] = talkframe;
return talkwait;
}
void restore_characters() {
for (int i = 0; i < _GP(game).numcharacters; ++i) {
_GP(charextra)[i].zoom_offs = (_GP(game).options[OPT_SCALECHAROFFSETS] != 0) ? _GP(charextra)[i].zoom : 100;
}
}
Rect GetCharacterRoomBBox(int charid, bool use_frame_0) {
int width, height;
const CharacterExtras &chex = _GP(charextra)[charid];
const CharacterInfo &chin = _GP(game).chars[charid];
int frame = use_frame_0 ? 0 : chin.frame;
int pic = _GP(views)[chin.view].loops[chin.loop].frames[frame].pic;
scale_sprite_size(pic, chex.zoom, &width, &height);
return RectWH(chin.x - width / 2, chin.y - height, width, height);
}
PViewport FindNearestViewport(int charid) {
Rect bbox = GetCharacterRoomBBox(charid, true);
float min_dist = -1.f;
PViewport nearest_view;
for (int i = 0; i < _GP(play).GetRoomViewportCount(); ++i) {
auto view = _GP(play).GetRoomViewport(i);
if (!view->IsVisible())
continue;
auto cam = view->GetCamera();
if (!cam)
continue;
Rect camr = cam->GetRect();
float dist = DistanceBetween(bbox, camr);
if (dist == 0.f)
return view;
if (min_dist < 0.f || dist < min_dist) {
min_dist = dist;
nearest_view = view;
}
}
return nearest_view ? nearest_view : _GP(play).GetRoomViewport(0);
}
//=============================================================================
//
// Script API Functions
//
//=============================================================================
CharacterInfo *Character_GetByName(const char *name) {
return static_cast<CharacterInfo *>(ccGetScriptObjectAddress(name, _GP(ccDynamicCharacter).GetType()));
}
RuntimeScriptValue Sc_Character_GetByName(const RuntimeScriptValue *params, int32_t param_count) {
API_SCALL_OBJ_POBJ(CharacterInfo, _GP(ccDynamicCharacter), Character_GetByName, const char);
}
// void | CharacterInfo *chaa, ScriptInvItem *invi, int addIndex
RuntimeScriptValue Sc_Character_AddInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_AddInventory, ScriptInvItem);
}
// void | CharacterInfo *chaa, int x, int y
RuntimeScriptValue Sc_Character_AddWaypoint(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_AddWaypoint);
}
// void | CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction
RuntimeScriptValue Sc_Character_Animate5(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Animate5);
}
RuntimeScriptValue Sc_Character_Animate6(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT6(CharacterInfo, Character_Animate6);
}
RuntimeScriptValue Sc_Character_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT7(CharacterInfo, Character_Animate);
}
// void | CharacterInfo *chaa, int room, int x, int y
RuntimeScriptValue Sc_Character_ChangeRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_ChangeRoom);
}
RuntimeScriptValue Sc_Character_ChangeRoomSetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_ChangeRoomSetLoop);
}
// void | CharacterInfo *chaa, int room, int newPos
RuntimeScriptValue Sc_Character_ChangeRoomAutoPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_ChangeRoomAutoPosition);
}
// void | CharacterInfo *chap, int vii
RuntimeScriptValue Sc_Character_ChangeView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_ChangeView);
}
// void | CharacterInfo *char1, CharacterInfo *char2, int blockingStyle
RuntimeScriptValue Sc_Character_FaceCharacter(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_FaceCharacter, CharacterInfo);
}
// void | CharacterInfo *char1, int direction, int blockingStyle
RuntimeScriptValue Sc_Character_FaceDirection(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_FaceDirection);
}
// void | CharacterInfo *char1, int xx, int yy, int blockingStyle
RuntimeScriptValue Sc_Character_FaceLocation(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_FaceLocation);
}
// void | CharacterInfo *char1, ScriptObject *obj, int blockingStyle
RuntimeScriptValue Sc_Character_FaceObject(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_FaceObject, ScriptObject);
}
// void | CharacterInfo *chaa, CharacterInfo *tofollow, int distaway, int eagerness
RuntimeScriptValue Sc_Character_FollowCharacter(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ_PINT2(CharacterInfo, Character_FollowCharacter, CharacterInfo);
}
// int (CharacterInfo *chaa, const char *property)
RuntimeScriptValue Sc_Character_GetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT_POBJ(CharacterInfo, Character_GetProperty, const char);
}
// void (CharacterInfo *chaa, const char *property, char *bufer)
RuntimeScriptValue Sc_Character_GetPropertyText(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ2(CharacterInfo, Character_GetPropertyText, const char, char);
}
// const char* (CharacterInfo *chaa, const char *property)
RuntimeScriptValue Sc_Character_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_OBJ_POBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetTextProperty, const char);
}
RuntimeScriptValue Sc_Character_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_BOOL_POBJ_PINT(CharacterInfo, Character_SetProperty, const char);
}
RuntimeScriptValue Sc_Character_SetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_BOOL_POBJ2(CharacterInfo, Character_SetTextProperty, const char, const char);
}
// int (CharacterInfo *chaa, ScriptInvItem *invi)
RuntimeScriptValue Sc_Character_HasInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT_POBJ(CharacterInfo, Character_HasInventory, ScriptInvItem);
}
// int (CharacterInfo *char1, CharacterInfo *char2)
RuntimeScriptValue Sc_Character_IsCollidingWithChar(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT_POBJ(CharacterInfo, Character_IsCollidingWithChar, CharacterInfo);
}
// int (CharacterInfo *chin, ScriptObject *objid)
RuntimeScriptValue Sc_Character_IsCollidingWithObject(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT_POBJ(CharacterInfo, Character_IsCollidingWithObject, ScriptObject);
}
RuntimeScriptValue Sc_Character_IsInteractionAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_BOOL_PINT(CharacterInfo, Character_IsInteractionAvailable);
}
// void (CharacterInfo *chap, int vii)
RuntimeScriptValue Sc_Character_LockView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_LockView);
}
// void (CharacterInfo *chap, int vii, int stopMoving)
RuntimeScriptValue Sc_Character_LockViewEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_LockViewEx);
}
// void (CharacterInfo *chap, int vii, int loop, int align)
RuntimeScriptValue Sc_Character_LockViewAligned_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewAligned_Old);
}
// void (CharacterInfo *chap, int vii, int loop, int align, int stopMoving)
RuntimeScriptValue Sc_Character_LockViewAlignedEx_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewAlignedEx_Old);
}
RuntimeScriptValue Sc_Character_LockViewAligned(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewAligned);
}
RuntimeScriptValue Sc_Character_LockViewAlignedEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewAlignedEx);
}
// void (CharacterInfo *chaa, int view, int loop, int frame)
RuntimeScriptValue Sc_Character_LockViewFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewFrame);
}
// void (CharacterInfo *chaa, int view, int loop, int frame, int stopMoving)
RuntimeScriptValue Sc_Character_LockViewFrameEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewFrameEx);
}
// void (CharacterInfo *chap, int vii, int xoffs, int yoffs)
RuntimeScriptValue Sc_Character_LockViewOffset(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewOffset);
}
// void (CharacterInfo *chap, int vii, int xoffs, int yoffs, int stopMoving)
RuntimeScriptValue Sc_Character_LockViewOffsetEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewOffsetEx);
}
// void (CharacterInfo *chap, ScriptInvItem *invi)
RuntimeScriptValue Sc_Character_LoseInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ(CharacterInfo, Character_LoseInventory, ScriptInvItem);
}
// void (CharacterInfo *chaa, int x, int y, int blocking, int direct)
RuntimeScriptValue Sc_Character_Move(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_Move);
}
// void (CharacterInfo *chap)
RuntimeScriptValue Sc_Character_PlaceOnWalkableArea(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID(CharacterInfo, Character_PlaceOnWalkableArea);
}
// void (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_RemoveTint(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID(CharacterInfo, Character_RemoveTint);
}
// void (CharacterInfo *chaa, int mood)
RuntimeScriptValue Sc_Character_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_RunInteraction);
}
// void (CharacterInfo *chaa, const char *texx, ...)
RuntimeScriptValue Sc_Character_Say(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_SCRIPT_SPRINTF(Character_Say, 1);
Character_Say((CharacterInfo *)self, scsf_buffer);
return RuntimeScriptValue((int32_t)0);
}
RuntimeScriptValue Sc_Character_SayAt(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_SCRIPT_SPRINTF(Character_SayAt, 4);
Character_SayAt((CharacterInfo *)self, params[0].IValue, params[1].IValue, params[2].IValue, scsf_buffer);
return RuntimeScriptValue((int32_t)0);
}
RuntimeScriptValue Sc_Character_SayBackground(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_SCRIPT_SPRINTF(Character_SayBackground, 1);
auto *ret_obj = Character_SayBackground((CharacterInfo *)self, scsf_buffer);
return RuntimeScriptValue().SetScriptObject(ret_obj, ret_obj);
}
// void (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_SetAsPlayer(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID(CharacterInfo, Character_SetAsPlayer);
}
// void (CharacterInfo *chaa, int iview, int itime)
RuntimeScriptValue Sc_Character_SetIdleView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetIdleView);
}
RuntimeScriptValue Sc_Character_GetHasExplicitLight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_BOOL(CharacterInfo, Character_GetHasExplicitLight);
}
RuntimeScriptValue Sc_Character_GetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetLightLevel);
}
RuntimeScriptValue Sc_Character_SetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetLightLevel);
}
RuntimeScriptValue Sc_Character_GetTintBlue(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTintBlue);
}
RuntimeScriptValue Sc_Character_GetTintGreen(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTintGreen);
}
RuntimeScriptValue Sc_Character_GetTintRed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTintRed);
}
RuntimeScriptValue Sc_Character_GetTintSaturation(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTintSaturation);
}
RuntimeScriptValue Sc_Character_GetTintLuminance(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTintLuminance);
}
// void (CharacterInfo *chaa, int xspeed, int yspeed)
RuntimeScriptValue Sc_Character_SetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetSpeed);
}
// void (CharacterInfo *charp)
RuntimeScriptValue Sc_Character_StopMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID(CharacterInfo, Character_StopMoving);
}
// void (CharacterInfo *chaa, const char *texx, ...)
RuntimeScriptValue Sc_Character_Think(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_SCRIPT_SPRINTF(Character_Think, 1);
Character_Think((CharacterInfo *)self, scsf_buffer);
return RuntimeScriptValue((int32_t)0);
}
//void (CharacterInfo *chaa, int red, int green, int blue, int opacity, int luminance)
RuntimeScriptValue Sc_Character_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Tint);
}
// void (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_UnlockView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID(CharacterInfo, Character_UnlockView);
}
// void (CharacterInfo *chaa, int stopMoving)
RuntimeScriptValue Sc_Character_UnlockViewEx(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_UnlockViewEx);
}
// void (CharacterInfo *chaa, int x, int y, int blocking, int direct)
RuntimeScriptValue Sc_Character_Walk(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT4(CharacterInfo, Character_Walk);
}
// void (CharacterInfo *chaa, int xx, int yy, int blocking)
RuntimeScriptValue Sc_Character_WalkStraight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT3(CharacterInfo, Character_WalkStraight);
}
RuntimeScriptValue Sc_GetCharacterAtRoom(const RuntimeScriptValue *params, int32_t param_count) {
API_SCALL_OBJ_PINT2(CharacterInfo, _GP(ccDynamicCharacter), GetCharacterAtRoom);
}
// CharacterInfo *(int xx, int yy)
RuntimeScriptValue Sc_GetCharacterAtScreen(const RuntimeScriptValue *params, int32_t param_count) {
API_SCALL_OBJ_PINT2(CharacterInfo, _GP(ccDynamicCharacter), GetCharacterAtScreen);
}
// ScriptInvItem* (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetActiveInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_OBJ(CharacterInfo, ScriptInvItem, _GP(ccDynamicInv), Character_GetActiveInventory);
}
// void (CharacterInfo *chaa, ScriptInvItem* iit)
RuntimeScriptValue Sc_Character_SetActiveInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ(CharacterInfo, Character_SetActiveInventory, ScriptInvItem);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetAnimating);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetAnimationSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetAnimationSpeed);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetAnimationSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetAnimationSpeed);
}
RuntimeScriptValue Sc_Character_GetAnimationVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetAnimationVolume);
}
RuntimeScriptValue Sc_Character_SetAnimationVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetAnimationVolume);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBaseline);
}
// void (CharacterInfo *chaa, int basel)
RuntimeScriptValue Sc_Character_SetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBaseline);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBlinkInterval(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBlinkInterval);
}
// void (CharacterInfo *chaa, int interval)
RuntimeScriptValue Sc_Character_SetBlinkInterval(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkInterval);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBlinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBlinkView);
}
// void (CharacterInfo *chaa, int vii)
RuntimeScriptValue Sc_Character_SetBlinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkView);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBlinkWhileThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBlinkWhileThinking);
}
// void (CharacterInfo *chaa, int yesOrNo)
RuntimeScriptValue Sc_Character_SetBlinkWhileThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkWhileThinking);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBlockingHeight);
}
// void (CharacterInfo *chaa, int hit)
RuntimeScriptValue Sc_Character_SetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlockingHeight);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetBlockingWidth);
}
// void (CharacterInfo *chaa, int wid)
RuntimeScriptValue Sc_Character_SetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlockingWidth);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetClickable);
}
// void (CharacterInfo *chaa, int clik)
RuntimeScriptValue Sc_Character_SetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetClickable);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetDiagonalWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetDiagonalWalking);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetDiagonalWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetDiagonalWalking);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetFrame);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetFrame);
}
RuntimeScriptValue Sc_Character_GetHasExplicitTint_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetHasExplicitTint_Old);
}
RuntimeScriptValue Sc_Character_GetHasExplicitTint(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetHasExplicitTint);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetID);
}
RuntimeScriptValue Sc_Character_GetScriptName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_OBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetScriptName);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetIdleView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetIdleView);
}
// int (CharacterInfo *chaa, int index)
RuntimeScriptValue Sc_Character_GetIInventoryQuantity(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT_PINT(CharacterInfo, Character_GetIInventoryQuantity);
}
// void (CharacterInfo *chaa, int index, int quant)
RuntimeScriptValue Sc_Character_SetIInventoryQuantity(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetIInventoryQuantity);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetIgnoreLighting(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreLighting);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetIgnoreLighting(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreLighting);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreScaling);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreScaling);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreWalkbehinds);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreWalkbehinds);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetLoop);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetLoop);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetManualScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetManualScaling);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetMovementLinkedToAnimation(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetMovementLinkedToAnimation);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetMovementLinkedToAnimation(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetMovementLinkedToAnimation);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetMoving);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetDestinationX(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetDestinationX);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetDestinationY(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetDestinationY);
}
// const char* (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_OBJ(CharacterInfo, const char, _GP(myScriptStringImpl), Character_GetName);
}
// void (CharacterInfo *chaa, const char *newName)
RuntimeScriptValue Sc_Character_SetName(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_POBJ(CharacterInfo, Character_SetName, const char);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetNormalView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetNormalView);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetPreviousRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetPreviousRoom);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetRoom);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetScaleMoveSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetScaleMoveSpeed);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetScaleMoveSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaleMoveSpeed);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetScaleVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetScaleVolume);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetScaleVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaleVolume);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetScaling);
}
// void (CharacterInfo *chaa, int zoomlevel)
RuntimeScriptValue Sc_Character_SetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaling);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetSolid);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSolid);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetSpeaking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetSpeaking);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetSpeakingFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetSpeakingFrame);
}
// int (CharacterInfo *cha)
RuntimeScriptValue Sc_GetCharacterSpeechAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, GetCharacterSpeechAnimationDelay);
}
// void (CharacterInfo *chaa, int newDelay)
RuntimeScriptValue Sc_Character_SetSpeechAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechAnimationDelay);
}
RuntimeScriptValue Sc_Character_GetIdleAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetIdleAnimationDelay);
}
// void (CharacterInfo *chaa, int newDelay)
RuntimeScriptValue Sc_Character_SetIdleAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIdleAnimationDelay);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetSpeechColor(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetSpeechColor);
}
// void (CharacterInfo *chaa, int ncol)
RuntimeScriptValue Sc_Character_SetSpeechColor(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechColor);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetSpeechView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetSpeechView);
}
// void (CharacterInfo *chaa, int vii)
RuntimeScriptValue Sc_Character_SetSpeechView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechView);
}
RuntimeScriptValue Sc_Character_GetThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_BOOL(CharacterInfo, Character_GetThinking);
}
RuntimeScriptValue Sc_Character_GetThinkingFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetThinkingFrame);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetThinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetThinkView);
}
// void (CharacterInfo *chaa, int vii)
RuntimeScriptValue Sc_Character_SetThinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetThinkView);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTransparency);
}
// void (CharacterInfo *chaa, int trans)
RuntimeScriptValue Sc_Character_SetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetTransparency);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetTurnBeforeWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetTurnBeforeWalking);
}
// void (CharacterInfo *chaa, int yesorno)
RuntimeScriptValue Sc_Character_SetTurnBeforeWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetTurnBeforeWalking);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetView);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetWalkSpeedX(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetWalkSpeedX);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetWalkSpeedY(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetWalkSpeedY);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetX);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetX);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetY);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetY);
}
// int (CharacterInfo *chaa)
RuntimeScriptValue Sc_Character_GetZ(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_INT(CharacterInfo, Character_GetZ);
}
// void (CharacterInfo *chaa, int newval)
RuntimeScriptValue Sc_Character_SetZ(void *self, const RuntimeScriptValue *params, int32_t param_count) {
API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetZ);
}
//=============================================================================
//
// Exclusive variadic API implementation for Plugins
//
//=============================================================================
void ScPl_Character_Say(CharacterInfo *chaa, const char *texx, ...) {
API_PLUGIN_SCRIPT_SPRINTF(texx);
Character_Say(chaa, scsf_buffer);
}
void ScPl_Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx, ...) {
API_PLUGIN_SCRIPT_SPRINTF(texx);
Character_SayAt(chaa, x, y, width, scsf_buffer);
}
ScriptOverlay *ScPl_Character_SayBackground(CharacterInfo *chaa, const char *texx, ...) {
API_PLUGIN_SCRIPT_SPRINTF(texx);
return Character_SayBackground(chaa, scsf_buffer);
}
void ScPl_Character_Think(CharacterInfo *chaa, const char *texx, ...) {
API_PLUGIN_SCRIPT_SPRINTF(texx);
Character_Think(chaa, scsf_buffer);
}
void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion /* compat_api */) {
ScFnRegister character_api[] = {
{"Character::GetAtRoomXY^2", API_FN_PAIR(GetCharacterAtRoom)},
{"Character::GetAtScreenXY^2", API_FN_PAIR(GetCharacterAtScreen)},
{"Character::GetByName", API_FN_PAIR(Character_GetByName)},
{"Character::AddInventory^2", API_FN_PAIR(Character_AddInventory)},
{"Character::AddWaypoint^2", API_FN_PAIR(Character_AddWaypoint)},
{"Character::Animate^5", API_FN_PAIR(Character_Animate5)},
{"Character::Animate^6", API_FN_PAIR(Character_Animate6)},
{"Character::Animate^7", API_FN_PAIR(Character_Animate)},
{"Character::ChangeRoom^3", API_FN_PAIR(Character_ChangeRoom)},
{"Character::ChangeRoom^4", API_FN_PAIR(Character_ChangeRoomSetLoop)},
{"Character::ChangeRoomAutoPosition^2", API_FN_PAIR(Character_ChangeRoomAutoPosition)},
{"Character::ChangeView^1", API_FN_PAIR(Character_ChangeView)},
{"Character::FaceCharacter^2", API_FN_PAIR(Character_FaceCharacter)},
{"Character::FaceDirection^2", API_FN_PAIR(Character_FaceDirection)},
{"Character::FaceLocation^3", API_FN_PAIR(Character_FaceLocation)},
{"Character::FaceObject^2", API_FN_PAIR(Character_FaceObject)},
{"Character::FollowCharacter^3", API_FN_PAIR(Character_FollowCharacter)},
{"Character::GetProperty^1", API_FN_PAIR(Character_GetProperty)},
{"Character::GetPropertyText^2", API_FN_PAIR(Character_GetPropertyText)},
{"Character::GetTextProperty^1", API_FN_PAIR(Character_GetTextProperty)},
{"Character::SetProperty^2", API_FN_PAIR(Character_SetProperty)},
{"Character::SetTextProperty^2", API_FN_PAIR(Character_SetTextProperty)},
{"Character::HasInventory^1", API_FN_PAIR(Character_HasInventory)},
{"Character::IsCollidingWithChar^1", API_FN_PAIR(Character_IsCollidingWithChar)},
{"Character::IsCollidingWithObject^1", API_FN_PAIR(Character_IsCollidingWithObject)},
{"Character::IsInteractionAvailable^1", API_FN_PAIR(Character_IsInteractionAvailable)},
{"Character::LockView^1", API_FN_PAIR(Character_LockView)},
{"Character::LockView^2", API_FN_PAIR(Character_LockViewEx)},
{"Character::LockViewFrame^3", API_FN_PAIR(Character_LockViewFrame)},
{"Character::LockViewFrame^4", API_FN_PAIR(Character_LockViewFrameEx)},
{"Character::LockViewOffset^3", API_FN_PAIR(Character_LockViewOffset)},
{"Character::LockViewOffset^4", API_FN_PAIR(Character_LockViewOffsetEx)},
{"Character::LoseInventory^1", API_FN_PAIR(Character_LoseInventory)},
{"Character::Move^4", API_FN_PAIR(Character_Move)},
{"Character::PlaceOnWalkableArea^0", API_FN_PAIR(Character_PlaceOnWalkableArea)},
{"Character::RemoveTint^0", API_FN_PAIR(Character_RemoveTint)},
{"Character::RunInteraction^1", API_FN_PAIR(Character_RunInteraction)},
{"Character::Say^101", Sc_Character_Say},
// old non-variadic variants
{"Character::SayAt^4", API_FN_PAIR(Character_SayAt)},
{"Character::SayBackground^1", API_FN_PAIR(Character_SayBackground)},
// newer variadic variants
{"Character::SayAt^104", Sc_Character_SayAt},
{"Character::SayBackground^101", Sc_Character_SayBackground},
{"Character::SetAsPlayer^0", API_FN_PAIR(Character_SetAsPlayer)},
{"Character::SetIdleView^2", API_FN_PAIR(Character_SetIdleView)},
{"Character::SetLightLevel^1", API_FN_PAIR(Character_SetLightLevel)},
{"Character::SetWalkSpeed^2", API_FN_PAIR(Character_SetSpeed)},
{"Character::StopMoving^0", API_FN_PAIR(Character_StopMoving)},
{"Character::Think^101", Sc_Character_Think},
{"Character::Tint^5", API_FN_PAIR(Character_Tint)},
{"Character::UnlockView^0", API_FN_PAIR(Character_UnlockView)},
{"Character::UnlockView^1", API_FN_PAIR(Character_UnlockViewEx)},
{"Character::Walk^4", API_FN_PAIR(Character_Walk)},
{"Character::WalkStraight^3", API_FN_PAIR(Character_WalkStraight)},
{"Character::get_ActiveInventory", API_FN_PAIR(Character_GetActiveInventory)},
{"Character::set_ActiveInventory", API_FN_PAIR(Character_SetActiveInventory)},
{"Character::get_Animating", API_FN_PAIR(Character_GetAnimating)},
{"Character::get_AnimationSpeed", API_FN_PAIR(Character_GetAnimationSpeed)},
{"Character::set_AnimationSpeed", API_FN_PAIR(Character_SetAnimationSpeed)},
{"Character::get_AnimationVolume", API_FN_PAIR(Character_GetAnimationVolume)},
{"Character::set_AnimationVolume", API_FN_PAIR(Character_SetAnimationVolume)},
{"Character::get_Baseline", API_FN_PAIR(Character_GetBaseline)},
{"Character::set_Baseline", API_FN_PAIR(Character_SetBaseline)},
{"Character::get_BlinkInterval", API_FN_PAIR(Character_GetBlinkInterval)},
{"Character::set_BlinkInterval", API_FN_PAIR(Character_SetBlinkInterval)},
{"Character::get_BlinkView", API_FN_PAIR(Character_GetBlinkView)},
{"Character::set_BlinkView", API_FN_PAIR(Character_SetBlinkView)},
{"Character::get_BlinkWhileThinking", API_FN_PAIR(Character_GetBlinkWhileThinking)},
{"Character::set_BlinkWhileThinking", API_FN_PAIR(Character_SetBlinkWhileThinking)},
{"Character::get_BlockingHeight", API_FN_PAIR(Character_GetBlockingHeight)},
{"Character::set_BlockingHeight", API_FN_PAIR(Character_SetBlockingHeight)},
{"Character::get_BlockingWidth", API_FN_PAIR(Character_GetBlockingWidth)},
{"Character::set_BlockingWidth", API_FN_PAIR(Character_SetBlockingWidth)},
{"Character::get_Clickable", API_FN_PAIR(Character_GetClickable)},
{"Character::set_Clickable", API_FN_PAIR(Character_SetClickable)},
{"Character::get_DestinationX", API_FN_PAIR(Character_GetDestinationX)},
{"Character::get_DestinationY", API_FN_PAIR(Character_GetDestinationY)},
{"Character::get_DiagonalLoops", API_FN_PAIR(Character_GetDiagonalWalking)},
{"Character::set_DiagonalLoops", API_FN_PAIR(Character_SetDiagonalWalking)},
{"Character::get_Frame", API_FN_PAIR(Character_GetFrame)},
{"Character::set_Frame", API_FN_PAIR(Character_SetFrame)},
{"Character::get_ID", API_FN_PAIR(Character_GetID)},
{"Character::get_IdleView", API_FN_PAIR(Character_GetIdleView)},
{"Character::get_IdleAnimationDelay", API_FN_PAIR(Character_GetIdleAnimationDelay)},
{"Character::set_IdleAnimationDelay", API_FN_PAIR(Character_SetIdleAnimationDelay)},
{"Character::geti_InventoryQuantity", API_FN_PAIR(Character_GetIInventoryQuantity)},
{"Character::seti_InventoryQuantity", API_FN_PAIR(Character_SetIInventoryQuantity)},
{"Character::get_IgnoreLighting", API_FN_PAIR(Character_GetIgnoreLighting)},
{"Character::set_IgnoreLighting", API_FN_PAIR(Character_SetIgnoreLighting)},
{"Character::get_IgnoreScaling", API_FN_PAIR(Character_GetIgnoreScaling)},
{"Character::set_IgnoreScaling", API_FN_PAIR(Character_SetIgnoreScaling)},
{"Character::get_IgnoreWalkbehinds", API_FN_PAIR(Character_GetIgnoreWalkbehinds)},
{"Character::set_IgnoreWalkbehinds", API_FN_PAIR(Character_SetIgnoreWalkbehinds)},
{"Character::get_Loop", API_FN_PAIR(Character_GetLoop)},
{"Character::set_Loop", API_FN_PAIR(Character_SetLoop)},
{"Character::get_ManualScaling", API_FN_PAIR(Character_GetIgnoreScaling)},
{"Character::set_ManualScaling", API_FN_PAIR(Character_SetManualScaling)},
{"Character::get_MovementLinkedToAnimation", API_FN_PAIR(Character_GetMovementLinkedToAnimation)},
{"Character::set_MovementLinkedToAnimation", API_FN_PAIR(Character_SetMovementLinkedToAnimation)},
{"Character::get_Moving", API_FN_PAIR(Character_GetMoving)},
{"Character::get_Name", API_FN_PAIR(Character_GetName)},
{"Character::set_Name", API_FN_PAIR(Character_SetName)},
{"Character::get_NormalView", API_FN_PAIR(Character_GetNormalView)},
{"Character::get_PreviousRoom", API_FN_PAIR(Character_GetPreviousRoom)},
{"Character::get_Room", API_FN_PAIR(Character_GetRoom)},
{"Character::get_ScaleMoveSpeed", API_FN_PAIR(Character_GetScaleMoveSpeed)},
{"Character::set_ScaleMoveSpeed", API_FN_PAIR(Character_SetScaleMoveSpeed)},
{"Character::get_ScaleVolume", API_FN_PAIR(Character_GetScaleVolume)},
{"Character::set_ScaleVolume", API_FN_PAIR(Character_SetScaleVolume)},
{"Character::get_Scaling", API_FN_PAIR(Character_GetScaling)},
{"Character::set_Scaling", API_FN_PAIR(Character_SetScaling)},
{"Character::get_ScriptName", API_FN_PAIR(Character_GetScriptName)},
{"Character::get_Solid", API_FN_PAIR(Character_GetSolid)},
{"Character::set_Solid", API_FN_PAIR(Character_SetSolid)},
{"Character::get_Speaking", API_FN_PAIR(Character_GetSpeaking)},
{"Character::get_SpeakingFrame", API_FN_PAIR(Character_GetSpeakingFrame)},
{"Character::get_SpeechAnimationDelay", API_FN_PAIR(GetCharacterSpeechAnimationDelay)},
{"Character::set_SpeechAnimationDelay", API_FN_PAIR(Character_SetSpeechAnimationDelay)},
{"Character::get_SpeechColor", API_FN_PAIR(Character_GetSpeechColor)},
{"Character::set_SpeechColor", API_FN_PAIR(Character_SetSpeechColor)},
{"Character::get_SpeechView", API_FN_PAIR(Character_GetSpeechView)},
{"Character::set_SpeechView", API_FN_PAIR(Character_SetSpeechView)},
{"Character::get_Thinking", API_FN_PAIR(Character_GetThinking)},
{"Character::get_ThinkingFrame", API_FN_PAIR(Character_GetThinkingFrame)},
{"Character::get_ThinkView", API_FN_PAIR(Character_GetThinkView)},
{"Character::set_ThinkView", API_FN_PAIR(Character_SetThinkView)},
{"Character::get_Transparency", API_FN_PAIR(Character_GetTransparency)},
{"Character::set_Transparency", API_FN_PAIR(Character_SetTransparency)},
{"Character::get_TurnBeforeWalking", API_FN_PAIR(Character_GetTurnBeforeWalking)},
{"Character::set_TurnBeforeWalking", API_FN_PAIR(Character_SetTurnBeforeWalking)},
{"Character::get_View", API_FN_PAIR(Character_GetView)},
{"Character::get_WalkSpeedX", API_FN_PAIR(Character_GetWalkSpeedX)},
{"Character::get_WalkSpeedY", API_FN_PAIR(Character_GetWalkSpeedY)},
{"Character::get_X", API_FN_PAIR(Character_GetX)},
{"Character::set_X", API_FN_PAIR(Character_SetX)},
{"Character::get_x", API_FN_PAIR(Character_GetX)},
{"Character::set_x", API_FN_PAIR(Character_SetX)},
{"Character::get_Y", API_FN_PAIR(Character_GetY)},
{"Character::set_Y", API_FN_PAIR(Character_SetY)},
{"Character::get_y", API_FN_PAIR(Character_GetY)},
{"Character::set_y", API_FN_PAIR(Character_SetY)},
{"Character::get_Z", API_FN_PAIR(Character_GetZ)},
{"Character::set_Z", API_FN_PAIR(Character_SetZ)},
{"Character::get_z", API_FN_PAIR(Character_GetZ)},
{"Character::set_z", API_FN_PAIR(Character_SetZ)},
{"Character::get_HasExplicitLight", API_FN_PAIR(Character_GetHasExplicitLight)},
{"Character::get_LightLevel", API_FN_PAIR(Character_GetLightLevel)},
{"Character::get_TintBlue", API_FN_PAIR(Character_GetTintBlue)},
{"Character::get_TintGreen", API_FN_PAIR(Character_GetTintGreen)},
{"Character::get_TintRed", API_FN_PAIR(Character_GetTintRed)},
{"Character::get_TintSaturation", API_FN_PAIR(Character_GetTintSaturation)},
{"Character::get_TintLuminance", API_FN_PAIR(Character_GetTintLuminance)},
};
ccAddExternalFunctions361(character_api);
// Few functions have to be selected based on API level
if (base_api < kScriptAPI_v350) {
ccAddExternalObjectFunction361("Character::LockViewAligned^3", API_FN_PAIR(Character_LockViewAligned_Old));
ccAddExternalObjectFunction361("Character::LockViewAligned^4", API_FN_PAIR(Character_LockViewAlignedEx_Old));
} else {
ccAddExternalObjectFunction361("Character::LockViewAligned^3", API_FN_PAIR(Character_LockViewAligned));
ccAddExternalObjectFunction361("Character::LockViewAligned^4", API_FN_PAIR(Character_LockViewAlignedEx));
}
if (base_api < kScriptAPI_v341) {
ccAddExternalObjectFunction361("Character::get_HasExplicitTint", API_FN_PAIR(Character_GetHasExplicitTint_Old));
} else {
ccAddExternalObjectFunction361("Character::get_HasExplicitTint", API_FN_PAIR(Character_GetHasExplicitTint));
}
}
} // namespace AGS3