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  * Additional copyright for this file:
8  * Copyright (C) 1994-1998 Revolution Software Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 // WALKER.CPP by James (14nov96)
26 
27 // Functions for moving megas about the place & also for keeping tabs on them
28 
29 
30 
31 #include "sword2/sword2.h"
32 #include "sword2/defs.h"
33 #include "sword2/header.h"
34 #include "sword2/interpreter.h"
35 #include "sword2/logic.h"
36 #include "sword2/resman.h"
37 #include "sword2/router.h"
38 #include "sword2/screen.h"
39 
40 namespace Sword2 {
41 
setStandbyCoords(int16 x,int16 y,uint8 dir)42 void Router::setStandbyCoords(int16 x, int16 y, uint8 dir) {
43 	assert(dir <= 7);
44 
45 	_standbyX = x;
46 	_standbyY = y;
47 	_standbyDir = dir;
48 }
49 
50 /**
51  * Work out direction from start to dest.
52  */
53 
54 // Used in whatTarget(); not valid for all megas
55 #define	diagonalx 36
56 #define	diagonaly 8
57 
whatTarget(int startX,int startY,int destX,int destY)58 int Router::whatTarget(int startX, int startY, int destX, int destY) {
59 	int deltaX = destX - startX;
60 	int deltaY = destY - startY;
61 
62 	// 7 0 1
63 	// 6   2
64 	// 5 4 3
65 
66 	// Flat route
67 
68 	if (ABS(deltaY) * diagonalx < ABS(deltaX) * diagonaly / 2)
69 		return (deltaX > 0) ? 2 : 6;
70 
71 	// Vertical route
72 
73 	if (ABS(deltaY) * diagonalx / 2 > ABS(deltaX) * diagonaly)
74 		return (deltaY > 0) ? 4 : 0;
75 
76 	// Diagonal route
77 
78 	if (deltaX > 0)
79 		return (deltaY > 0) ? 3 : 1;
80 
81 	return (deltaY > 0) ? 5 : 7;
82 }
83 
84 /**
85  * Walk meta to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set
86  * RESULT to 1. Return true if the mega has finished walking.
87  */
88 
doWalk(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,int16 target_x,int16 target_y,uint8 target_dir)89 int Router::doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir) {
90 	ObjectLogic obLogic(ob_logic);
91 	ObjectGraphic obGraph(ob_graph);
92 	ObjectMega obMega(ob_mega);
93 
94 	// If this is the start of the walk, calculate the route.
95 
96 	if (obLogic.getLooping() == 0) {
97 		// If we're already there, don't even bother allocating
98 		// memory and calling the router, just quit back & continue
99 		// the script! This avoids an embarassing mega stand frame
100 		// appearing for one cycle when we're already in position for
101 		// an anim eg. repeatedly clicking on same object to repeat
102 		// an anim - no mega frame will appear in between runs of the
103 		// anim.
104 
105 		if (obMega.getFeetX() == target_x && obMega.getFeetY() == target_y && obMega.getCurDir() == target_dir) {
106 			_vm->_logic->writeVar(RESULT, 0);
107 			return IR_CONT;
108 		}
109 
110 		assert(target_dir <= 8);
111 
112 		obMega.setWalkPc(0);
113 
114 		// Set up mem for _walkData in route_slots[] & set mega's
115 		// 'route_slot_id' accordingly
116 		allocateRouteMem();
117 
118 		int32 route = routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir);
119 
120 		// 0 = can't make route to target
121 		// 1 = created route
122 		// 2 = zero route but may need to turn
123 
124 		if (route != 1 && route != 2) {
125 			freeRouteMem();
126 			_vm->_logic->writeVar(RESULT, 1);
127 			return IR_CONT;
128 		}
129 
130 		// Walk is about to start
131 
132 		obMega.setIsWalking(1);
133 		obLogic.setLooping(1);
134 		obGraph.setAnimResource(obMega.getMegasetRes());
135 	} else if (_vm->_logic->readVar(EXIT_FADING) && _vm->_screen->getFadeStatus() == RDFADE_BLACK) {
136 		// Double clicked an exit, and the screen has faded down to
137 		// black. Ok, that's it. Back to script and change screen.
138 
139 		// We have to clear te EXIT_CLICK_ID variable in case there's a
140 		// walk instruction on the new screen, or it'd be cut short.
141 
142 		freeRouteMem();
143 
144 		obLogic.setLooping(0);
145 		obMega.setIsWalking(0);
146 		_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
147 		_vm->_logic->writeVar(RESULT, 0);
148 
149 		return IR_CONT;
150 	}
151 
152 	// Get pointer to walkanim & current frame position
153 
154 	WalkData *walkAnim = getRouteMem();
155 	int32 walk_pc = obMega.getWalkPc();
156 
157 	// If stopping the walk early, overwrite the next step with a
158 	// slow-out, then finish
159 
160 	if (_vm->_logic->checkEventWaiting() && walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) {
161 		// At the beginning of a step
162 		earlySlowOut(ob_mega, ob_walkdata);
163 	}
164 
165 	// Get new frame of walk
166 
167 	obGraph.setAnimPc(walkAnim[walk_pc].frame);
168 	obMega.setCurDir(walkAnim[walk_pc].dir);
169 	obMega.setFeetX(walkAnim[walk_pc].x);
170 	obMega.setFeetY(walkAnim[walk_pc].y);
171 
172 	// Is the NEXT frame is the end-marker (512) of the walk sequence?
173 
174 	if (walkAnim[walk_pc + 1].frame != 512) {
175 		// No, it wasn't. Increment the walk-anim frame number and
176 		// come back next cycle.
177 		obMega.setWalkPc(obMega.getWalkPc() + 1);
178 		return IR_REPEAT;
179 	}
180 
181 	// We have reached the end-marker, which means we can return to the
182 	// script just as the final (stand) frame of the walk is set.
183 
184 	freeRouteMem();
185 	obLogic.setLooping(0);
186 	obMega.setIsWalking(0);
187 
188 	// If George's walk has been interrupted to run a new action script for
189 	// instance or Nico's walk has been interrupted by player clicking on
190 	// her to talk
191 
192 	// There used to be code here for checking if two megas were colliding,
193 	// but it had been commented out, and it was only run if a function
194 	// that always returned zero returned non-zero.
195 
196 	if (_vm->_logic->checkEventWaiting()) {
197 		_vm->_logic->startEvent();
198 		_vm->_logic->writeVar(RESULT, 1);
199 		return IR_TERMINATE;
200 	}
201 
202 	_vm->_logic->writeVar(RESULT, 0);
203 
204 	// CONTINUE the script so that RESULT can be checked! Also, if an anim
205 	// command follows the fnWalk command, the 1st frame of the anim (which
206 	// is always a stand frame itself) can replace the final stand frame of
207 	// the walk, to hide the slight difference between the shrinking on the
208 	// mega frames and the pre-shrunk anim start-frame.
209 
210 	return IR_CONT;
211 }
212 
213 /**
214  * Walk mega to start position of anim
215  */
216 
walkToAnim(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,uint32 animRes)217 int Router::walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes) {
218 	int16 target_x = 0;
219 	int16 target_y = 0;
220 	uint8 target_dir = 0;
221 
222 	// Walkdata is needed for earlySlowOut if player clicks elsewhere
223 	// during the walk.
224 
225 	// If this is the start of the walk, read anim file to get start coords
226 
227 	ObjectLogic obLogic(ob_logic);
228 
229 	if (obLogic.getLooping() == 0) {
230 		byte *anim_file = _vm->_resman->openResource(animRes);
231 		AnimHeader anim_head;
232 
233 		anim_head.read(_vm->fetchAnimHeader(anim_file));
234 
235 		target_x = anim_head.feetStartX;
236 		target_y = anim_head.feetStartY;
237 		target_dir = anim_head.feetStartDir;
238 
239 		_vm->_resman->closeResource(animRes);
240 
241 		// If start coords not yet set in anim header, use the standby
242 		// coords (which should be set beforehand in the script).
243 
244 		if (target_x == 0 && target_y == 0) {
245 			target_x = _standbyX;
246 			target_y = _standbyY;
247 			target_dir = _standbyDir;
248 		}
249 
250 		assert(target_dir <= 7);
251 	}
252 
253 	return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
254 }
255 
256 /**
257  * Route to the left or right hand side of target id, if possible.
258  */
259 
walkToTalkToMega(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,uint32 megaId,uint32 separation)260 int Router::walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation) {
261 	ObjectMega obMega(ob_mega);
262 
263 	int16 target_x = 0;
264 	int16 target_y = 0;
265 	uint8 target_dir = 0;
266 
267 	// If this is the start of the walk, calculate the route.
268 
269 	ObjectLogic obLogic(ob_logic);
270 
271 	if (obLogic.getLooping() == 0) {
272 		assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
273 
274 		// Call the base script. This is the graphic/mouse service
275 		// call, and will set _engineMega to the ObjectMega of mega we
276 		// want to route to.
277 
278 		_vm->_logic->runResScript(megaId, 3);
279 
280 		ObjectMega targetMega(_vm->_logic->getEngineMega());
281 
282 		// Stand exactly beside the mega, ie. at same y-coord
283 		target_y = targetMega.getFeetY();
284 
285 		int scale = obMega.calcScale();
286 		int mega_separation = (separation * scale) / 256;
287 
288 		debug(4, "Target is at (%d, %d), separation %d", targetMega.getFeetX(), targetMega.getFeetY(), mega_separation);
289 
290 		if (targetMega.getFeetX() < obMega.getFeetX()) {
291 			// Target is left of us, so aim to stand to their
292 			// right. Face down_left
293 
294 			target_x = targetMega.getFeetX() + mega_separation;
295 			target_dir = 5;
296 		} else {
297 			// Ok, must be right of us so aim to stand to their
298 			// left. Face down_right.
299 
300 			target_x = targetMega.getFeetX() - mega_separation;
301 			target_dir = 3;
302 		}
303 	}
304 
305 	return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
306 }
307 /**
308  * Turn mega to the specified direction. Just needs to call doWalk() with
309  * current feet coords, so router can produce anim of turn frames.
310  */
311 
doFace(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,uint8 target_dir)312 int Router::doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir) {
313 	int16 target_x = 0;
314 	int16 target_y = 0;
315 
316 	// If this is the start of the turn, get the mega's current feet
317 	// coords + the required direction
318 
319 	ObjectLogic obLogic(ob_logic);
320 
321 	if (obLogic.getLooping() == 0) {
322 		assert(target_dir <= 7);
323 
324 		ObjectMega obMega(ob_mega);
325 
326 		target_x = obMega.getFeetX();
327 		target_y = obMega.getFeetY();
328 	}
329 
330 	return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
331 }
332 
333 /**
334  * Turn mega to face point (x,y) on the floor
335  */
336 
faceXY(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,int16 target_x,int16 target_y)337 int Router::faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y) {
338 	uint8 target_dir = 0;
339 
340 	// If this is the start of the turn, get the mega's current feet
341 	// coords + the required direction
342 
343 	ObjectLogic obLogic(ob_logic);
344 
345 	if (obLogic.getLooping() == 0) {
346 		ObjectMega obMega(ob_mega);
347 
348 		target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), target_x, target_y);
349 	}
350 
351 	return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
352 }
353 
354 /**
355  * Turn mega to face another mega.
356  */
357 
faceMega(byte * ob_logic,byte * ob_graph,byte * ob_mega,byte * ob_walkdata,uint32 megaId)358 int Router::faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId) {
359 	uint8 target_dir = 0;
360 
361 	// If this is the start of the walk, decide where to walk to.
362 
363 	ObjectLogic obLogic(ob_logic);
364 
365 	if (obLogic.getLooping() == 0) {
366 		assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
367 
368 		// Call the base script. This is the graphic/mouse service
369 		// call, and will set _engineMega to the ObjectMega of mega we
370 		// want to turn to face.
371 
372 		_vm->_logic->runResScript(megaId, 3);
373 
374 		ObjectMega obMega(ob_mega);
375 		ObjectMega targetMega(_vm->_logic->getEngineMega());
376 
377 		target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), targetMega.getFeetX(), targetMega.getFeetY());
378 	}
379 
380 	return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
381 }
382 
383 /**
384  * Stand mega at (x,y,dir)
385  * Sets up the graphic object, but also needs to set the new 'current_dir' in
386  * the mega object, so the router knows in future
387  */
388 
standAt(byte * ob_graph,byte * ob_mega,int32 x,int32 y,int32 dir)389 void Router::standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir) {
390 	assert(dir >= 0 && dir <= 7);
391 
392 	ObjectGraphic obGraph(ob_graph);
393 	ObjectMega obMega(ob_mega);
394 
395 	// Set up the stand frame & set the mega's new direction
396 
397 	obMega.setFeetX(x);
398 	obMega.setFeetY(y);
399 	obMega.setCurDir(dir);
400 
401 	// Mega-set animation file
402 	obGraph.setAnimResource(obMega.getMegasetRes());
403 
404 	// Dir + first stand frame (always frame 96)
405 	obGraph.setAnimPc(dir + 96);
406 }
407 
408 /**
409  * Stand mega at end position of anim
410  */
411 
standAfterAnim(byte * ob_graph,byte * ob_mega,uint32 animRes)412 void Router::standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
413 	byte *anim_file = _vm->_resman->openResource(animRes);
414 	AnimHeader anim_head;
415 
416 	anim_head.read(_vm->fetchAnimHeader(anim_file));
417 
418 	int32 x = anim_head.feetEndX;
419 	int32 y = anim_head.feetEndY;
420 	int32 dir = anim_head.feetEndDir;
421 
422 	_vm->_resman->closeResource(animRes);
423 
424 	// If start coords not available either use the standby coords (which
425 	// should be set beforehand in the script)
426 
427 	if (x == 0 && y == 0) {
428 		x = _standbyX;
429 		y = _standbyY;
430 		dir = _standbyDir;
431 	}
432 
433 	standAt(ob_graph, ob_mega, x, y, dir);
434 }
435 
standAtAnim(byte * ob_graph,byte * ob_mega,uint32 animRes)436 void Router::standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
437 	byte *anim_file = _vm->_resman->openResource(animRes);
438 	AnimHeader anim_head;
439 
440 	anim_head.read(_vm->fetchAnimHeader(anim_file));
441 
442 	int32 x = anim_head.feetStartX;
443 	int32 y = anim_head.feetStartY;
444 	int32 dir = anim_head.feetStartDir;
445 
446 	_vm->_resman->closeResource(animRes);
447 
448 	// If start coords not available use the standby coords (which should
449 	// be set beforehand in the script)
450 
451 	if (x == 0 && y == 0) {
452 		x = _standbyX;
453 		y = _standbyY;
454 		dir = _standbyDir;
455 	}
456 
457 	standAt(ob_graph, ob_mega, x, y, dir);
458 }
459 
460 } // End of namespace Sword2
461