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