1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1999-2000 Revolution Software Ltd.
9  * This code is based on source code created by Revolution Software,
10  * used with permission.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  *
26  */
27 
28 #include "engines/icb/debug.h"
29 #include "engines/icb/mission.h"
30 #include "engines/icb/global_objects.h"
31 #include "engines/icb/common/ptr_util.h"
32 #include "engines/icb/bone.h"
33 #include "engines/icb/sound.h"
34 #include "engines/icb/icb.h"
35 
36 #include "common/textconsole.h"
37 
38 namespace ICB {
39 
40 #define NECK_PERCENT 16
41 #define NECK_RANGE 96
42 #define NECK_SPEED 8
43 
44 #define JAW_PERCENT 40
45 #define JAW_MAX 256
46 #define JAW_SPEED 32
47 
48 #define STANDARD_LOOK_SPEED 128
49 
50 // useful function to cap a short value at min-max range
LimitShort(short & v,short min,short max)51 void LimitShort(short &v, short min, short max) {
52 	if (v < min)
53 		v = min;
54 	else if (v > max)
55 		v = max;
56 }
57 
58 #define BULLET_COL 200
59 #define BULLET_DC -8
60 
61 #define BULLET_G -8  // dy+=g
62 #define BULLET_DX 4  // initial dy
63 #define BULLET_DY 16 // initial dy
64 
InitCartridgeCase(SVECTOR * initPos,short initialHeight)65 void _mega::InitCartridgeCase(SVECTOR *initPos, short initialHeight) {
66 	bulletInitialHeight = initialHeight;
67 	bulletOn = TRUE8;
68 	bulletPos = *initPos;
69 	bulletDX = BULLET_DX;
70 	bulletDY = BULLET_DY;
71 	bulletColour = (uint8)BULLET_COL;
72 	bulletBounced = (uint8)0;
73 }
74 
UpdateCartridgeCase()75 void _game_session::UpdateCartridgeCase() {
76 	if (M->bulletOn) {
77 		// gravity acceleration
78 		M->bulletDY = (int16)(M->bulletDY + BULLET_G);
79 
80 		// movement
81 		M->bulletPos.vx = (int16)(M->bulletPos.vx + M->bulletDX);
82 		M->bulletPos.vy = (int16)(M->bulletPos.vy + M->bulletDY);
83 
84 		// only reduce colour if >0
85 		if (M->bulletColour)
86 			M->bulletColour = (uint8)(M->bulletColour + BULLET_DC);
87 
88 		if ((M->bulletPos.vy < -M->bulletInitialHeight) && (M->bulletBounced >= 1)) { // we are at ground height and have already bounced enough (1) times, turn us off
89 			M->bulletOn = FALSE8;
90 		} else if (M->bulletPos.vy < -M->bulletInitialHeight) {     // otherwise if we are at floor height then bounce us back up...
91 			M->bulletPos.vy = (int16)(-M->bulletInitialHeight); // clip
92 			M->bulletDY = (int16)(-M->bulletDY / 2);
93 			M->bulletDX = (int16)(M->bulletDX / 2);
94 			M->bulletBounced++;
95 
96 			// this is where we make the bouncing sound...
97 			RegisterSound(cur_id, object->GetStringValueOrDefault(tinkleSfxVar, defaultTinkleSfx), tinkleDesc);
98 		}
99 	}
100 }
101 
102 // update talking character log
103 // using there rap file to get bones
104 // for jaw and neck. This sets random
105 // movement on neck (x,y,z) and random
106 // movement for jaw (just in x)
UpdateTalking(_logic * log,rap_API * rap)107 void UpdateTalking(_logic *log, rap_API *rap) {
108 	// check for -1 in rap
109 	if (rap->jawBone == (int8)-1)
110 		Tdebug("bones.txt", "mega %s speaking but has no jaw bone!", log->mega->chr_name);
111 	if (rap->neckBone == (int8)-1)
112 		Tdebug("bones.txt", "mega %s speaking but has no neck bone!", log->mega->chr_name);
113 
114 	// set deformations....
115 	BoneDeformation *neckBone = &(log->voxel_info->neckBone);
116 	BoneDeformation *jawBone = &(log->voxel_info->jawBone);
117 
118 	// set speed
119 	neckBone->boneSpeed = NECK_SPEED;
120 	jawBone->boneSpeed = JAW_SPEED;
121 
122 	// set number of bone from rap file
123 	jawBone->boneNumber = (int16)(rap->jawBone);
124 	neckBone->boneNumber = (int16)(rap->neckBone);
125 
126 	// unless it's -1 (no bone) we want the bone above this...
127 	if (neckBone->boneNumber != (int16)-1) {
128 		neckBone->boneNumber++; // add one to the value
129 	}
130 
131 	// random neck movement (all three planes)
132 	if ((g_icb->getRandomSource()->getRandomNumber(100 - 1)) < NECK_PERCENT) {
133 		neckBone->boneTarget.vx = (int16)((g_icb->getRandomSource()->getRandomNumber(2 * NECK_RANGE - 1)) - NECK_RANGE);
134 		neckBone->boneTarget.vz = (int16)((g_icb->getRandomSource()->getRandomNumber(2 * NECK_RANGE - 1)) - NECK_RANGE);
135 		neckBone->boneTarget.vy = (int16)((g_icb->getRandomSource()->getRandomNumber(2 * NECK_RANGE - 1)) - NECK_RANGE);
136 	}
137 
138 	// random jaw movement (just the v moves)
139 	if ((g_icb->getRandomSource()->getRandomNumber(100 - 1)) < JAW_PERCENT) {
140 		jawBone->boneTarget.vx = (int16)(g_icb->getRandomSource()->getRandomNumber(JAW_MAX - 1));
141 		jawBone->boneTarget.vz = (int16)0;
142 		jawBone->boneTarget.vy = (int16)0;
143 	}
144 }
145 
146 // player was shot by obj
147 // needs some work to  get right
SetPlayerShotBone(int32 obj_id)148 void SetPlayerShotBone(int32 obj_id) {
149 	_logic *player_log = MS->player.log;
150 	_logic *obj_log = MS->logic_structs[obj_id];
151 
152 	PXfloat px, py, pz, ox, oy, oz;
153 
154 	// get locations (only really interested in x and z
155 	player_log->GetPosition(px, py, pz);
156 	obj_log->GetPosition(ox, oy, oz);
157 
158 	PXfloat dx, dz;
159 
160 	dx = ox - px;
161 	dz = oz - pz;
162 
163 	// get player facing direction...
164 
165 	PXfloat player_pan;
166 
167 	if (MS->player.log->auto_panning == FALSE8)
168 		player_pan = MS->player.log->pan;
169 	else
170 		player_pan = MS->player.log->auto_display_pan;
171 
172 	int32 direction;
173 
174 // get direction got shot from...
175 
176 	direction = (int32)(4096.0 * (PXAngleOfVector(-dz, -dx) - player_pan));
177 
178 	// make sure it is -2048 - 2048
179 	if (direction > 2047)
180 		direction -= 4096;
181 
182 	if (direction < -2048)
183 		direction += 4096;
184 
185 	// bone number 2 set to (512,0,512)
186 
187 	if (abs(direction) < 1024)                             // shot from somewhere behind...
188 		MS->player.shotDeformation.boneValue.vx = 256; // front
189 	else
190 		MS->player.shotDeformation.boneValue.vx = -256; // back
191 
192 	if ((g_icb->getRandomSource()->getRandomNumber(100 - 1)) < 50) { // 50% chance of going either way left or right
193 		MS->player.shotDeformation.boneValue.vy = 32;            // turn a bit just for variation
194 		MS->player.shotDeformation.boneValue.vz = 32;            // turn a bit just for variation
195 	} else {
196 		MS->player.shotDeformation.boneValue.vy = -32; // turn
197 		MS->player.shotDeformation.boneValue.vz = -32; // turn a bit just for variation
198 	}
199 
200 	MS->player.shotDeformation.Target0();
201 
202 	MS->player.shotDeformation.boneNumber = 1;
203 	MS->player.shotDeformation.boneSpeed = 128;
204 }
205 
206 #define STATUS_NONE 0
207 #define STATUS_BODY 1
208 #define STATUS_HEAD 2
209 
210 #define PLAYER_STOOD_EYE 180
211 #define PLAYER_CROUCH_EYE 65
212 
213 #define MEGA_STOOD_EYE 170
214 #define MEGA_CROUCH_EYE 55
215 #define MEGA_DEAD_EYE 0
216 #define ROBOT_EYE 40
217 
218 #define STANDARD_MARKER_HEIGHT 170
219 
220 // update the neck bone
221 // should only be called with player as logic
UpdatePlayerLook()222 void UpdatePlayerLook() {
223 	static int32 status = STATUS_NONE;
224 
225 	_logic *log = MS->player.log;
226 	BoneDeformation *b = &(log->voxel_info->lookBone);
227 
228 	// start off with manually set values
229 	b->boneTarget = log->voxel_info->scriptedLookBoneTarget;
230 
231 	// interact object, or look at object if we don't have an interact one
232 	bool8 has_interact = MS->player.interact_selected;
233 	uint32 sel_id = MS->player.cur_interact_id;
234 
235 	// we have no interact so try getting a look at...
236 	if (!has_interact) {
237 		has_interact = MS->player.look_at_selected;
238 		sel_id = MS->player.look_at_id;
239 	}
240 
241 	// if we have a specific look set then we set bone number to 23
242 	if ((b->boneTarget.vx != 0) || (b->boneTarget.vy != 0) || (b->boneTarget.vz != 0)) {
243 		status = STATUS_NONE;
244 		b->boneNumber = 23;
245 		b->boneSpeed = STANDARD_LOOK_SPEED;
246 	}
247 
248 	// we only override these things if we do not have a bone target set (ie bone target is <0,0,0>)
249 	// also we only do this if the player is looking at something... (otherwise the targets are <0,0,0>
250 	// also we only do this when not playing a custom anim...
251 	if ((b->boneTarget.vx == 0) && (b->boneTarget.vy == 0) && (b->boneTarget.vz == 0) && // bone override not set
252 	    (has_interact) &&                                                                // are looking at something
253 	    (log->cur_anim_type != __PROMOTED_NON_GENERIC) &&                                // we are not on a special non-generic (custom)
254 	    (log->cur_anim_type != __NON_GENERIC)                                            // we are not on a non-generic (custom)
255 	    ) {
256 		_logic *target;
257 		PXfloat player_pan;
258 		PXreal ox, oy, oz, px, py, pz;
259 		PXreal dx, dy, dz;
260 		int32 playerEye;
261 
262 		// get postion of players head
263 
264 		// raw position (foot)
265 		log->GetPosition(px, py, pz);
266 
267 		if (log->mega->Is_crouched())
268 			playerEye = PLAYER_CROUCH_EYE; // crouch
269 		else
270 			playerEye = PLAYER_STOOD_EYE; // stand
271 
272 		// height increases to eye...
273 		py += playerEye;
274 
275 		// get position of targetting object
276 
277 		target = MS->logic_structs[sel_id];
278 
279 		// raw position
280 		target->GetPosition(ox, oy, oz);
281 
282 		// target is an actor so need adjustment for eye height...
283 		if (target->image_type == VOXEL) {
284 			c_game_object *pGameObject = (c_game_object *)MS->objects->Fetch_item_by_number(sel_id);
285 			int32 dead = pGameObject->GetIntegerVariable(pGameObject->GetVariable("state"));
286 
287 			if (target->object_type == __NON_ORGANIC_MEGA) { // robot (look down)
288 				oy += ROBOT_EYE;
289 			} else if (dead) { // dead
290 				oy += MEGA_DEAD_EYE;
291 			} else if (target->mega->Is_crouched()) { // crouch
292 				oy += MEGA_CROUCH_EYE;
293 			} else {
294 				oy += MEGA_STOOD_EYE; // standing
295 			}
296 
297 			// difference in x,y,z
298 			dx = (px - ox);
299 			dy = (py - oy);
300 			dz = (pz - oz);
301 
302 		}
303 		// is an interact object, check for height.
304 		else {
305 			int32 height = STANDARD_MARKER_HEIGHT; // standard prop height
306 
307 			// if look_height set for this marker set
308 			if (target->look_height != -1)
309 				height = target->look_height;
310 
311 			dx = (px - ox);
312 			// difference between player eye and marker height
313 			dy = (PXreal)(playerEye - height);
314 			dz = (pz - oz);
315 		}
316 
317 		// get player pan (for calculating how far round the head moves)
318 
319 		if (MS->player.log->auto_panning == FALSE8)
320 			player_pan = MS->player.log->pan;
321 		else
322 			player_pan = MS->player.log->auto_display_pan;
323 
324 // Now find angles for neck bone...
325 
326 		b->boneTarget.vz = (int16)(4096.0 * (PXAngleOfVector(-dz, -dx) - player_pan));
327 		b->boneTarget.vx = (int16)(4096.0 * PXAngleOfVector((PXfloat)PXsqrt(dx * dx + dz * dz), dy));
328 
329 		// make sure vz is in range -2048 - 2048... this might not be because of subtracting off player_pan
330 
331 		while (b->boneTarget.vz > 2048)
332 			b->boneTarget.vz -= 4096;
333 
334 		while (b->boneTarget.vz < -2048)
335 			b->boneTarget.vz += 4096;
336 
337 		if (b->boneTarget.vz > 1024)
338 			b->boneTarget.vz = 1024;
339 		if (b->boneTarget.vz < -1024)
340 			b->boneTarget.vz = -1024;
341 
342 		// armed unarmed
343 
344 		int32 armed = MS->player.log->mega->Fetch_armed_status();
345 
346 		// from NONE to a status
347 
348 		// if status is none and we are armed and we have returned to central then start looking with upper body...
349 		if ((status == STATUS_NONE) && (armed) && (b->boneValue.vz == 0) && (b->boneValue.vx == 0))
350 			status = STATUS_BODY;
351 
352 		// if status is none and we are not armed and we have returned to central then start looking with head...
353 		if ((status == STATUS_NONE) && (!armed) && (b->boneValue.vz == 0) && (b->boneValue.vx == 0))
354 			status = STATUS_HEAD;
355 
356 		// from wrong status to NONE
357 
358 		// if aiming with body but not armed then status is none (in prep of going to status_body)
359 		if ((status == STATUS_BODY) && (!armed))
360 			status = STATUS_NONE;
361 
362 		// if looking with head but armed then status is none (in preperation for going to STATUS_HEAD)
363 		if ((status == STATUS_HEAD) && (armed))
364 			status = STATUS_NONE;
365 
366 		// now what to do in each status...
367 
368 		// no status, target nothing...
369 		if (status == STATUS_NONE) {
370 			b->boneTarget.vx = 0;
371 			b->boneTarget.vy = 0;
372 			b->boneTarget.vz = 0;
373 			b->boneSpeed = STANDARD_LOOK_SPEED * 2;
374 		}
375 		// look with head...
376 		else if (status == STATUS_HEAD) {
377 			// limit pan and pitch
378 			LimitShort(b->boneTarget.vz, -512, 384);
379 			LimitShort(b->boneTarget.vx, -256, 256);
380 
381 			// for turning it looks better if you look up slightly (vx=vx-abs(vz)/2)
382 			b->boneTarget.vx = (int16)(b->boneTarget.vx - (abs(b->boneTarget.vz) / 3));
383 
384 			// we need to set the speed to be STANDARD_LOOK_SPEED
385 			b->boneSpeed = STANDARD_LOOK_SPEED;
386 
387 			// should not be hard coded, neck bone
388 			b->boneNumber = 23;
389 		}
390 		// look with body...
391 		else if (status == STATUS_BODY) {
392 			// no bend when aimed
393 			b->boneTarget.vy = 0;
394 
395 			// limit pitch (pan can be any value so gun is always pointing at target...)
396 			LimitShort(b->boneTarget.vx, -256, 256);
397 
398 			b->boneTarget.vy = (int16)((b->boneTarget.vx * b->boneTarget.vz) / 1024);
399 
400 			// we need to set the speed to be STANDARD_LOOK_SPEED
401 			b->boneSpeed = STANDARD_LOOK_SPEED * 2;
402 
403 			// should not be hard coded, body bone
404 			b->boneNumber = 1;
405 		}
406 
407 	} else {
408 		// if still in body mode and we have gone back to straight then set mode to none and bone goes to 23 (neck) for specific
409 		// looks...
410 		if ((b->boneTarget.vx == 0) && (b->boneValue.vz == 0) && (status == STATUS_BODY)) {
411 			status = STATUS_NONE;
412 			b->boneNumber = 23;
413 			b->boneSpeed = STANDARD_LOOK_SPEED;
414 		}
415 	}
416 
417 	// dont do an update here...
418 }
419 
fn_set_neck_bone(int32 & result,int32 * params)420 mcodeFunctionReturnCodes fn_set_neck_bone(int32 &result, int32 *params) { return (MS->fn_set_neck_bone(result, params)); }
421 
fn_set_neck_vector(int32 & result,int32 * params)422 mcodeFunctionReturnCodes fn_set_neck_vector(int32 &result, int32 *params) { return (MS->fn_set_neck_vector(result, params)); }
423 
speak_set_neck_vector(int32 & result,int32 * params)424 mcodeFunctionReturnCodes speak_set_neck_vector(int32 &result, int32 *params) { return (MS->speak_set_neck_vector(result, params)); }
425 
fn_simple_look(int32 & result,int32 * params)426 mcodeFunctionReturnCodes fn_simple_look(int32 &result, int32 *params) { return (MS->fn_simple_look(result, params)); }
427 
speak_simple_look(int32 & result,int32 * params)428 mcodeFunctionReturnCodes speak_simple_look(int32 &result, int32 *params) { return (MS->speak_simple_look(result, params)); }
429 
430 // the array of standard look coords
431 
432 #define LOOK_RIGHT (int16)(-384)
433 #define LOOK_UP (int16)(-196)
434 
435 #define LOOK_LEFT (int16)(-LOOK_RIGHT)
436 #define LOOK_DOWN (int16)(-LOOK_UP)
437 
438 const short looks[9][3] = {
439 	{0, 0, 0},                  // ahead
440 	{0, 0, LOOK_UP},            // up
441 	{LOOK_RIGHT, 0, LOOK_UP},   // up/right
442 	{LOOK_RIGHT, 0, 0},         // right
443 	{LOOK_RIGHT, 0, LOOK_DOWN}, // down/right
444 	{0, 0, LOOK_DOWN},          // down
445 	{LOOK_LEFT, 0, LOOK_DOWN},  // down/left
446 	{LOOK_LEFT, 0, 0},          // left
447 	{LOOK_LEFT, 0, LOOK_UP}     // up/left
448 };
449 
450 // look
451 // 0    - Ahead
452 // 1    - Up
453 // 2    - Up/Right
454 // 3    - Right
455 // 4    - Right/Down
456 // 5    - Down
457 // 6    - Down/Left
458 // 7    - Left
459 // 8    - Left/Up
460 
461 // simple look
fn_simple_look(int32 &,int32 * params)462 mcodeFunctionReturnCodes _game_session::fn_simple_look(int32 &, int32 *params) {
463 	int32 l = params[0]; // which direction
464 
465 	if (!logic_structs[cur_id]->mega)
466 		Fatal_error("fn_set_neck_vector called by non mega %s", L->GetName());
467 
468 	int32 callingParams[2];
469 
470 	callingParams[0] = MemoryUtil::encodePtr((uint8 *)const_cast<char *>(L->GetName()));
471 	callingParams[1] = l;
472 
473 	int32 ret;
474 	return speak_simple_look(ret, callingParams);
475 }
476 
477 // simple look from speech scripts...
478 
speak_simple_look(int32 &,int32 * params)479 mcodeFunctionReturnCodes _game_session::speak_simple_look(int32 &, int32 *params) {
480 	const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
481 
482 	// object
483 	int32 object_id = objects->Fetch_item_number_by_name(object_name);
484 
485 	// direction
486 	int32 l = params[1];
487 
488 	if (!logic_structs[object_id]->mega)
489 		Fatal_error("fn_set_neck_vector called by non mega %s", logic_structs[object_id]->GetName());
490 
491 	if (logic_structs[object_id]->voxel_info->lookBone.boneNumber == (int8)-1)
492 		Fatal_error("fn_set_neck_vector called but no fn_set_neck_bone() has been called for object %s", logic_structs[object_id]->GetName());
493 
494 	Tdebug("bones.txt", "%s: Simple look %d <%d,%d,%d> at speed %d", object_name, l, looks[l][2], looks[l][1], looks[l][0], STANDARD_LOOK_SPEED);
495 
496 	// sorry looks is wrong way round! (z,y,x)
497 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vx = looks[l][2];
498 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vy = looks[l][1];
499 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vz = looks[l][0];
500 	logic_structs[object_id]->voxel_info->lookBone.boneSpeed = STANDARD_LOOK_SPEED;
501 
502 	warning("doing a look direction: %d bone: %d", l, logic_structs[object_id]->voxel_info->lookBone.boneNumber);
503 
504 	return IR_CONT;
505 }
506 
507 // set neck bone of current object
fn_set_neck_bone(int32 &,int32 * params)508 mcodeFunctionReturnCodes _game_session::fn_set_neck_bone(int32 &, int32 *params) {
509 	int32 bone = params[0];
510 
511 	if (!logic_structs[cur_id]->mega)
512 		Fatal_error("fn_set_neck_bone called by non mega %s", L->GetName());
513 
514 	Tdebug("bones.txt", "%s: Neck bone is %d", L->GetName(), bone);
515 
516 	logic_structs[cur_id]->voxel_info->lookBone.boneNumber = (int8)bone;
517 	logic_structs[cur_id]->voxel_info->neckBone.boneNumber = (int8)bone;
518 
519 	return IR_CONT;
520 }
521 
522 // set neck vector
523 // params
524 // 0    - x
525 // 1    - y
526 // 2    - z
527 // 3    - speed
528 // equiverlant to speech_set_neck_vector(NAME,x,y,z,speed)
529 // where NAME is object name...
530 //
fn_set_neck_vector(int32 &,int32 * params)531 mcodeFunctionReturnCodes _game_session::fn_set_neck_vector(int32 &, int32 *params) {
532 	int32 x, y, z, speed;
533 
534 	x = params[0];
535 	y = params[1];
536 	z = params[2];
537 	speed = params[3];
538 
539 	if (!logic_structs[cur_id]->mega)
540 		Fatal_error("fn_set_neck_vector called by non mega %s", L->GetName());
541 
542 	int32 callingParams[5];
543 
544 	callingParams[0] = MemoryUtil::encodePtr((uint8 *)const_cast<char *>(L->GetName()));
545 	callingParams[1] = x;
546 	callingParams[2] = y;
547 	callingParams[3] = z;
548 	callingParams[4] = speed;
549 
550 	int32 ret;
551 	return speak_set_neck_vector(ret, callingParams);
552 }
553 
554 // set neck vector
555 // params
556 // 0    - "object"
557 // 1    - x
558 // 2    - y
559 // 3    - z
560 // 4    - speed
561 //
speak_set_neck_vector(int32 &,int32 * params)562 mcodeFunctionReturnCodes _game_session::speak_set_neck_vector(int32 &, int32 *params) {
563 	int32 object_id;
564 	int32 x, y, z, speed;
565 
566 	const char *object_name = (const char *)MemoryUtil::resolvePtr(params[0]);
567 
568 	object_id = objects->Fetch_item_number_by_name(object_name);
569 	x = params[1];
570 	y = params[2];
571 	z = params[3];
572 	speed = params[4];
573 
574 	if (L == player.log) {
575 		warning("player set neck vector...");
576 		logic_structs[object_id]->voxel_info->lookBone.boneNumber = 23;
577 	}
578 
579 	if (!logic_structs[object_id]->mega)
580 		Fatal_error("fn_set_neck_vector called by non mega %s", logic_structs[object_id]->GetName());
581 
582 	if (logic_structs[object_id]->voxel_info->lookBone.boneNumber == (int8)-1)
583 		Fatal_error("fn_set_neck_vector called but no fn_set_neck_bone() has been called for object %s", logic_structs[object_id]->GetName());
584 
585 	Tdebug("bones.txt", "%s: Setting bone <%d,%d,%d> at speed %d", object_name, x, y, z, speed);
586 
587 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vx = (int16)x;
588 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vy = (int16)y;
589 	logic_structs[object_id]->voxel_info->scriptedLookBoneTarget.vz = (int16)z;
590 	logic_structs[object_id]->voxel_info->lookBone.boneSpeed = (int16)speed;
591 
592 	return IR_CONT;
593 }
594 
595 } // End of namespace ICB
596