1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14 
15 #include "ac/characterinfo.h"
16 #include "ac/common.h"
17 #include "ac/gamesetupstruct.h"
18 #include "ac/roomstruct.h"
19 #include "media/audio/audiodefines.h"
20 #include "ac/character.h"
21 #include "ac/characterextras.h"
22 #include "ac/gamestate.h"
23 #include "ac/global_character.h"
24 #include "ac/math.h"
25 #include "ac/viewframe.h"
26 #include "debug/debug_log.h"
27 #include "main/maindefines_ex.h"	// RETURN_CONTINUE
28 #include "main/update.h"
29 
30 extern ViewStruct*views;
31 extern GameSetupStruct game;
32 extern int displayed_room;
33 extern GameState play;
34 extern int char_speaking;
35 extern roomstruct thisroom;
36 extern SOUNDCLIP *channels[MAX_SOUND_CHANNELS+1];
37 extern unsigned int loopcounter;
38 
39 #define Random __Rand
40 
get_effective_y()41 int CharacterInfo::get_effective_y() {
42     return y - z;
43 }
get_baseline()44 int CharacterInfo::get_baseline() {
45     if (baseline < 1)
46         return y;
47     return baseline;
48 }
get_blocking_top()49 int CharacterInfo::get_blocking_top() {
50     if (blocking_height > 0)
51         return y - blocking_height / 2;
52     return y - 2;
53 }
get_blocking_bottom()54 int CharacterInfo::get_blocking_bottom() {
55     // the blocking_bottom should be 1 less than the top + height
56     // since the code does <= checks on it rather than < checks
57     if (blocking_height > 0)
58         return (y + (blocking_height + 1) / 2) - 1;
59     return y + 3;
60 }
61 
UpdateMoveAndAnim(int & char_index,CharacterExtras * chex,int & numSheep,int * followingAsSheep)62 void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, int &numSheep, int *followingAsSheep)
63 {
64 	int res;
65 
66 	if (on != 1) return;
67 
68 	// walking
69 	res = update_character_walking(chex);
70 	// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
71 	if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
72 		return;				      //  must be careful not to screw things up
73 	}
74 
75     // Make sure it doesn't flash up a blue cup
76     if (view < 0) ;
77     else if (loop >= views[view].numLoops)
78       loop = 0;
79 
80     int doing_nothing = 1;
81 
82 	update_character_moving(char_index, chex, doing_nothing);
83 
84 	// [IKM] 2012-06-28:
85 	// Character index value is used to set up some variables in there, so I cannot just cease using it
86 	res = update_character_animating(char_index, doing_nothing);
87 	// [IKM] Yes, it should return! upon getting RETURN_CONTINUE here
88 	if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places...
89 		return;				      //  must be careful not to screw things up
90 	}
91 
92 	update_character_follower(char_index, numSheep, followingAsSheep, doing_nothing);
93 
94 	update_character_idle(chex, doing_nothing);
95 
96     chex->process_idle_this_time = 0;
97 }
98 
UpdateFollowingExactlyCharacter()99 void CharacterInfo::UpdateFollowingExactlyCharacter()
100 {
101 	x = game.chars[following].x;
102     y = game.chars[following].y;
103     z = game.chars[following].z;
104     room = game.chars[following].room;
105     prevroom = game.chars[following].prevroom;
106 
107     int usebase = game.chars[following].get_baseline();
108 
109     if (flags & CHF_BEHINDSHEPHERD)
110       baseline = usebase - 1;
111     else
112       baseline = usebase + 1;
113 }
114 
update_character_walking(CharacterExtras * chex)115 int CharacterInfo::update_character_walking(CharacterExtras *chex)
116 {
117     if (walking >= TURNING_AROUND) {
118       // Currently rotating to correct direction
119       if (walkwait > 0) walkwait--;
120       else {
121         // Work out which direction is next
122         int wantloop = find_looporder_index(loop) + 1;
123         // going anti-clockwise, take one before instead
124         if (walking >= TURNING_BACKWARDS)
125           wantloop -= 2;
126         while (1) {
127           if (wantloop >= 8)
128             wantloop = 0;
129           if (wantloop < 0)
130             wantloop = 7;
131           if ((turnlooporder[wantloop] >= views[view].numLoops) ||
132               (views[view].loops[turnlooporder[wantloop]].numFrames < 1) ||
133               ((turnlooporder[wantloop] >= 4) && ((flags & CHF_NODIAGONAL)!=0))) {
134             if (walking >= TURNING_BACKWARDS)
135               wantloop--;
136             else
137               wantloop++;
138           }
139           else break;
140         }
141         loop = turnlooporder[wantloop];
142         walking -= TURNING_AROUND;
143         // if still turning, wait for next frame
144         if (walking % TURNING_BACKWARDS >= TURNING_AROUND)
145           walkwait = animspeed;
146         else
147           walking = walking % TURNING_BACKWARDS;
148         chex->animwait = 0;
149       }
150 	  return RETURN_CONTINUE;
151       //continue;
152     }
153 
154 	return 0;
155 }
156 
update_character_moving(int & char_index,CharacterExtras * chex,int & doing_nothing)157 void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing)
158 {
159 	if ((walking > 0) && (room == displayed_room))
160     {
161       if (walkwait > 0) walkwait--;
162       else
163       {
164         flags &= ~CHF_AWAITINGMOVE;
165 
166         // Move the character
167         int numSteps = wantMoveNow(this, chex);
168 
169         if ((numSteps) && (chex->xwas != INVALID_X)) {
170           // if the zoom level changed mid-move, the walkcounter
171           // might not have come round properly - so sort it out
172           x = chex->xwas;
173           y = chex->ywas;
174           chex->xwas = INVALID_X;
175         }
176 
177         int oldxp = x, oldyp = y;
178 
179         for (int ff = 0; ff < abs(numSteps); ff++) {
180           if (doNextCharMoveStep (this, char_index, chex))
181             break;
182           if ((walking == 0) || (walking >= TURNING_AROUND))
183             break;
184         }
185 
186         if (numSteps < 0) {
187           // very small scaling, intersperse the movement
188           // to stop it being jumpy
189           chex->xwas = x;
190           chex->ywas = y;
191           x = ((x) - oldxp) / 2 + oldxp;
192           y = ((y) - oldyp) / 2 + oldyp;
193         }
194         else if (numSteps > 0)
195           chex->xwas = INVALID_X;
196 
197         if ((flags & CHF_ANTIGLIDE) == 0)
198           walkwaitcounter++;
199       }
200 
201       if (loop >= views[view].numLoops)
202         quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, name, loop, view + 1);
203 
204       // check don't overflow loop
205       int framesInLoop = views[view].loops[loop].numFrames;
206       if (frame > framesInLoop)
207       {
208         frame = 1;
209 
210         if (framesInLoop < 2)
211           frame = 0;
212 
213         if (framesInLoop < 1)
214           quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, name, loop);
215       }
216 
217       if (walking<1) {
218         chex->process_idle_this_time = 1;
219         doing_nothing=1;
220         walkwait=0;
221         chex->animwait = 0;
222         // use standing pic
223         Character_StopMoving(this);
224         frame = 0;
225         CheckViewFrameForCharacter(this);
226       }
227       else if (chex->animwait > 0) chex->animwait--;
228       else {
229         if (flags & CHF_ANTIGLIDE)
230           walkwaitcounter++;
231 
232         if ((flags & CHF_MOVENOTWALK) == 0)
233         {
234           frame++;
235           if (frame >= views[view].loops[loop].numFrames)
236           {
237             // end of loop, so loop back round skipping the standing frame
238             frame = 1;
239 
240             if (views[view].loops[loop].numFrames < 2)
241               frame = 0;
242           }
243 
244           chex->animwait = views[view].loops[loop].frames[frame].speed + animspeed;
245 
246           if (flags & CHF_ANTIGLIDE)
247             walkwait = chex->animwait;
248           else
249             walkwait = 0;
250 
251           CheckViewFrameForCharacter(this);
252         }
253       }
254       doing_nothing = 0;
255     }
256 }
257 
update_character_animating(int & aa,int & doing_nothing)258 int CharacterInfo::update_character_animating(int &aa, int &doing_nothing)
259 {
260 	// not moving, but animating
261     // idleleft is <0 while idle view is playing (.animating is 0)
262     if (((animating != 0) || (idleleft < 0)) &&
263         ((walking == 0) || ((flags & CHF_MOVENOTWALK) != 0)) &&
264         (room == displayed_room))
265     {
266       const bool is_voice = channels[SCHAN_SPEECH] != NULL;
267 
268       doing_nothing = 0;
269       // idle anim doesn't count as doing something
270       if (idleleft < 0)
271         doing_nothing = 1;
272 
273       if (wait>0) wait--;
274       else if ((char_speaking == aa) && (game.options[OPT_LIPSYNCTEXT] != 0)) {
275         // currently talking with lip-sync speech
276         int fraa = frame;
277         wait = update_lip_sync (view, loop, &fraa) - 1;
278         // closed mouth at end of sentence
279         // NOTE: standard lip-sync is synchronized with text timer, not voice file
280         if (play.speech_in_post_state ||
281             (play.messagetime >= 0) && (play.messagetime < play.close_mouth_speech_time))
282           frame = 0;
283 
284         if (frame != fraa) {
285           frame = fraa;
286           CheckViewFrameForCharacter(this);
287         }
288 
289         //continue;
290 		return RETURN_CONTINUE;
291       }
292       else {
293         int oldframe = frame;
294         if (animating & CHANIM_BACKWARDS) {
295           frame--;
296           if (frame < 0) {
297             // if the previous loop is a Run Next Loop one, go back to it
298             if ((loop > 0) &&
299               (views[view].loops[loop - 1].RunNextLoop())) {
300 
301               loop --;
302               frame = views[view].loops[loop].numFrames - 1;
303             }
304             else if (animating & CHANIM_REPEAT) {
305 
306               frame = views[view].loops[loop].numFrames - 1;
307 
308               while (views[view].loops[loop].RunNextLoop()) {
309                 loop++;
310                 frame = views[view].loops[loop].numFrames - 1;
311               }
312             }
313             else {
314               frame++;
315               animating = 0;
316             }
317           }
318         }
319         else
320           frame++;
321 
322         if ((aa == char_speaking) &&
323              (play.speech_in_post_state ||
324              (!is_voice) &&
325              (play.close_mouth_speech_time > 0) &&
326              (play.messagetime < play.close_mouth_speech_time))) {
327           // finished talking - stop animation
328           animating = 0;
329           frame = 0;
330         }
331 
332         if (frame >= views[view].loops[loop].numFrames) {
333 
334           if (views[view].loops[loop].RunNextLoop())
335           {
336             if (loop+1 >= views[view].numLoops)
337               quit("!Animating character tried to overrun last loop in view");
338             loop++;
339             frame=0;
340           }
341           else if ((animating & CHANIM_REPEAT)==0) {
342             animating=0;
343             frame--;
344             // end of idle anim
345             if (idleleft < 0) {
346               // constant anim, reset (need this cos animating==0)
347               if (idletime == 0)
348                 frame = 0;
349               // one-off anim, stop
350               else {
351                 ReleaseCharacterView(aa);
352                 idleleft=idletime;
353               }
354             }
355           }
356           else {
357             frame=0;
358             // if it's a multi-loop animation, go back to start
359             if (play.no_multiloop_repeat == 0) {
360               while ((loop > 0) &&
361                   (views[view].loops[loop - 1].RunNextLoop()))
362                 loop--;
363             }
364           }
365         }
366         wait = views[view].loops[loop].frames[frame].speed;
367         // idle anim doesn't have speed stored cos animating==0
368         if (idleleft < 0)
369           wait += animspeed+5;
370         else
371           wait += (animating >> 8) & 0x00ff;
372 
373         if (frame != oldframe)
374           CheckViewFrameForCharacter(this);
375       }
376     }
377 
378 	return 0;
379 }
380 
update_character_follower(int & aa,int & numSheep,int * followingAsSheep,int & doing_nothing)381 void CharacterInfo::update_character_follower(int &aa, int &numSheep, int *followingAsSheep, int &doing_nothing)
382 {
383 	if ((following >= 0) && (followinfo == FOLLOW_ALWAYSONTOP)) {
384       // an always-on-top follow
385       if (numSheep >= MAX_SHEEP)
386         quit("too many sheep");
387       followingAsSheep[numSheep] = aa;
388       numSheep++;
389     }
390     // not moving, but should be following another character
391     else if ((following >= 0) && (doing_nothing == 1)) {
392       short distaway=(followinfo >> 8) & 0x00ff;
393       // no character in this room
394       if ((game.chars[following].on == 0) || (on == 0)) ;
395       else if (room < 0) {
396         room ++;
397         if (room == 0) {
398           // appear in the new room
399           room = game.chars[following].room;
400           x = play.entered_at_x;
401           y = play.entered_at_y;
402         }
403       }
404       // wait a bit, so we're not constantly walking
405       else if (Random(100) < (followinfo & 0x00ff)) ;
406       // the followed character has changed room
407       else if ((room != game.chars[following].room)
408             && (game.chars[following].on == 0))
409         ;  // do nothing if the player isn't visible
410       else if (room != game.chars[following].room) {
411         prevroom = room;
412         room = game.chars[following].room;
413 
414         if (room == displayed_room) {
415           // only move to the room-entered position if coming into
416           // the current room
417           if (play.entered_at_x > (thisroom.width - 8)) {
418             x = thisroom.width+8;
419             y = play.entered_at_y;
420             }
421           else if (play.entered_at_x < 8) {
422             x = -8;
423             y = play.entered_at_y;
424             }
425           else if (play.entered_at_y > (thisroom.height - 8)) {
426             y = thisroom.height+8;
427             x = play.entered_at_x;
428             }
429           else if (play.entered_at_y < thisroom.top+8) {
430             y = thisroom.top+1;
431             x = play.entered_at_x;
432             }
433           else {
434             // not at one of the edges
435             // delay for a few seconds to let the player move
436             room = -play.follow_change_room_timer;
437           }
438           if (room >= 0) {
439             walk_character(aa,play.entered_at_x,play.entered_at_y,1, true);
440             doing_nothing = 0;
441           }
442         }
443       }
444       else if (room != displayed_room) {
445         // if the characetr is following another character and
446         // neither is in the current room, don't try to move
447       }
448       else if ((abs(game.chars[following].x - x) > distaway+30) |
449         (abs(game.chars[following].y - y) > distaway+30) |
450         ((followinfo & 0x00ff) == 0)) {
451         // in same room
452         int goxoffs=(Random(50)-25);
453         // make sure he's not standing on top of the other man
454         if (goxoffs < 0) goxoffs-=distaway;
455         else goxoffs+=distaway;
456         walk_character(aa,game.chars[following].x + goxoffs,
457           game.chars[following].y + (Random(50)-25),0, true);
458         doing_nothing = 0;
459       }
460     }
461 }
462 
update_character_idle(CharacterExtras * chex,int & doing_nothing)463 void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_nothing)
464 {
465 	// no idle animation, so skip this bit
466     if (idleview < 1) ;
467     // currently playing idle anim
468     else if (idleleft < 0) ;
469     // not in the current room
470     else if (room != displayed_room) ;
471     // they are moving or animating (or the view is locked), so
472     // reset idle timeout
473     else if ((doing_nothing == 0) || ((flags & CHF_FIXVIEW) != 0))
474       idleleft = idletime;
475     // count idle time
476     else if ((loopcounter%40==0) || (chex->process_idle_this_time == 1)) {
477       idleleft--;
478       if (idleleft == -1) {
479         int useloop=loop;
480         debug_script_log("%s: Now idle (view %d)", scrname, idleview+1);
481 		Character_LockView(this, idleview+1);
482         // SetCharView resets it to 0
483         idleleft = -2;
484         int maxLoops = views[idleview].numLoops;
485         // if the char is set to "no diagonal loops", don't try
486         // to use diagonal idle loops either
487         if ((maxLoops > 4) && (useDiagonal(this)))
488           maxLoops = 4;
489         // If it's not a "swimming"-type idleanim, choose a random loop
490         // if there arent enough loops to do the current one.
491         if ((idletime > 0) && (useloop >= maxLoops)) {
492           do {
493             useloop = rand() % maxLoops;
494           // don't select a loop which is a continuation of a previous one
495           } while ((useloop > 0) && (views[idleview].loops[useloop-1].RunNextLoop()));
496         }
497         // Normal idle anim - just reset to loop 0 if not enough to
498         // use the current one
499         else if (useloop >= maxLoops)
500           useloop = 0;
501 
502         animate_character(this,useloop,
503           animspeed+5,(idletime == 0) ? 1 : 0, 1);
504 
505         // don't set Animating while the idle anim plays
506         animating = 0;
507       }
508     }  // end do idle animation
509 }
510