508 lines
16 KiB
C++
508 lines
16 KiB
C++
//=============================================================================
|
|
//
|
|
// Adventure Game Studio (AGS)
|
|
//
|
|
// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
|
|
// The full list of copyright holders can be found in the Copyright.txt
|
|
// file, which is part of this source code distribution.
|
|
//
|
|
// The AGS source code is provided under the Artistic License 2.0.
|
|
// A copy of this license can be found in the file License.txt and at
|
|
// http://www.opensource.org/licenses/artistic-license-2.0.php
|
|
//
|
|
//=============================================================================
|
|
|
|
#include "ac/characterinfo.h"
|
|
#include "ac/common.h"
|
|
#include "ac/gamesetupstruct.h"
|
|
#include "ac/character.h"
|
|
#include "ac/characterextras.h"
|
|
#include "ac/gamestate.h"
|
|
#include "ac/global_character.h"
|
|
#include "ac/math.h"
|
|
#include "ac/viewframe.h"
|
|
#include "debug/debug_log.h"
|
|
#include "game/roomstruct.h"
|
|
#include "main/maindefines_ex.h" // RETURN_CONTINUE
|
|
#include "main/update.h"
|
|
#include "media/audio/audio_system.h"
|
|
|
|
using namespace AGS::Common;
|
|
|
|
extern ViewStruct*views;
|
|
extern GameSetupStruct game;
|
|
extern int displayed_room;
|
|
extern GameState play;
|
|
extern int char_speaking;
|
|
extern RoomStruct thisroom;
|
|
extern unsigned int loopcounter;
|
|
|
|
#define Random __Rand
|
|
|
|
int CharacterInfo::get_effective_y() {
|
|
return y - z;
|
|
}
|
|
int CharacterInfo::get_baseline() {
|
|
if (baseline < 1)
|
|
return y;
|
|
return baseline;
|
|
}
|
|
int CharacterInfo::get_blocking_top() {
|
|
if (blocking_height > 0)
|
|
return y - blocking_height / 2;
|
|
return y - 2;
|
|
}
|
|
int CharacterInfo::get_blocking_bottom() {
|
|
// the blocking_bottom should be 1 less than the top + height
|
|
// since the code does <= checks on it rather than < checks
|
|
if (blocking_height > 0)
|
|
return (y + (blocking_height + 1) / 2) - 1;
|
|
return y + 3;
|
|
}
|
|
|
|
void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, int &numSheep, int *followingAsSheep)
|
|
{
|
|
int res;
|
|
|
|
if (on != 1) return;
|
|
|
|
// walking
|
|
res = update_character_walking(chex);
|
|
// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
|
|
if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
|
|
return; // must be careful not to screw things up
|
|
}
|
|
|
|
// Make sure it doesn't flash up a blue cup
|
|
if (view < 0) ;
|
|
else if (loop >= views[view].numLoops)
|
|
loop = 0;
|
|
|
|
int doing_nothing = 1;
|
|
|
|
update_character_moving(char_index, chex, doing_nothing);
|
|
|
|
// [IKM] 2012-06-28:
|
|
// Character index value is used to set up some variables in there, so I cannot just cease using it
|
|
res = update_character_animating(char_index, doing_nothing);
|
|
// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
|
|
if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
|
|
return; // must be careful not to screw things up
|
|
}
|
|
|
|
update_character_follower(char_index, numSheep, followingAsSheep, doing_nothing);
|
|
|
|
update_character_idle(chex, doing_nothing);
|
|
|
|
chex->process_idle_this_time = 0;
|
|
}
|
|
|
|
void CharacterInfo::UpdateFollowingExactlyCharacter()
|
|
{
|
|
x = game.chars[following].x;
|
|
y = game.chars[following].y;
|
|
z = game.chars[following].z;
|
|
room = game.chars[following].room;
|
|
prevroom = game.chars[following].prevroom;
|
|
|
|
int usebase = game.chars[following].get_baseline();
|
|
|
|
if (flags & CHF_BEHINDSHEPHERD)
|
|
baseline = usebase - 1;
|
|
else
|
|
baseline = usebase + 1;
|
|
}
|
|
|
|
int CharacterInfo::update_character_walking(CharacterExtras *chex)
|
|
{
|
|
if (walking >= TURNING_AROUND) {
|
|
// Currently rotating to correct direction
|
|
if (walkwait > 0) walkwait--;
|
|
else {
|
|
// Work out which direction is next
|
|
int wantloop = find_looporder_index(loop) + 1;
|
|
// going anti-clockwise, take one before instead
|
|
if (walking >= TURNING_BACKWARDS)
|
|
wantloop -= 2;
|
|
while (1) {
|
|
if (wantloop >= 8)
|
|
wantloop = 0;
|
|
if (wantloop < 0)
|
|
wantloop = 7;
|
|
if ((turnlooporder[wantloop] >= views[view].numLoops) ||
|
|
(views[view].loops[turnlooporder[wantloop]].numFrames < 1) ||
|
|
((turnlooporder[wantloop] >= 4) && ((flags & CHF_NODIAGONAL)!=0))) {
|
|
if (walking >= TURNING_BACKWARDS)
|
|
wantloop--;
|
|
else
|
|
wantloop++;
|
|
}
|
|
else break;
|
|
}
|
|
loop = turnlooporder[wantloop];
|
|
walking -= TURNING_AROUND;
|
|
// if still turning, wait for next frame
|
|
if (walking % TURNING_BACKWARDS >= TURNING_AROUND)
|
|
walkwait = animspeed;
|
|
else
|
|
walking = walking % TURNING_BACKWARDS;
|
|
chex->animwait = 0;
|
|
}
|
|
return RETURN_CONTINUE;
|
|
//continue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing)
|
|
{
|
|
if ((walking > 0) && (room == displayed_room))
|
|
{
|
|
if (walkwait > 0) walkwait--;
|
|
else
|
|
{
|
|
flags &= ~CHF_AWAITINGMOVE;
|
|
|
|
// Move the character
|
|
int numSteps = wantMoveNow(this, chex);
|
|
|
|
if ((numSteps) && (chex->xwas != INVALID_X)) {
|
|
// if the zoom level changed mid-move, the walkcounter
|
|
// might not have come round properly - so sort it out
|
|
x = chex->xwas;
|
|
y = chex->ywas;
|
|
chex->xwas = INVALID_X;
|
|
}
|
|
|
|
int oldxp = x, oldyp = y;
|
|
|
|
for (int ff = 0; ff < abs(numSteps); ff++) {
|
|
if (doNextCharMoveStep (this, char_index, chex))
|
|
break;
|
|
if ((walking == 0) || (walking >= TURNING_AROUND))
|
|
break;
|
|
}
|
|
|
|
if (numSteps < 0) {
|
|
// very small scaling, intersperse the movement
|
|
// to stop it being jumpy
|
|
chex->xwas = x;
|
|
chex->ywas = y;
|
|
x = ((x) - oldxp) / 2 + oldxp;
|
|
y = ((y) - oldyp) / 2 + oldyp;
|
|
}
|
|
else if (numSteps > 0)
|
|
chex->xwas = INVALID_X;
|
|
|
|
if ((flags & CHF_ANTIGLIDE) == 0)
|
|
walkwaitcounter++;
|
|
}
|
|
|
|
if (loop >= views[view].numLoops)
|
|
quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, name, loop, view + 1);
|
|
|
|
// check don't overflow loop
|
|
int framesInLoop = views[view].loops[loop].numFrames;
|
|
if (frame > framesInLoop)
|
|
{
|
|
frame = 1;
|
|
|
|
if (framesInLoop < 2)
|
|
frame = 0;
|
|
|
|
if (framesInLoop < 1)
|
|
quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, name, loop);
|
|
}
|
|
|
|
if (walking<1) {
|
|
chex->process_idle_this_time = 1;
|
|
doing_nothing=1;
|
|
walkwait=0;
|
|
chex->animwait = 0;
|
|
// use standing pic
|
|
Character_StopMoving(this);
|
|
frame = 0;
|
|
CheckViewFrameForCharacter(this);
|
|
}
|
|
else if (chex->animwait > 0) chex->animwait--;
|
|
else {
|
|
if (flags & CHF_ANTIGLIDE)
|
|
walkwaitcounter++;
|
|
|
|
if ((flags & CHF_MOVENOTWALK) == 0)
|
|
{
|
|
frame++;
|
|
if (frame >= views[view].loops[loop].numFrames)
|
|
{
|
|
// end of loop, so loop back round skipping the standing frame
|
|
frame = 1;
|
|
|
|
if (views[view].loops[loop].numFrames < 2)
|
|
frame = 0;
|
|
}
|
|
|
|
chex->animwait = views[view].loops[loop].frames[frame].speed + animspeed;
|
|
|
|
if (flags & CHF_ANTIGLIDE)
|
|
walkwait = chex->animwait;
|
|
else
|
|
walkwait = 0;
|
|
|
|
CheckViewFrameForCharacter(this);
|
|
}
|
|
}
|
|
doing_nothing = 0;
|
|
}
|
|
}
|
|
|
|
int CharacterInfo::update_character_animating(int &aa, int &doing_nothing)
|
|
{
|
|
// not moving, but animating
|
|
// idleleft is <0 while idle view is playing (.animating is 0)
|
|
if (((animating != 0) || (idleleft < 0)) &&
|
|
((walking == 0) || ((flags & CHF_MOVENOTWALK) != 0)) &&
|
|
(room == displayed_room))
|
|
{
|
|
doing_nothing = 0;
|
|
// idle anim doesn't count as doing something
|
|
if (idleleft < 0)
|
|
doing_nothing = 1;
|
|
|
|
if (wait>0) wait--;
|
|
else if ((char_speaking == aa) && (game.options[OPT_LIPSYNCTEXT] != 0)) {
|
|
// currently talking with lip-sync speech
|
|
int fraa = frame;
|
|
wait = update_lip_sync (view, loop, &fraa) - 1;
|
|
// closed mouth at end of sentence
|
|
// NOTE: standard lip-sync is synchronized with text timer, not voice file
|
|
if (play.speech_in_post_state ||
|
|
((play.messagetime >= 0) && (play.messagetime < play.close_mouth_speech_time)))
|
|
frame = 0;
|
|
|
|
if (frame != fraa) {
|
|
frame = fraa;
|
|
CheckViewFrameForCharacter(this);
|
|
}
|
|
|
|
//continue;
|
|
return RETURN_CONTINUE;
|
|
}
|
|
else {
|
|
int oldframe = frame;
|
|
if (animating & CHANIM_BACKWARDS) {
|
|
frame--;
|
|
if (frame < 0) {
|
|
// if the previous loop is a Run Next Loop one, go back to it
|
|
if ((loop > 0) &&
|
|
(views[view].loops[loop - 1].RunNextLoop())) {
|
|
|
|
loop --;
|
|
frame = views[view].loops[loop].numFrames - 1;
|
|
}
|
|
else if (animating & CHANIM_REPEAT) {
|
|
|
|
frame = views[view].loops[loop].numFrames - 1;
|
|
|
|
while (views[view].loops[loop].RunNextLoop()) {
|
|
loop++;
|
|
frame = views[view].loops[loop].numFrames - 1;
|
|
}
|
|
}
|
|
else {
|
|
frame++;
|
|
animating = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
frame++;
|
|
|
|
if ((aa == char_speaking) &&
|
|
(play.speech_in_post_state ||
|
|
((!play.speech_has_voice) &&
|
|
(play.close_mouth_speech_time > 0) &&
|
|
(play.messagetime < play.close_mouth_speech_time)))) {
|
|
// finished talking - stop animation
|
|
animating = 0;
|
|
frame = 0;
|
|
}
|
|
|
|
if (frame >= views[view].loops[loop].numFrames) {
|
|
|
|
if (views[view].loops[loop].RunNextLoop())
|
|
{
|
|
if (loop+1 >= views[view].numLoops)
|
|
quit("!Animating character tried to overrun last loop in view");
|
|
loop++;
|
|
frame=0;
|
|
}
|
|
else if ((animating & CHANIM_REPEAT)==0) {
|
|
animating=0;
|
|
frame--;
|
|
// end of idle anim
|
|
if (idleleft < 0) {
|
|
// constant anim, reset (need this cos animating==0)
|
|
if (idletime == 0)
|
|
frame = 0;
|
|
// one-off anim, stop
|
|
else {
|
|
ReleaseCharacterView(aa);
|
|
idleleft=idletime;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
frame=0;
|
|
// if it's a multi-loop animation, go back to start
|
|
if (play.no_multiloop_repeat == 0) {
|
|
while ((loop > 0) &&
|
|
(views[view].loops[loop - 1].RunNextLoop()))
|
|
loop--;
|
|
}
|
|
}
|
|
}
|
|
wait = views[view].loops[loop].frames[frame].speed;
|
|
// idle anim doesn't have speed stored cos animating==0
|
|
if (idleleft < 0)
|
|
wait += animspeed+5;
|
|
else
|
|
wait += (animating >> 8) & 0x00ff;
|
|
|
|
if (frame != oldframe)
|
|
CheckViewFrameForCharacter(this);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CharacterInfo::update_character_follower(int &aa, int &numSheep, int *followingAsSheep, int &doing_nothing)
|
|
{
|
|
if ((following >= 0) && (followinfo == FOLLOW_ALWAYSONTOP)) {
|
|
// an always-on-top follow
|
|
if (numSheep >= MAX_SHEEP)
|
|
quit("too many sheep");
|
|
followingAsSheep[numSheep] = aa;
|
|
numSheep++;
|
|
}
|
|
// not moving, but should be following another character
|
|
else if ((following >= 0) && (doing_nothing == 1)) {
|
|
short distaway=(followinfo >> 8) & 0x00ff;
|
|
// no character in this room
|
|
if ((game.chars[following].on == 0) || (on == 0)) ;
|
|
else if (room < 0) {
|
|
room ++;
|
|
if (room == 0) {
|
|
// appear in the new room
|
|
room = game.chars[following].room;
|
|
x = play.entered_at_x;
|
|
y = play.entered_at_y;
|
|
}
|
|
}
|
|
// wait a bit, so we're not constantly walking
|
|
else if (Random(100) < (followinfo & 0x00ff)) ;
|
|
// the followed character has changed room
|
|
else if ((room != game.chars[following].room)
|
|
&& (game.chars[following].on == 0))
|
|
; // do nothing if the player isn't visible
|
|
else if (room != game.chars[following].room) {
|
|
prevroom = room;
|
|
room = game.chars[following].room;
|
|
|
|
if (room == displayed_room) {
|
|
// only move to the room-entered position if coming into
|
|
// the current room
|
|
if (play.entered_at_x > (thisroom.Width - 8)) {
|
|
x = thisroom.Width+8;
|
|
y = play.entered_at_y;
|
|
}
|
|
else if (play.entered_at_x < 8) {
|
|
x = -8;
|
|
y = play.entered_at_y;
|
|
}
|
|
else if (play.entered_at_y > (thisroom.Height - 8)) {
|
|
y = thisroom.Height+8;
|
|
x = play.entered_at_x;
|
|
}
|
|
else if (play.entered_at_y < thisroom.Edges.Top+8) {
|
|
y = thisroom.Edges.Top+1;
|
|
x = play.entered_at_x;
|
|
}
|
|
else {
|
|
// not at one of the edges
|
|
// delay for a few seconds to let the player move
|
|
room = -play.follow_change_room_timer;
|
|
}
|
|
if (room >= 0) {
|
|
walk_character(aa,play.entered_at_x,play.entered_at_y,1, true);
|
|
doing_nothing = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (room != displayed_room) {
|
|
// if the characetr is following another character and
|
|
// neither is in the current room, don't try to move
|
|
}
|
|
else if ((abs(game.chars[following].x - x) > distaway+30) |
|
|
(abs(game.chars[following].y - y) > distaway+30) |
|
|
((followinfo & 0x00ff) == 0)) {
|
|
// in same room
|
|
int goxoffs=(Random(50)-25);
|
|
// make sure he's not standing on top of the other man
|
|
if (goxoffs < 0) goxoffs-=distaway;
|
|
else goxoffs+=distaway;
|
|
walk_character(aa,game.chars[following].x + goxoffs,
|
|
game.chars[following].y + (Random(50)-25),0, true);
|
|
doing_nothing = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_nothing)
|
|
{
|
|
// no idle animation, so skip this bit
|
|
if (idleview < 1) ;
|
|
// currently playing idle anim
|
|
else if (idleleft < 0) ;
|
|
// not in the current room
|
|
else if (room != displayed_room) ;
|
|
// they are moving or animating (or the view is locked), so
|
|
// reset idle timeout
|
|
else if ((doing_nothing == 0) || ((flags & CHF_FIXVIEW) != 0))
|
|
idleleft = idletime;
|
|
// count idle time
|
|
else if ((loopcounter%40==0) || (chex->process_idle_this_time == 1)) {
|
|
idleleft--;
|
|
if (idleleft == -1) {
|
|
int useloop=loop;
|
|
debug_script_log("%s: Now idle (view %d)", scrname, idleview+1);
|
|
Character_LockView(this, idleview+1);
|
|
// SetCharView resets it to 0
|
|
idleleft = -2;
|
|
int maxLoops = views[idleview].numLoops;
|
|
// if the char is set to "no diagonal loops", don't try
|
|
// to use diagonal idle loops either
|
|
if ((maxLoops > 4) && (useDiagonal(this)))
|
|
maxLoops = 4;
|
|
// If it's not a "swimming"-type idleanim, choose a random loop
|
|
// if there arent enough loops to do the current one.
|
|
if ((idletime > 0) && (useloop >= maxLoops)) {
|
|
do {
|
|
useloop = rand() % maxLoops;
|
|
// don't select a loop which is a continuation of a previous one
|
|
} while ((useloop > 0) && (views[idleview].loops[useloop-1].RunNextLoop()));
|
|
}
|
|
// Normal idle anim - just reset to loop 0 if not enough to
|
|
// use the current one
|
|
else if (useloop >= maxLoops)
|
|
useloop = 0;
|
|
|
|
animate_character(this,useloop,
|
|
animspeed+5,(idletime == 0) ? 1 : 0, 1);
|
|
|
|
// don't set Animating while the idle anim plays
|
|
animating = 0;
|
|
}
|
|
} // end do idle animation
|
|
}
|