/* 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 . * */ //============================================================================= // // 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(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