1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * Should really be called "moving actors.c"
22  */
23 
24 #include "tinsel/actors.h"
25 #include "tinsel/anim.h"
26 #include "tinsel/background.h"
27 #include "tinsel/config.h"
28 #include "tinsel/dw.h"
29 #include "tinsel/film.h"
30 #include "tinsel/handle.h"
31 #include "tinsel/dialogs.h"
32 #include "tinsel/mareels.h"
33 #include "tinsel/move.h"
34 #include "tinsel/multiobj.h"	// multi-part object defintions etc.
35 #include "tinsel/object.h"
36 #include "tinsel/pcode.h"
37 #include "tinsel/pid.h"
38 #include "tinsel/play.h"
39 #include "tinsel/polygons.h"
40 #include "tinsel/movers.h"
41 #include "tinsel/sched.h"
42 #include "tinsel/sysvar.h"
43 #include "tinsel/timers.h"
44 #include "tinsel/tinsel.h"
45 #include "tinsel/token.h"
46 
47 #include "common/textconsole.h"
48 #include "common/util.h"
49 
50 namespace Tinsel {
51 
52 //----------------- LOCAL GLOBAL DATA --------------------
53 
54 // These vars are reset upon engine destruction
55 static MOVER g_Movers[MAX_MOVERS];
56 
57 //----------------- FUNCTIONS ----------------------------
58 
59 /**
60  * Called from ActorPalette(), normally once just after the beginning of time.
61  */
StoreMoverPalette(PMOVER pMover,int startColor,int length)62 void StoreMoverPalette(PMOVER pMover, int startColor, int length) {
63 	pMover->startColor = startColor;
64 	pMover->paletteLength = length;
65 }
66 
67 /**
68  * Called from the moving actor's main loop.
69  */
CheckBrightness(PMOVER pMover)70 static void CheckBrightness(PMOVER pMover) {
71 	int brightness;
72 
73 	if (pMover->hCpath == NOPOLY || pMover->bHidden)
74 		return;
75 
76 	brightness = GetBrightness(pMover->hCpath, pMover->objY);
77 
78 	if (brightness != pMover->brightness) {
79 		// Do it all immediately on first appearance,
80 		// otherwise do it iteratively
81 
82 		if (pMover->brightness == BOGUS_BRIGHTNESS)
83 			pMover->brightness = brightness;	// all the way
84 		else if (brightness > pMover->brightness)
85 			pMover->brightness++;			// ramp up
86 		else
87 			pMover->brightness--;			// ramp down
88 
89 		DimPartPalette(_vm->_bg->BgPal(),
90 				pMover->startColor,
91 				pMover->paletteLength,
92 				pMover->brightness);
93 	}
94 }
95 
96 /**
97  * Called from ActorBrightness() Glitter call.
98  * Typically called before the moving actor is created
99  * at the start of a scene to cover a walk-in Play().
100  */
MoverBrightness(PMOVER pMover,int brightness)101 void MoverBrightness(PMOVER pMover, int brightness) {
102 	// Note: Like with some of the Tinsel1 code, this routine original had a process yield
103 	// if BgPal is NULL, and has been changed for ScummVM to a simple assert
104 
105 	// This is changed from a ProcessGiveWay in DW2 to an assert in ScummVM
106 	assert(_vm->_bg->BgPal());
107 
108 	// Do it all immediately
109 	DimPartPalette(_vm->_bg->BgPal(), pMover->startColor, pMover->paletteLength, brightness);
110 
111 	// The actor is probably hidden at this point,
112 	pMover->brightness = brightness;
113 }
114 
115 /**
116  * RebootMovers
117  */
RebootMovers()118 void RebootMovers() {
119 	memset(g_Movers, 0, sizeof(g_Movers));
120 }
121 
122 /**
123  * Given an actor number, return pointer to its moving actor structure,
124  * if it is a moving actor.
125  */
GetMover(int ano)126 PMOVER GetMover(int ano) {
127 	int i;
128 
129 	// Slot 0 is reserved for lead actor
130 	if (ano == _vm->_actor->GetLeadId() || ano == LEAD_ACTOR)
131 		return &g_Movers[0];
132 
133 	for (i = 1; i < MAX_MOVERS; i++)
134 		if (g_Movers[i].actorID == ano)
135 			return &g_Movers[i];
136 
137 	return NULL;
138 }
139 
140 /**
141  * Register an actor as being a moving one.
142  */
RegisterMover(int ano)143 PMOVER RegisterMover(int ano) {
144 	int i;
145 
146 	// Slot 0 is reserved for lead actor
147 	if (ano == _vm->_actor->GetLeadId() || ano == LEAD_ACTOR) {
148 		g_Movers[0].actorToken = TOKEN_LEAD;
149 		g_Movers[0].actorID = _vm->_actor->GetLeadId();
150 		return &g_Movers[0];
151 	}
152 
153 	// Check it hasn't already been declared
154 	for (i = 1; i < MAX_MOVERS; i++) {
155 		if (g_Movers[i].actorID == ano) {
156 			// Actor is already a moving actor
157 			return &g_Movers[i];
158 		}
159 	}
160 
161 	// Find an empty slot
162 	for (i = 1; i < MAX_MOVERS; i++)
163 		if (!g_Movers[i].actorID) {
164 			g_Movers[i].actorToken = TOKEN_LEAD + i;
165 			g_Movers[i].actorID = ano;
166 			return &g_Movers[i];
167 		}
168 
169 	error("Too many moving actors");
170 }
171 
172 /**
173  * Given an index, returns the associated moving actor.
174  *
175  * At the time of writing, used by the effect process.
176  */
GetLiveMover(int index)177 PMOVER GetLiveMover(int index) {
178 	assert(index >= 0 && index < MAX_MOVERS); // out of range
179 
180 	if (g_Movers[index].bActive)
181 		return &g_Movers[index];
182 	else
183 		return NULL;
184 }
185 
IsMAinEffectPoly(int index)186 bool IsMAinEffectPoly(int index) {
187 	assert(index >= 0 && index < MAX_MOVERS); // out of range
188 
189 	return g_Movers[index].bInEffect;
190 }
191 
SetMoverInEffect(int index,bool tf)192 void SetMoverInEffect(int index, bool tf) {
193 	assert(index >= 0 && index < MAX_MOVERS); // out of range
194 
195 	g_Movers[index].bInEffect = tf;
196 }
197 
198 /**
199  * Remove a moving actor from the current scene.
200  */
KillMover(PMOVER pMover)201 void KillMover(PMOVER pMover) {
202 	if (pMover->bActive) {
203 		pMover->bActive = false;
204 		MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_WORLD), pMover->actorObj);
205 		pMover->actorObj = nullptr;
206 		assert(CoroScheduler.getCurrentProcess() != pMover->pProc);
207 		CoroScheduler.killProcess(pMover->pProc);
208 	}
209 }
210 
211 /**
212  * getMActorState
213  */
getMActorState(PMOVER pActor)214 bool getMActorState(PMOVER pActor) {
215 	return pActor->bActive;
216 }
217 
218 /**
219  * If the actor's object exists, move it behind the background.
220  * MultiHideObject() is deliberately not used, as StepAnimScript() calls
221  * cause the object to re-appear.
222  */
HideMover(PMOVER pMover,int sf)223 void HideMover(PMOVER pMover, int sf) {
224 	assert(pMover); // Hiding null moving actor
225 
226 	pMover->bHidden = true;
227 
228 	if (!TinselV2) {
229 		// sf is only passed in Tinsel v1
230 		pMover->SlowFactor = sf;
231 	} else {
232 		// Tinsel 2 specific code
233 		if (_vm->_actor->IsTaggedActor(pMover->actorID)) {
234 			// It may be pointed to
235 			_vm->_actor->SetActorPointedTo(pMover->actorID, false);
236 			_vm->_actor->SetActorTagWanted(pMover->actorID, false, false, 0);
237 		}
238 	}
239 
240 	if (pMover->actorObj)
241 		MultiSetZPosition(pMover->actorObj, -1);
242 }
243 
244 /**
245  * MoverHidden
246  */
MoverHidden(PMOVER pMover)247 bool MoverHidden(PMOVER pMover) {
248 	if (pMover)
249 		return pMover->bHidden;
250 	else
251 		return false;
252 }
253 
254 /**
255  * To be or not to be? If it be, then it is.
256  */
MoverIs(PMOVER pMover)257 bool MoverIs(PMOVER pMover) {
258 	if (TinselV2)
259 		return pMover->actorObj ? true : false;
260 	else
261 		return getMActorState(pMover);
262 }
263 
264 /**
265  * To be SWalk()ing or not to be SWalk()ing?
266  */
MoverIsSWalking(PMOVER pMover)267 bool MoverIsSWalking(PMOVER pMover) {
268 	return (MoverMoving(pMover) && pMover->bIgPath);
269 }
270 
271 /**
272  * MoverMoving()
273  */
MoverMoving(PMOVER pMover)274 bool MoverMoving(PMOVER pMover) {
275 	if (!TinselV2)
276 		return pMover->bMoving;
277 
278 	if (pMover->UtargetX == -1 && pMover->UtargetY == -1)
279 		return false;
280 	else
281 		return true;
282 }
283 
284 /**
285  * Return an actor's walk ticket.
286  */
GetWalkNumber(PMOVER pMover)287 int GetWalkNumber(PMOVER pMover) {
288 	return pMover->walkNumber;
289 }
290 
291 /**
292  * GetMoverId
293  */
GetMoverId(PMOVER pMover)294 int GetMoverId(PMOVER pMover) {
295 	return pMover->actorID;
296 }
297 
298 /**
299  * Sets the mover Z position
300  */
SetMoverZ(PMOVER pMover,int y,uint32 zFactor)301 void SetMoverZ(PMOVER pMover, int y, uint32 zFactor) {
302 	if (!pMover->bHidden) {
303 		if (!TinselV2)
304 			_vm->_actor->AsetZPos(pMover->actorObj, y, zFactor);
305 		else if (MoverIsSWalking(pMover) && pMover->zOverride != -1) {
306 			// Special for SWalk()
307 			MultiSetZPosition(pMover->actorObj, (pMover->zOverride << ZSHIFT) + y);
308 		} else {
309 			// Normal case
310 			MultiSetZPosition(pMover->actorObj, (zFactor << ZSHIFT) + y);
311 		}
312 	}
313 }
314 
SetMoverZoverride(PMOVER pMover,uint32 zFactor)315 void SetMoverZoverride(PMOVER pMover, uint32 zFactor) {
316 	pMover->zOverride = zFactor;
317 }
318 
319 /**
320  * UnHideMover
321  */
UnHideMover(PMOVER pMover)322 void UnHideMover(PMOVER pMover) {
323 	assert(pMover); // unHiding null moving actor
324 
325 	if (!TinselV2 || pMover->bHidden) {
326 		pMover->bHidden = false;
327 
328 		// Make visible on the screen
329 		if (pMover->actorObj) {
330 			// If no path, just use first path in the scene
331 			if (pMover->hCpath != NOPOLY)
332 				SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath));
333 			else
334 				SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly()));
335 		}
336 	}
337 }
338 
339 /**
340  * Clear everything out at actor start-up time.
341  */
InitMover(PMOVER pMover)342 static void InitMover(PMOVER pMover) {
343 	pMover->bActive = false;
344 	pMover->actorObj = nullptr;
345 	pMover->objX = pMover->objY = 0;
346 
347 	pMover->hRpath = NOPOLY;
348 
349 	pMover->targetX = pMover->targetY = -1;
350 	pMover->ItargetX = pMover->ItargetY = -1;
351 	pMover->hIpath = NOPOLY;
352 	pMover->UtargetX = pMover->UtargetY = -1;
353 	pMover->hUpath = NOPOLY;
354 	pMover->hCpath = NOPOLY;
355 
356 	pMover->over = false;
357 	pMover->InDifficulty = NO_PROB;
358 
359 	pMover->hFnpath = NOPOLY;
360 	pMover->npstatus = NOT_IN;
361 	pMover->line = 0;
362 
363 	pMover->Tline = 0;
364 
365 	if (pMover->direction != FORWARD && pMover->direction != AWAY
366 			&& pMover->direction != LEFTREEL && pMover->direction != RIGHTREEL)
367 		pMover->direction = FORWARD;
368 
369 	if (pMover->scale < 0 || pMover->scale > TOTAL_SCALES)
370 		pMover->scale = 1;
371 
372 	pMover->brightness = BOGUS_BRIGHTNESS;	// Force initial setup
373 
374 	pMover->bNoPath = false;
375 	pMover->bIgPath = false;
376 	pMover->bHidden = false;	// 20/2/95
377 	pMover->bStop = false;
378 
379 	pMover->walkNumber= 0;
380 	pMover->stepCount = 0;
381 
382 	pMover->bWalkReel = false;
383 	pMover->bSpecReel = false;
384 	pMover->hLastFilm = 0;
385 	pMover->hPushedFilm = 0;
386 
387 	pMover->bInEffect = false;
388 
389 	pMover->walkedFromX = pMover->walkedFromY = 0;
390 }
391 
392 /**
393  * Get it into our heads that there's nothing doing.
394  * Called at the end of a scene.
395  */
DropMovers()396 void DropMovers() {
397 	for (int i = 0; i < MAX_MOVERS; i++)
398 		InitMover(&g_Movers[i]);
399 }
400 
401 
402 /**
403  * Reposition a moving actor.
404  */
PositionMover(PMOVER pMover,int x,int y)405 void PositionMover(PMOVER pMover, int x, int y) {
406 	int	z;
407 	int	node;
408 	HPOLYGON hPath;
409 
410 	assert(pMover); // Moving null moving actor
411 	assert(pMover->actorObj);
412 
413 	pMover->objX = x;
414 	pMover->objY = y;
415 	MultiSetAniXY(pMover->actorObj, x, y);
416 
417 	hPath = InPolygon(x, y, PATH);
418 	if (hPath != NOPOLY) {
419 		pMover->hCpath = hPath;
420 		if (PolySubtype(hPath) == NODE) {
421 			node = NearestNodeWithin(hPath, x, y);
422 			getNpathNode(hPath, node, &pMover->objX, &pMover->objY);
423 			pMover->hFnpath = hPath;
424 			pMover->line = node;
425 			pMover->npstatus = GOING_UP;
426 		} else {
427 			pMover->hFnpath = NOPOLY;
428 			pMover->npstatus = NOT_IN;
429 		}
430 
431 		z = GetScale(hPath, pMover->objY);
432 		pMover->scale = z;
433 		SetMoverStanding(pMover);
434 	} else {
435 		pMover->bNoPath = true;
436 
437 		pMover->hFnpath = NOPOLY;	// Ain't in one
438 		pMover->npstatus = NOT_IN;
439 
440 		// Ensure legal reel and scale
441 		if (pMover->direction < 0 || pMover->direction > 3)
442 			pMover->direction = FORWARD;
443 		if (pMover->scale < 0 || pMover->scale > TOTAL_SCALES)
444 			pMover->scale = 1;
445 	}
446 }
447 
448 /**
449  * Get position of a moving actor.
450  */
GetMoverPosition(PMOVER pMover,int * paniX,int * paniY)451 void GetMoverPosition(PMOVER pMover, int *paniX, int *paniY) {
452 	assert(pMover); // Getting null moving actor's position
453 
454 	if (pMover->actorObj != NULL)
455 		GetAniPosition(pMover->actorObj, paniX, paniY);
456 	else {
457 		*paniX = 0;
458 		*paniY = 0;
459 	}
460 }
461 
462 /**
463  * Moving actor's mid-top position.
464  */
GetMoverMidTop(PMOVER pMover,int * aniX,int * aniY)465 void GetMoverMidTop(PMOVER pMover, int *aniX, int *aniY) {
466 	assert(pMover); // Getting null moving actor's mid-top position
467 	assert(pMover->actorObj); // Getting null moving actor's mid-top position
468 
469 	*aniX = (MultiLeftmost(pMover->actorObj) + MultiRightmost(pMover->actorObj)) / 2;
470 	*aniY = MultiHighest(pMover->actorObj);
471 }
472 
473 /**
474  * Moving actor's left-most co-ordinate.
475  */
GetMoverLeft(PMOVER pMover)476 int GetMoverLeft(PMOVER pMover) {
477 	assert(pMover); // Getting null moving actor's leftmost position
478 	if (pMover->type == MOVER_3D) {
479 		warning("TODO: Finish implementation of GetMoverLeft() for Noir");
480 		return 0;
481 	} else {
482 		assert(pMover->actorObj); // Getting null moving actor's leftmost position
483 		return MultiLeftmost(pMover->actorObj);
484 	}
485 }
486 
487 /**
488  * Moving actor's right-most co-ordinate.
489  */
GetMoverRight(PMOVER pMover)490 int GetMoverRight(PMOVER pMover) {
491 	assert(pMover); // Getting null moving actor's rightmost position
492 	if (pMover->type == MOVER_3D) {
493 		warning("TODO: Finish implementation of GetMoverRight() for Noir");
494 		return 0;
495 	} else {
496 		assert(pMover->actorObj); // Getting null moving actor's rightmost position
497 		return MultiRightmost(pMover->actorObj);
498 	}
499 }
500 
501 /**
502  * Moving actor's top co-ordinate.
503  */
GetMoverTop(PMOVER pMover)504 int GetMoverTop(PMOVER pMover) {
505 	assert(pMover); // Getting null moving actor's topmost position
506 
507 	if (pMover->type == MOVER_3D) {
508 		warning("TODO: Finish implementation of GetMoverTop() for Noir");
509 		return 0;
510 	} else {
511 		assert(pMover->actorObj); // Getting null moving actor's topmost position
512 		return MultiHighest(pMover->actorObj);
513 	}
514 }
515 
516 /**
517  * Moving actor's bottom co-ordinate.
518  */
GetMoverBottom(PMOVER pMover)519 int GetMoverBottom(PMOVER pMover) {
520 	assert(pMover); // Getting null moving actor's bottommost position
521 	if (pMover->type == MOVER_3D) {
522 		warning("TODO: Finish implementation of GetMoverBottom() for Noir");
523 		return 0;
524 	} else {
525 		assert(pMover->actorObj); // Getting null moving actor's bottommost position
526 		return MultiLowest(pMover->actorObj);
527 	}
528 }
529 
530 /**
531  * See if moving actor is stood within a polygon.
532  */
MoverIsInPolygon(PMOVER pMover,HPOLYGON hp)533 bool MoverIsInPolygon(PMOVER pMover, HPOLYGON hp) {
534 	assert(pMover); // Checking if null moving actor is in polygon
535 	assert(pMover->actorObj); // Checking if null moving actor is in polygon
536 
537 	int aniX, aniY;
538 	GetAniPosition(pMover->actorObj, &aniX, &aniY);
539 
540 	return IsInPolygon(aniX, aniY, hp);
541 }
542 
543 /**
544  * Change which reel is playing for a moving actor.
545  */
AlterMover(PMOVER pMover,SCNHANDLE film,AR_FUNCTION fn)546 void AlterMover(PMOVER pMover, SCNHANDLE film, AR_FUNCTION fn) {
547 	const FILM *pfilm;
548 
549 	assert(pMover->actorObj); // Altering null moving actor's animation script
550 
551 	if (fn == AR_POPREEL) {
552 		// Use the saved film
553 		film = pMover->hPushedFilm;
554 	}
555 	if (fn == AR_PUSHREEL) {
556 		// Save the one we're replacing
557 		pMover->hPushedFilm = (pMover->bSpecReel) ? pMover->hLastFilm : 0;
558 	}
559 
560 	if (film == 0) {
561 		if (pMover->bSpecReel) {
562 			// Revert to 'normal' actor
563 			SetMoverWalkReel(pMover, pMover->direction, pMover->scale, true);
564 			pMover->bSpecReel = false;
565 		}
566 	} else {
567 		// Remember this one in case the actor talks
568 		pMover->hLastFilm = film;
569 
570 		pfilm = (const FILM *)_vm->_handle->LockMem(film);
571 		assert(pfilm != NULL);
572 
573 		InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_32(pfilm->reels[0].script), ONE_SECOND / FROM_32(pfilm->frate));
574 		if (!TinselV2)
575 			pMover->stepCount = 0;
576 
577 		// If no path, just use first path in the scene
578 		if (pMover->hCpath != NOPOLY)
579 			SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath));
580 		else
581 			SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly()));
582 
583 		if (fn == AR_WALKREEL) {
584 			pMover->bSpecReel = false;
585 			pMover->bWalkReel = true;
586 		} else {
587 			pMover->bSpecReel = true;
588 			pMover->bWalkReel = false;
589 
590 #ifdef DEBUG
591 			assert(StepAnimScript(&pMover->actorAnim) != ScriptFinished); // Actor reel has finished!
592 #else
593 			StepAnimScript(&pMover->actorAnim);	// 04/01/95
594 #endif
595 		}
596 
597 		// Hang on, we may not want him yet! 04/01/95
598 		if (pMover->bHidden)
599 			MultiSetZPosition(pMover->actorObj, -1);
600 	}
601 }
602 
603 /**
604  * Return the actor's direction.
605  */
GetMoverDirection(PMOVER pMover)606 DIRECTION GetMoverDirection(PMOVER pMover) {
607 	return pMover->direction;
608 }
609 
610 /**
611  * Return the actor's scale.
612  */
GetMoverScale(PMOVER pMover)613 int GetMoverScale(PMOVER pMover) {
614 	return pMover->scale;
615 }
616 
617 /**
618  * Point actor in specified derection
619  */
SetMoverDirection(PMOVER pMover,DIRECTION dirn)620 void SetMoverDirection(PMOVER pMover, DIRECTION dirn) {
621 	pMover->direction = dirn;
622 }
623 
624 /**
625  * Get actor to adopt its appropriate standing reel.
626  */
SetMoverStanding(PMOVER pMover)627 void SetMoverStanding(PMOVER pMover) {
628 	if (TinselV3) {
629 		warning("TODO: Finish implementation of GetMoverStanding() for Noir");
630 		return;
631 	}
632 	assert(pMover->actorObj);
633 	AlterMover(pMover, pMover->standReels[pMover->scale - 1][pMover->direction], AR_NORMAL);
634 }
635 
636 /**
637  * Get actor to adopt its appropriate walking reel.
638  */
SetMoverWalkReel(PMOVER pMover,DIRECTION reel,int scale,bool force)639 void SetMoverWalkReel(PMOVER pMover, DIRECTION reel, int scale, bool force) {
640 	SCNHANDLE	whichReel;
641 	const FILM *pfilm;
642 
643 	// Kill off any play that may be going on for this actor
644 	// and restore the real actor
645 	_vm->_actor->storeActorReel(pMover->actorID, NULL, 0, NULL, 0, 0, 0);
646 	UnHideMover(pMover);
647 
648 	// Don't do it if using a special walk reel
649 	if (pMover->bWalkReel)
650 		return;
651 
652 	if (force || pMover->scale != scale || pMover->direction != reel) {
653 		assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel
654 
655 		// If scale change and both are regular scales
656 		// and there's a scaling reel in the right direction
657 		if (pMover->scale != scale
658 				&& scale <= NUM_MAINSCALES && pMover->scale <= NUM_MAINSCALES
659 				&& (whichReel = ScalingReel(pMover->actorID, pMover->scale, scale, reel)) != 0) {
660 //			error("Cripes");
661 			;	// Use what is now in 'whichReel'
662 		} else {
663 			whichReel = pMover->walkReels[scale-1][reel];
664 			assert(whichReel); // no reel
665 		}
666 
667 		pfilm = (const FILM *)_vm->_handle->LockMem(whichReel);
668 		assert(pfilm != NULL); // no film
669 
670 		InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_32(pfilm->reels[0].script), 1);
671 
672 		// Synchronised walking reels
673 		assert(pMover->stepCount >= 0);
674 		SkipFrames(&pMover->actorAnim, pMover->stepCount);
675 
676 		pMover->scale = scale;
677 		pMover->direction = reel;
678 	}
679 }
680 
681 /**
682  * Sort some stuff out at actor start-up time.
683  */
InitialPathChecks(PMOVER pMover,int xpos,int ypos)684 static void InitialPathChecks(PMOVER pMover, int xpos, int ypos) {
685 	HPOLYGON hPath;
686 	int	node;
687 	int	z;
688 
689 	pMover->objX = xpos;
690 	pMover->objY = ypos;
691 
692 	/*--------------------------------------
693 	| If Actor is in a follow nodes path,	|
694 	| position it at the nearest node.	|
695 	 --------------------------------------*/
696 	hPath = InPolygon(xpos, ypos, PATH);
697 
698 	if (hPath != NOPOLY) {
699 		pMover->hCpath = hPath;
700 		if (PolySubtype(hPath) == NODE) {
701 			node = NearestNodeWithin(hPath, xpos, ypos);
702 			getNpathNode(hPath, node, &pMover->objX, &pMover->objY);
703 			pMover->hFnpath = hPath;
704 			pMover->line = node;
705 			pMover->npstatus = GOING_UP;
706 		}
707 
708 		z = GetScale(hPath, pMover->objY);
709 	} else {
710 		pMover->bNoPath = true;
711 
712 		z = GetScale(FirstPathPoly(), pMover->objY);
713 	}
714 	if (TinselV3) {
715 		warning("TODO: Finish implementation of InitialPathChecks() for Noir");
716 	} else {
717 		SetMoverWalkReel(pMover, FORWARD, z, false);
718 	}
719 }
720 
MoverProcessHelper(int X,int Y,int id,PMOVER pMover)721 static void MoverProcessHelper(int X, int Y, int id, PMOVER pMover) {
722 	const FILM *pfilm;
723 	const MULTI_INIT *pmi;
724 	const FRAME *pFrame;
725 	IMAGE *pim;
726 
727 
728 	assert(_vm->_bg->BgPal()); // Can't start actor without a background palette
729 	assert(pMover->walkReels[0][FORWARD]); // Starting actor process without walk reels
730 
731 	InitMover(pMover);
732 	InitialPathChecks(pMover, X, Y);
733 
734 	pfilm = (const FILM *)_vm->_handle->LockMem(pMover->walkReels[0][FORWARD]);
735 	pmi = (const MULTI_INIT *)_vm->_handle->LockMem(FROM_32(pfilm->reels[0].mobj));
736 
737 //---
738 	pFrame = (const FRAME *)_vm->_handle->LockMem(FROM_32(pmi->hMulFrame));
739 
740 	// get pointer to image
741 	pim = (IMAGE *)_vm->_handle->LockMem(READ_32(pFrame)); // handle to image
742 	pim->hImgPal = TO_32(_vm->_bg->BgPal());
743 //---
744 	pMover->actorObj = MultiInitObject(pmi);
745 
746 /**/	assert(pMover->actorID == id);
747 	pMover->actorID = id;
748 
749 	// add it to display list
750 	MultiInsertObject(_vm->_bg->GetPlayfieldList(FIELD_WORLD), pMover->actorObj);
751 	_vm->_actor->storeActorReel(id, NULL, 0, pMover->actorObj, 0, 0, 0);
752 
753 	InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, FROM_32(pfilm->reels[0].script), ONE_SECOND / FROM_32(pfilm->frate));
754 	pMover->stepCount = 0;
755 
756 	MultiSetAniXY(pMover->actorObj, pMover->objX, pMover->objY);
757 
758 	// If no path, just use first path in the scene
759 	if (pMover->hCpath != NOPOLY)
760 		SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath));
761 	else
762 		SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly()));
763 
764 	// Make him the right size
765 	SetMoverStanding(pMover);
766 
767 //**** if added 18/11/94, am
768 	if (X != MAGICX && Y != MAGICY) {
769 		HideMover(pMover, 0);		// Allows a play to come in before this appears
770 		pMover->bHidden = false;	// ...but don't stay hidden
771 	}
772 
773 	pMover->bActive = true;
774 }
775 
776 /**
777  * Moving actor process - 1 per moving actor in current scene.
778  */
T1MoverProcess(CORO_PARAM,const void * param)779 void T1MoverProcess(CORO_PARAM, const void *param) {
780 	// COROUTINE
781 	CORO_BEGIN_CONTEXT;
782 	CORO_END_CONTEXT(_ctx);
783 
784 	const PMOVER pActor = *(const PMOVER *)param;
785 
786 	CORO_BEGIN_CODE(_ctx);
787 
788 	while (1) {
789 		if (pActor->bSpecReel) {
790 			if (!pActor->bHidden)
791 #ifdef DEBUG
792 			assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
793 #else
794 			StepAnimScript(&pActor->actorAnim);
795 #endif
796 		} else
797 			DoMoveActor(pActor);
798 
799 		CORO_SLEEP(1);		// allow rescheduling
800 
801 	}
802 
803 	CORO_END_CODE;
804 }
805 
806 /**
807  * Tinsel 2 Moving actor process
808  * - 1 per moving actor in current scene.
809  */
T2MoverProcess(CORO_PARAM,const void * param)810 void T2MoverProcess(CORO_PARAM, const void *param) {
811 	CORO_BEGIN_CONTEXT;
812 	CORO_END_CONTEXT(_ctx);
813 
814 	// Get the co-ordinates - copied to process when it was created
815 	const MAINIT *rpos = (const MAINIT *)param;
816 	PMOVER pMover = rpos->pMover;
817 	int i;
818 	FILM *pFilm;
819 	PMULTI_INIT pmi;
820 
821 	CORO_BEGIN_CODE(_ctx);
822 
823 	for (i = 0; i < TOTAL_SCALES; i++) {
824 		if (pMover->walkReels[i][FORWARD])
825 			break;
826 	}
827 	assert(i < TOTAL_SCALES);
828 
829 	InitMover(pMover);
830 	InitialPathChecks(pMover, rpos->X, rpos->Y);
831 
832 	pFilm = (FILM *)_vm->_handle->LockMem(pMover->walkReels[i][FORWARD]); // Any old reel
833 	pmi = (PMULTI_INIT)_vm->_handle->LockMem(FROM_32(pFilm->reels[0].mobj));
834 
835 	// Poke in the background palette
836 	PokeInPalette(pmi);
837 
838 	pMover->actorObj = MultiInitObject(pmi);
839 	// FIXME: This is what the original did. A bug, perhaps?
840 	// pMover->actorID = pMover->actorID;
841 	pMover->bActive = true;
842 
843 	// add it to display list
844 	MultiInsertObject(_vm->_bg->GetPlayfieldList(FIELD_WORLD), pMover->actorObj);
845 
846 	InitStepAnimScript(&pMover->actorAnim, pMover->actorObj, pFilm->reels[0].script, ONE_SECOND/pFilm->frate);
847 	pMover->stepCount = 0;
848 
849 	MultiSetAniXY(pMover->actorObj, pMover->objX, pMover->objY);
850 
851 	// If no path, just use first path in the scene
852 	if (pMover->hCpath != NOPOLY)
853 		SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath));
854 	else
855 		SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly()));
856 
857 	// Make him the right size
858 	SetMoverStanding(pMover);
859 
860 	HideMover(pMover);		// Allows a play to come in before this appears
861 	pMover->bHidden = false;	// ...but don't stay hidden
862 
863 	for (;;) {
864 		if (pMover->bSpecReel) {
865 			if (!pMover->bHidden)
866 				StepAnimScript(&pMover->actorAnim);
867 		} else
868 			DoMoveActor(pMover);
869 
870 		CheckBrightness(pMover);
871 
872 		CORO_SLEEP(1);
873 	}
874 
875 	CORO_END_CODE;
876 }
877 
878 /**
879  * Tinsel 3 Moving actor process
880  * - 1 per moving actor in current scene.
881  */
T3MoverProcess(CORO_PARAM,const void * param)882 void T3MoverProcess(CORO_PARAM, const void *param) {
883 	CORO_BEGIN_CONTEXT;
884 	CORO_END_CONTEXT(_ctx);
885 
886 	// Get the co-ordinates - copied to process when it was created
887 	const MAINIT *rpos = (const MAINIT *)param;
888 	PMOVER pMover = rpos->pMover;
889 
890 	CORO_BEGIN_CODE(_ctx);
891 
892 	warning("TODO: Finish implementation of T3MoverProcess() for Noir");
893 
894 	InitMover(pMover);
895 	InitialPathChecks(pMover, rpos->X, rpos->Y);
896 
897 	HideMover(pMover);		// Allows a play to come in before this appears
898 	pMover->bHidden = false;	// ...but don't stay hidden
899 
900 	for (;;) {
901 		DoMoveActor(pMover);
902 
903 		CheckBrightness(pMover);
904 
905 		CORO_SLEEP(1);
906 	}
907 
908 	CORO_END_CODE;
909 }
910 
911 
912 /**
913  * Creates a handling process for a moving actor
914  */
MoverProcessCreate(int X,int Y,int id,PMOVER pMover)915 void MoverProcessCreate(int X, int Y, int id, PMOVER pMover) {
916 	if (TinselV2 || TinselV3) {
917 		MAINIT iStruct;
918 		iStruct.X = X;
919 		iStruct.Y = Y;
920 		iStruct.pMover = pMover;
921 
922 		CoroScheduler.createProcess(PID_MOVER, TinselV3 ? T3MoverProcess : T2MoverProcess, &iStruct, sizeof(MAINIT));
923 	} else {
924 		MoverProcessHelper(X, Y, id, pMover);
925 		pMover->pProc = CoroScheduler.createProcess(PID_MOVER, T1MoverProcess, &pMover, sizeof(PMOVER));
926 	}
927 }
928 
929 /**
930  * Check for moving actor collision.
931  */
InMoverBlock(PMOVER pMover,int x,int y)932 PMOVER InMoverBlock(PMOVER pMover, int x, int y) {
933 	int	caX;		// Calling actor's pos'n
934 	int	caL, caR;	// Calling actor's left and right
935 	int	taX, taY;	// Test actor's pos'n
936 	int	taL, taR;	// Test actor's left and right
937 
938 	caX = pMover->objX;
939 	if (pMover->hFnpath != NOPOLY || GetNoBlocking())
940 		return NULL;
941 
942 	caL = GetMoverLeft(pMover) + x - caX;
943 	caR = GetMoverRight(pMover) + x - caX;
944 
945 	for (int i = 0; i < MAX_MOVERS; i++) {
946 		if (pMover == &g_Movers[i] ||
947 				(TinselV2 && (g_Movers[i].actorObj == NULL)) ||
948 				(!TinselV2 && !g_Movers[i].bActive))
949 			continue;
950 
951 		// At around the same height?
952 		GetMoverPosition(&g_Movers[i], &taX, &taY);
953 		if (g_Movers[i].hFnpath != NOPOLY)
954 			continue;
955 
956 		if (ABS(y - taY) > 2)	// 2 was 8
957 			continue;
958 
959 		// To the left?
960 		taL = GetMoverLeft(&g_Movers[i]);
961 		if (caR <= taL)
962 			continue;
963 
964 		// To the right?
965 		taR = GetMoverRight(&g_Movers[i]);
966 		if (caL >= taR)
967 			continue;
968 
969 		return &g_Movers[i];
970 	}
971 	return NULL;
972 }
973 
974 /**
975  * Copies key information for savescn.c to store away.
976  */
SaveMovers(SAVED_MOVER * sMoverInfo)977 void SaveMovers(SAVED_MOVER *sMoverInfo) {
978 	for (int i = 0; i < MAX_MOVERS; i++) {
979 		sMoverInfo[i].bActive = !TinselV2 ? g_Movers[i].bActive : g_Movers[i].actorObj != NULL;
980 		sMoverInfo[i].actorID	= g_Movers[i].actorID;
981 		sMoverInfo[i].objX	= g_Movers[i].objX;
982 		sMoverInfo[i].objY	= g_Movers[i].objY;
983 		sMoverInfo[i].hLastfilm	= g_Movers[i].hLastFilm;
984 
985 		if (TinselV2) {
986 			sMoverInfo[i].bHidden = g_Movers[i].bHidden;
987 			sMoverInfo[i].brightness = g_Movers[i].brightness;
988 			sMoverInfo[i].startColor = g_Movers[i].startColor;
989 			sMoverInfo[i].paletteLength = g_Movers[i].paletteLength;
990 		}
991 
992 		memcpy(sMoverInfo[i].walkReels, g_Movers[i].walkReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
993 		memcpy(sMoverInfo[i].standReels, g_Movers[i].standReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
994 		memcpy(sMoverInfo[i].talkReels, g_Movers[i].talkReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
995 	}
996 }
997 
RestoreAuxScales(SAVED_MOVER * sMoverInfo)998 void RestoreAuxScales(SAVED_MOVER *sMoverInfo) {
999 	for (int i = 0; i < MAX_MOVERS; i++) {
1000 		if (TinselV2)
1001 			g_Movers[i].actorID = sMoverInfo[i].actorID;
1002 
1003 		memcpy(g_Movers[i].walkReels, sMoverInfo[i].walkReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
1004 		memcpy(g_Movers[i].standReels, sMoverInfo[i].standReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
1005 		memcpy(g_Movers[i].talkReels, sMoverInfo[i].talkReels, TOTAL_SCALES * 4 * sizeof(SCNHANDLE));
1006 	}
1007 }
1008 
1009 
NextMover(PMOVER pMover)1010 PMOVER NextMover(PMOVER pMover) {
1011 	int next;
1012 
1013 	if (pMover == NULL)
1014 		next = 0;
1015 	else
1016 		next = pMover - g_Movers + 1;
1017 
1018 	if (g_Movers[next].actorID)
1019 		return &g_Movers[next];
1020 	else
1021 		return NULL;
1022 }
1023 
StopMover(PMOVER pMover)1024 void StopMover(PMOVER pMover) {
1025 	pMover->bStop = true;
1026 	DoMoveActor(pMover);
1027 }
1028 
1029 } // End of namespace Tinsel
1030