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