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/p4.h"
29 #include "engines/icb/common/px_common.h"
30 #include "engines/icb/common/px_linkeddatafile.h"
31 #include "engines/icb/common/ptr_util.h"
32 #include "engines/icb/mission.h"
33 #include "engines/icb/session.h"
34 #include "engines/icb/object_structs.h"
35 #include "engines/icb/debug.h"
36 #include "engines/icb/player.h"
37 #include "engines/icb/direct_input.h"
38 #include "engines/icb/barriers.h"
39 #include "engines/icb/common/px_route_barriers.h"
40 #include "engines/icb/global_objects.h"
41 #include "engines/icb/animation_mega_set.h"
42 #include "engines/icb/mission.h"
43 #include "engines/icb/common/px_scriptengine.h"
44 #include "engines/icb/text.h"
45 #include "engines/icb/session.h"
46 #include "engines/icb/global_switches.h"
47 
48 namespace ICB {
49 
fn_start_player_interaction(int32 & result,int32 * params)50 mcodeFunctionReturnCodes fn_start_player_interaction(int32 &result, int32 *params) { return (MS->fn_start_player_interaction(result, params)); }
51 
52 #define INTERACT_DISTANCE (250 * REAL_ONE)
53 #define MIN_INTERACT_DISTANCE (5 * REAL_ONE)
54 
55 // distance to point head at - 15m
56 #define LOOK_AT_DISTANCE (500 * REAL_ONE)
57 
58 //( ( FULL_TURN*n)/360 )
59 #define NEAR_PROP_DISTANCE (70 * REAL_ONE)
60 
61 // was 130 original EU psx release
62 #define DEAD_MEGA_DISTANCE (230 * REAL_ONE)
63 
64 //+/- 90deg
65 #define NEAR_PROP_ANGLE (FULL_TURN / 4)
66 
Find_current_player_interact_object()67 void _player::Find_current_player_interact_object() {
68 	// find the nico that represents our current interactable object
69 
70 	// search nicos on our level
71 	// find within distance
72 	// find within angle
73 	// the name of the nico will be the name of the object
74 
75 	uint32 j;
76 	PXreal sub1, sub2, len;
77 	PXreal nearest = REAL_LARGE * REAL_LARGE; // high number to be bettered
78 	PXreal nearest_mega = REAL_LARGE * REAL_LARGE; // high number to be bettered
79 	PXfloat new_pan, diff;
80 
81 	uint32 prop_id = 0;
82 	uint32 mega_id = 0;
83 	uint32 look_at_prop_id = 0;
84 	uint32 pl_id = Fetch_player_id();
85 	bool8 armed_status = log->mega->Fetch_armed_status();
86 	uint8 crouch_status = log->mega->Is_crouched();
87 
88 	interact_selected = FALSE8;
89 	look_at_selected = FALSE8;
90 	dead_mega = FALSE8;
91 	bool8 evil_chosen = FALSE8;
92 
93 	// ** check first for megas as indicated by line-of-sight-manager - these get priority over prop nicos **
94 
95 	// run through all the objects calling their logic
96 	for (j = 0; j < MS->total_objects; j++) { // object 0 is used
97 		//		object must be alive and interactable
98 		if ((MS->logic_structs[j]->ob_status != OB_STATUS_HELD) && (MS->logic_structs[j]->player_can_interact)) { // not if the object has been manually switched out
99 
100 			if ((MS->logic_structs[j]->image_type == PROP) && (!armed_status) &&
101 			    (crouch_status == (MS->logic_structs[j]->three_sixty_interact & PROP_CROUCH_INTERACT))) {
102 				if ((MS->logic_structs[j]->prop_xyz.y >= log->mega->actor_xyz.y) && (MS->logic_structs[j]->owner_floor_rect == log->owner_floor_rect))
103 					if ((MS->logic_structs[j]->prop_xyz.y - log->mega->actor_xyz.y) < (190 * REAL_ONE)) {
104 						sub1 = (PXreal)MS->logic_structs[j]->prop_xyz.x - log->mega->actor_xyz.x;
105 						sub2 = (PXreal)MS->logic_structs[j]->prop_xyz.z - log->mega->actor_xyz.z;
106 
107 						//              dist
108 						len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
109 
110 						//              less than n centimeters away
111 						if ((len > MIN_INTERACT_DISTANCE * MIN_INTERACT_DISTANCE) && (len < LOOK_AT_DISTANCE * LOOK_AT_DISTANCE) && (len < nearest)) {
112 							//                      check for prop being a 360deg type
113 							if (MS->logic_structs[j]->three_sixty_interact & THREE_SIXTY_INTERACT) {
114 
115 								new_pan = PXAngleOfVector(sub2, sub1); // work out vector
116 								//                          get difference between the two
117 								diff = new_pan - log->pan;
118 								//                      correct
119 								if (diff > HALF_TURN)
120 									diff -= FULL_TURN;
121 								else if (diff < -HALF_TURN)
122 									diff += FULL_TURN;
123 
124 								if (PXfabs(diff) < (FULL_TURN / 10)) { // 0.1f
125 									MS->prop_interact_dist = len; // can be used later in core_prop_interact
126 									nearest = len;
127 									prop_id = j + 1;
128 								}
129 							} else { // are we a similar pan to target object
130 								//                      angle
131 								new_pan = MS->logic_structs[j]->prop_interact_pan; // get targets pan
132 
133 								//                      get difference between the two
134 								diff = log->pan - new_pan;
135 
136 								//                      correct
137 								if (diff > HALF_TURN)
138 									diff -= FULL_TURN;
139 								else if (diff < -HALF_TURN)
140 									diff += FULL_TURN;
141 
142 								if (((len < NEAR_PROP_DISTANCE * NEAR_PROP_DISTANCE) && (PXfabs(diff) < NEAR_PROP_ANGLE)) /* OR */ ||
143 								    (PXfabs(diff) < (FULL_TURN / 8))) { //*
144 									//                          ok, we are facing the right direction - i.e. nearly same as interact pan
145 									//                          but
146 									//                          are we behind or infront - need another check
147 
148 									PXreal dx = (PXreal)PXsin((log->pan + (FULL_TURN / 4)) * TWO_PI);
149 									PXreal dz = (PXreal)PXcos((log->pan + (FULL_TURN / 4)) * TWO_PI);
150 
151 									PXreal mx = MS->logic_structs[j]->prop_xyz.x;
152 									PXreal mz = MS->logic_structs[j]->prop_xyz.z;
153 
154 									if ((dz * (mx - log->mega->actor_xyz.x)) <= (dx * (mz - log->mega->actor_xyz.z))) {
155 										MS->prop_interact_dist = len; // can be used later in core_prop_interact
156 										nearest = len;
157 										prop_id = j + 1;
158 									}
159 								}
160 							}
161 						}
162 					}
163 			} else if ((MS->logic_structs[j]->image_type == VOXEL) && (MS->logic_structs[j]->mega->actor_xyz.y == log->mega->actor_xyz.y)) { // mega character
164 				// we have targeted an evil but this one is not evil then skip it - regardless of proximity
165 				if ((evil_chosen) && (!MS->logic_structs[j]->mega->is_evil))
166 					continue;
167 
168 				// if there is a chi then we can target her when armed
169 				if ((MS->is_there_a_chi) && (j == MS->chi_id) && (armed_status))
170 					continue;
171 
172 				if ((g_oLineOfSight->LineOfSight(pl_id, j)) && (MS->Object_visible_to_camera(j))) { // must be on screen
173 					sub1 = (PXreal)MS->logic_structs[j]->mega->actor_xyz.x - log->mega->actor_xyz.x;
174 					sub2 = (PXreal)MS->logic_structs[j]->mega->actor_xyz.z - log->mega->actor_xyz.z;
175 
176 					// dist
177 					len = (PXreal)((sub1 * sub1) + (sub2 * sub2));
178 
179 					// nearer or the current is dead or armed and the current is non-evil
180 					if (((armed_status) && (!evil_chosen) && (MS->logic_structs[j]->mega->is_evil)) || (dead_mega) || (len < nearest_mega)) {
181 						// we are nearer or the current is dead
182 						// see if object is dead
183 						if ((MS->logic_structs[j]->mega->dead) &&
184 						    (crouch_status)) { // this mega is dead and we're crouched - only register him if there isnt another
185 							if ((!mega_id) && (len < DEAD_MEGA_DISTANCE * DEAD_MEGA_DISTANCE)) { // dead mega chosen - must be within prop type range
186 								nearest_mega = len;
187 								mega_id = j + 1;
188 								dead_mega = TRUE8; // chosen a dead mega
189 							}
190 						} else if (!MS->logic_structs[j]->mega->dead) { // must belive if we're stood
191 							evil_chosen = MS->logic_structs[j]->mega->is_evil;
192 							nearest_mega = len;
193 							mega_id = j + 1;
194 							dead_mega = FALSE8; // chosen a live mega
195 						}
196 					}
197 				}
198 			}
199 		}
200 	}
201 
202 	// if crouching and targeting a mega the mega must be dead
203 	// crouch props are filtered out above
204 	if ((crouch_status) && (mega_id)) {
205 		// if dead AND not armed   or    alive AND armed
206 		if (((dead_mega) && (!armed_status)) || ((armed_status) && (!dead_mega))) {
207 			cur_interact_id = (mega_id - 1);
208 			interact_selected = TRUE8;
209 		}
210 		return;
211 	}
212 
213 	// mega if armed, nearest prop or live mega, dead mega
214 	if ((prop_id) && (nearest < nearest_mega)) { // UNARMED prop nearer than mega (wont be a prop if armed)
215 		cur_interact_id = (prop_id - 1);
216 		interact_selected = TRUE8;
217 	} else if ((mega_id) && (!dead_mega)) { // live mega
218 		cur_interact_id = (mega_id - 1);
219 		interact_selected = TRUE8;
220 	} else if (prop_id) { // prop
221 		cur_interact_id = (prop_id - 1);
222 		interact_selected = TRUE8;
223 	}
224 
225 	// look at
226 	if ((!interact_selected) && (look_at_prop_id)) {
227 		look_at_id = look_at_prop_id - 1;
228 		look_at_selected = TRUE8;
229 	}
230 }
231 
232 #if CD_MODE == 0
233 
Render_crude_interact_highlight()234 void _player::Render_crude_interact_highlight() {
235 	uint32 pitch; // backbuffer pitch
236 	uint8 *ad;
237 
238 	_rgb pen = {255, 0, 0, 0};
239 
240 	// anything highlighted?
241 	if (interact_selected == FALSE8)
242 		return;
243 
244 	// cross hair is now a development option
245 	if (g_px->cross_hair == FALSE8)
246 		return;
247 
248 	ad = surface_manager->Lock_surface(working_buffer_id);
249 	pitch = surface_manager->Get_pitch(working_buffer_id);
250 
251 	// setup camera
252 	PXcamera &camera = MS->GetCamera();
253 
254 	// set up nico world coords
255 	PXvector pos;
256 
257 	if (MS->logic_structs[cur_interact_id]->image_type == PROP) {
258 		pos.x = MS->logic_structs[cur_interact_id]->prop_xyz.x;
259 		pos.y = MS->logic_structs[cur_interact_id]->prop_xyz.y;
260 		pos.z = MS->logic_structs[cur_interact_id]->prop_xyz.z;
261 	} else {
262 		pos.x = MS->logic_structs[cur_interact_id]->mega->actor_xyz.x;
263 		pos.y = MS->logic_structs[cur_interact_id]->mega->actor_xyz.y;
264 		pos.z = MS->logic_structs[cur_interact_id]->mega->actor_xyz.z;
265 	}
266 
267 	// screen pos
268 	PXvector filmpos;
269 
270 	// yesno
271 	bool8 result = FALSE8;
272 
273 	// compute screen coord
274 	PXWorldToFilm(pos, camera, result, filmpos);
275 
276 	// print name if on screen
277 	if (result) {
278 		Clip_text_print(&pen, (int32)(filmpos.x + (SCREEN_WIDTH / 2)), (int32)((SCREEN_DEPTH / 2) - filmpos.y), ad, pitch, "+");
279 	}
280 
281 	surface_manager->Unlock_surface(working_buffer_id);
282 }
283 
284 #else
285 
Render_crude_interact_highlight()286 void _player::Render_crude_interact_highlight() {}
287 
288 #endif // #if CD_MODE == 0
289 
Player_interact()290 __mode_return _player::Player_interact() {
291 	// check if the player has pressed the interact button
292 	// if so see if there's a current interact object and if so setup the interaction
293 
294 	// return
295 	//				__FINISHED_THIS_CYCLE, or
296 	//				__MORE_THIS_CYCLE
297 
298 	c_game_object *iobject;
299 	uint32 j;
300 
301 	// first check for auto-interact objects
302 
303 	if ((interact_selected) && ((log->cur_anim_type == __WALK) || ((log->cur_anim_type == __RUN))))
304 		for (j = 0; j < MAX_auto_interact; j++)
305 			if (MS->auto_interact_list[j] == (cur_interact_id + 1)) {
306 				//      try to fetch the object
307 				iobject = (c_game_object *)MS->objects->Fetch_item_by_number(cur_interact_id);
308 
309 				Zdebug("  INTERACT with %s", iobject->GetName());
310 
311 				//      get the address of the script we want to run
312 				const char *pc = (const char *)MS->scripts->Try_fetch_item_by_hash(iobject->GetScriptNameFullHash(OB_ACTION_CONTEXT)); //
313 
314 				if (pc == NULL)
315 					Fatal_error("Object [%s] has no interact script", iobject->GetName());
316 
317 				//      now run the action context script which may or may not set a new script on level 1
318 				RunScript(pc, iobject);
319 
320 				//      stop for a cycle regardless
321 				return (__FINISHED_THIS_CYCLE);
322 			}
323 
324 	// check for interact button AND there being an object to interact with
325 	if ((cur_state.IsButtonSet(__INTERACT)) && (interact_selected) && (!interact_lock) && (!stood_on_lift)) {
326 		// try to fetch the object
327 		iobject = (c_game_object *)MS->objects->Fetch_item_by_number(cur_interact_id);
328 
329 		// get the address of the script we want to run
330 		const char *pc = (const char *)MS->scripts->Try_fetch_item_by_hash(iobject->GetScriptNameFullHash(OB_ACTION_CONTEXT)); //
331 
332 		if (pc == NULL)
333 			Fatal_error("Object [%s] has no interact script", iobject->GetName());
334 
335 		interact_lock = TRUE8; // switch the lock on
336 
337 		// reset player to either stood or stood armed
338 		if (MS->logic_structs[Fetch_player_id()]->mega->Is_crouched())
339 			Set_player_status(CROUCHING);
340 		else if (MS->logic_structs[Fetch_player_id()]->mega->Fetch_armed_status())
341 			Set_player_status(NEW_AIM);
342 		else
343 			Set_player_status(STOOD);
344 
345 		Push_player_stat();
346 
347 		// now run the action context script which may or may not set a new script on level 1
348 		RunScript(pc, iobject);
349 
350 		// stop for a cycle regardless
351 		return (__FINISHED_THIS_CYCLE);
352 	} else if (!cur_state.IsButtonSet(__INTERACT)) // release the interact lock
353 		interact_lock = FALSE8; // let go
354 
355 	return (__MORE_THIS_CYCLE);
356 }
357 
fn_start_player_interaction(int32 &,int32 * params)358 mcodeFunctionReturnCodes _game_session::fn_start_player_interaction(int32 &, int32 *params) {
359 	// do check to see if script running
360 
361 	// if not set it up on level 2 and change script level
362 
363 	// then call the new script as if from logic because, remember, this is being called from a script that is being called
364 	// from a function ; fn_player
365 
366 	// S player::logic
367 	//	fn_player()
368 	//		S interact_context script
369 	//			fn_start_player_interact
370 	//				S new script
371 
372 	//	** if we start a new script we should write a flag for _player::Player_interact **
373 
374 	char *ad;
375 
376 	// set target id
377 	M->target_id = player.Fetch_player_interact_id();
378 
379 	// set this flag to avoid interact with id=0 based problems
380 	M->interacting = TRUE8;
381 
382 	// fetch action script
383 	ad = (char *)scripts->Try_fetch_item_by_hash(params[0] /*(uint32)params*/);
384 
385 	//	write actual offset
386 	L->logic[1] = ad;
387 
388 	//	write reference for change script checks later - i.e. FN_context_chosen_script
389 	L->logic_ref[1] = ad;
390 
391 	L->logic_level = 1; // reset to level 2
392 	// action script will fall back to looping level 1
393 
394 	L->looping = 0; // reset to 0 for new logics
395 
396 	// script interpretter shouldnt write a pc back
397 	return (IR_TERMINATE);
398 }
399 
Engine_start_interaction(const char * script,uint32 id)400 bool8 _game_session::Engine_start_interaction(const char *script, uint32 id) {
401 	// set the current mega object interacting named 'script' in target object 'id'
402 
403 	c_game_object *iobject;
404 	uint32 script_hash;
405 
406 	script_hash = HashString(script);
407 
408 	// get target object
409 	iobject = (c_game_object *)MS->objects->Fetch_item_by_number(id);
410 	if (!iobject)
411 		Fatal_error("Engine_start_interaction - named object dont exist"); // should never happen
412 
413 	// now try and find a script with the passed extention i.e. ???::looping
414 	for (uint32 k = 0; k < iobject->GetNoScripts(); k++) {
415 
416 		if (script_hash == iobject->GetScriptNamePartHash(k)) {
417 			//			script k is the one to run
418 			//			get the address of the script we want to run
419 			char *pc = (char *)scripts->Try_fetch_item_by_hash(iobject->GetScriptNameFullHash(k));
420 
421 			// set target id
422 			M->target_id = id;
423 
424 			// set this flag to avoid interact with id=0 based problems
425 			M->interacting = TRUE8;
426 
427 			//  write actual offset
428 			L->logic[1] = pc;
429 
430 			//  write reference for change script checks later - i.e. FN_context_chosen_script
431 			L->logic_ref[1] = pc;
432 
433 			L->logic_level = 1; // reset to level 2
434 			// action script will fall back to looping level 1
435 
436 			L->looping = 0; // reset to 0 for new logics
437 
438 			return (TRUE8);
439 		}
440 	}
441 
442 	// didnt find the named script
443 
444 	return (FALSE8);
445 }
446 
447 } // End of namespace ICB
448