1 //********************************************************************************************
2 //*
3 //* This file is part of Egoboo.
4 //*
5 //* Egoboo is free software: you can redistribute it and/or modify it
6 //* under the terms of the GNU General Public License as published by
7 //* the Free Software Foundation, either version 3 of the License, or
8 //* (at your option) any later version.
9 //*
10 //* Egoboo is distributed in the hope that it will be useful, but
11 //* WITHOUT ANY WARRANTY; without even the implied warranty of
12 //* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 //* General Public License for more details.
14 //*
15 //* You should have received a copy of the GNU General Public License
16 //* along with Egoboo. If not, see <http://www.gnu.org/licenses/>.
17 //*
18 //********************************************************************************************
19
20 /// @file passage.c
21 /// @brief Passages and doors and whatnot. Things that impede your progress!
22
23 #include "passage.h"
24
25 #include "char.inl"
26 #include "script.h"
27 #include "sound.h"
28 #include "mesh.inl"
29 #include "game.h"
30 #include "quest.h"
31 #include "network.h"
32
33 #include "egoboo_fileutil.h"
34 #include "egoboo_math.h"
35 #include "egoboo.h"
36
37 //--------------------------------------------------------------------------------------------
38 //--------------------------------------------------------------------------------------------
39 INSTANTIATE_STACK( ACCESS_TYPE_NONE, passage_t, PassageStack, MAX_PASS );
40 INSTANTIATE_STACK( ACCESS_TYPE_NONE, shop_t, ShopStack, MAX_SHOP );
41
42 //--------------------------------------------------------------------------------------------
43 //--------------------------------------------------------------------------------------------
PassageStack_free_all()44 void PassageStack_free_all()
45 {
46 PassageStack.count = 0;
47 }
48
49 //--------------------------------------------------------------------------------------------
PasageStack_get_free()50 int PasageStack_get_free()
51 {
52 int ipass = ( PASS_REF ) MAX_PASS;
53
54 if ( PassageStack.count < MAX_PASS )
55 {
56 ipass = PassageStack.count;
57 PassageStack.count++;
58 };
59
60 return ipass;
61 }
62
63 //--------------------------------------------------------------------------------------------
64 //--------------------------------------------------------------------------------------------
ShopStack_free_all()65 void ShopStack_free_all()
66 {
67 SHOP_REF cnt;
68
69 for ( cnt = 0; cnt < MAX_PASS; cnt++ )
70 {
71 ShopStack.lst[cnt].owner = SHOP_NOOWNER;
72 ShopStack.lst[cnt].passage = 0;
73 }
74 ShopStack.count = 0;
75 }
76
77 //--------------------------------------------------------------------------------------------
ShopStack_get_free()78 int ShopStack_get_free()
79 {
80 int ishop = ( PASS_REF ) MAX_PASS;
81
82 if ( ShopStack.count < MAX_PASS )
83 {
84 ishop = ShopStack.count;
85 ShopStack.count++;
86 };
87
88 return ishop;
89 }
90
91 //--------------------------------------------------------------------------------------------
92 //--------------------------------------------------------------------------------------------
open_passage(const PASS_REF passage)93 bool_t open_passage( const PASS_REF passage )
94 {
95 /// @details ZZ@> This function makes a passage passable
96
97 int x, y;
98 Uint32 fan;
99 bool_t useful = bfalse;
100 passage_t * ppass;
101
102 if ( INVALID_PASSAGE( passage ) ) return useful;
103 ppass = PassageStack.lst + passage;
104
105 useful = !ppass->open;
106 ppass->open = btrue;
107
108 if ( ppass->area.top <= ppass->area.bottom )
109 {
110 useful = ( !ppass->open );
111 ppass->open = btrue;
112
113 for ( y = ppass->area.top; y <= ppass->area.bottom; y++ )
114 {
115 for ( x = ppass->area.left; x <= ppass->area.right; x++ )
116 {
117 fan = mesh_get_tile_int( PMesh, x, y );
118 mesh_clear_fx( PMesh, fan, MPDFX_WALL | MPDFX_IMPASS );
119 }
120 }
121 }
122
123 return useful;
124 }
125
126 //--------------------------------------------------------------------------------------------
flash_passage(const PASS_REF passage,Uint8 color)127 void flash_passage( const PASS_REF passage, Uint8 color )
128 {
129 /// @details ZZ@> This function makes a passage flash white
130
131 int x, y, cnt;
132 Uint32 fan;
133 passage_t * ppass;
134
135 if ( INVALID_PASSAGE( passage ) ) return;
136 ppass = PassageStack.lst + passage;
137
138 for ( y = ppass->area.top; y <= ppass->area.bottom; y++ )
139 {
140 for ( x = ppass->area.left; x <= ppass->area.right; x++ )
141 {
142 fan = mesh_get_tile_int( PMesh, x, y );
143
144 if ( !mesh_grid_is_valid( PMesh, fan ) ) continue;
145
146 for ( cnt = 0; cnt < 4; cnt++ )
147 {
148 PMesh->tmem.tile_list[fan].lcache[cnt] = color;
149 }
150 }
151 }
152 }
153
154 //--------------------------------------------------------------------------------------------
point_is_in_passage(const PASS_REF passage,float xpos,float ypos)155 bool_t point_is_in_passage( const PASS_REF passage, float xpos, float ypos )
156 {
157 /// @details ZF@> This return btrue if the specified X and Y coordinates are within the passage
158
159 passage_t * ppass;
160 frect_t tmp_rect;
161
162 if ( INVALID_PASSAGE( passage ) ) return bfalse;
163 ppass = PassageStack.lst + passage;
164
165 // Passage area
166 tmp_rect.left = ppass->area.left * GRID_FSIZE;
167 tmp_rect.top = ppass->area.top * GRID_FSIZE;
168 tmp_rect.right = ( ppass->area.right + 1 ) * GRID_FSIZE;
169 tmp_rect.bottom = ( ppass->area.bottom + 1 ) * GRID_FSIZE;
170
171 return frect_point_inside( &tmp_rect, xpos, ypos );
172 }
173
174 //--------------------------------------------------------------------------------------------
object_is_in_passage(const PASS_REF passage,float xpos,float ypos,float radius)175 bool_t object_is_in_passage( const PASS_REF passage, float xpos, float ypos, float radius )
176 {
177 /// @details ZF@> This return btrue if the specified X and Y coordinates are within the passage
178 /// radius is how much offset we allow outside the passage
179
180 passage_t * ppass;
181 frect_t tmp_rect;
182
183 if ( INVALID_PASSAGE( passage ) ) return bfalse;
184 ppass = PassageStack.lst + passage;
185
186 // Passage area
187 radius += CLOSETOLERANCE;
188 tmp_rect.left = ( ppass->area.left * GRID_FSIZE ) - radius;
189 tmp_rect.top = ( ppass->area.top * GRID_FSIZE ) - radius;
190 tmp_rect.right = (( ppass->area.right + 1 ) * GRID_FSIZE ) + radius;
191 tmp_rect.bottom = (( ppass->area.bottom + 1 ) * GRID_FSIZE ) + radius;
192
193 return frect_point_inside( &tmp_rect, xpos, ypos );
194 }
195
196 //--------------------------------------------------------------------------------------------
who_is_blocking_passage(const PASS_REF passage,const CHR_REF isrc,IDSZ idsz,BIT_FIELD targeting_bits,IDSZ require_item)197 CHR_REF who_is_blocking_passage( const PASS_REF passage, const CHR_REF isrc, IDSZ idsz, BIT_FIELD targeting_bits, IDSZ require_item )
198 {
199 /// @details ZZ@> This function returns MAX_CHR if there is no character in the passage,
200 /// otherwise the index of the first character found is returned...
201 /// Can also look for characters with a specific quest or item in his or her inventory
202 /// Finds living ones, then items and corpses
203
204 CHR_REF character, foundother;
205 passage_t * ppass;
206 chr_t *psrc;
207
208 // Skip if the one who is looking doesn't exist
209 if ( !INGAME_CHR( isrc ) ) return ( CHR_REF )MAX_CHR;
210 psrc = ChrList.lst + isrc;
211
212 // Skip invalid passages
213 if ( INVALID_PASSAGE( passage ) ) return ( CHR_REF )MAX_CHR;
214 ppass = PassageStack.lst + passage;
215
216 // Look at each character
217 foundother = ( CHR_REF )MAX_CHR;
218 for ( character = 0; character < MAX_CHR; character++ )
219 {
220 chr_t * pchr;
221
222 if ( !INGAME_CHR( character ) ) continue;
223 pchr = ChrList.lst + character;
224
225 // dont do scenery objects unless we allow items
226 if ( !HAS_SOME_BITS( targeting_bits, TARGET_ITEMS ) && ( CHR_INFINITE_WEIGHT == pchr->phys.weight ) ) continue;
227
228 //Check if the object has the requirements
229 if ( !check_target( psrc, character, idsz, targeting_bits ) ) continue;
230
231 //Now check if it actually is inside the passage area
232 if ( object_is_in_passage( passage, pchr->pos.x, pchr->pos.y, pchr->bump_1.size ) )
233 {
234 // Found a live one, do we need to check for required items as well?
235 if ( IDSZ_NONE == require_item )
236 {
237 return character;
238 }
239
240 // It needs to have a specific item as well
241 else
242 {
243 // I: Check left hand
244 if ( chr_is_type_idsz( pchr->holdingwhich[SLOT_LEFT], require_item ) )
245 {
246 // It has the item...
247 return character;
248 }
249
250 // II: Check right hand
251 if ( chr_is_type_idsz( pchr->holdingwhich[SLOT_RIGHT], require_item ) )
252 {
253 // It has the item...
254 return character;
255 }
256
257 // III: Check the pack
258 PACK_BEGIN_LOOP( ipacked, pchr->pack.next )
259 {
260 if ( chr_is_type_idsz( ipacked, require_item ) )
261 {
262 // It has the ipacked in inventory...
263 return character;
264 }
265 }
266 PACK_END_LOOP( ipacked );
267 }
268 }
269 }
270
271 // No characters found
272 return foundother;
273 }
274
275 //--------------------------------------------------------------------------------------------
check_passage_music()276 void check_passage_music()
277 {
278 /// @details ZF@> This function checks all passages if there is a player in it, if it is, it plays a specified
279 /// song set in by the AI script functions
280
281 CHR_REF character = ( CHR_REF )MAX_CHR;
282 PASS_REF passage;
283
284 // Check every music passage
285 for ( passage = 0; passage < PassageStack.count; passage++ )
286 {
287 PLA_REF ipla;
288 passage_t * ppass = PassageStack.lst + passage;
289
290 if ( ppass->music == NO_MUSIC || ppass->music == get_current_song_playing() ) continue;
291
292 // Look at each player
293 for ( ipla = 0; ipla < MAX_PLAYER; ipla++ )
294 {
295 chr_t * pchr;
296
297 character = PlaStack.lst[ipla].index;
298
299 if ( !INGAME_CHR( character ) ) continue;
300 pchr = ChrList.lst + character;
301
302 if ( pchr->pack.is_packed || !pchr->alive || !VALID_PLA( pchr->is_which_player ) ) continue;
303
304 // Is it in the passage?
305 if ( object_is_in_passage( passage, pchr->pos.x, pchr->pos.y, pchr->bump_1.size ) )
306 {
307 // Found a player, start music track
308 sound_play_song( ppass->music, 0, -1 );
309 return;
310 }
311 }
312 }
313 }
314
315 //--------------------------------------------------------------------------------------------
close_passage(const PASS_REF passage)316 bool_t close_passage( const PASS_REF passage )
317 {
318 /// @details ZZ@> This function makes a passage impassable, and returns btrue if it isn't blocked
319 int x, y;
320 Uint32 fan, cnt;
321 passage_t * ppass;
322 CHR_REF character;
323
324 if ( INVALID_PASSAGE( passage ) ) return bfalse;
325 ppass = PassageStack.lst + passage;
326
327 // don't compute all of this for nothing
328 if ( 0 == ppass->mask ) return btrue;
329
330 // check to see if a wall can close
331 if ( 0 != HAS_SOME_BITS( ppass->mask, MPDFX_IMPASS | MPDFX_WALL ) )
332 {
333 size_t numcrushed = 0;
334 CHR_REF crushedcharacters[MAX_CHR];
335
336 // Make sure it isn't blocked
337 for ( character = 0; character < MAX_CHR; character++ )
338 {
339 chr_t *pchr;
340
341 if ( !INGAME_CHR( character ) ) continue;
342 pchr = ChrList.lst + character;
343
344 //Don't do held items
345 if ( pchr->pack.is_packed || INGAME_CHR( pchr->attachedto ) ) continue;
346
347 if ( 0.0f != pchr->bump_stt.size )
348 {
349 if ( object_is_in_passage( passage, pchr->pos.x, pchr->pos.y, pchr->bump_1.size ) )
350 {
351 if ( !pchr->canbecrushed && pchr->openstuff )
352 {
353 // Someone is blocking who can open stuff, stop here
354 return bfalse;
355 }
356 else
357 {
358 crushedcharacters[numcrushed] = character;
359 numcrushed++;
360 }
361 }
362 }
363 }
364
365 // Crush any unfortunate characters
366 for ( cnt = 0; cnt < numcrushed; cnt++ )
367 {
368 character = crushedcharacters[cnt];
369 SET_BIT( chr_get_pai( character )->alert, ALERTIF_CRUSHED );
370 }
371 }
372
373 // Close it off
374 ppass->open = bfalse;
375 for ( y = ppass->area.top; y <= ppass->area.bottom; y++ )
376 {
377 for ( x = ppass->area.left; x <= ppass->area.right; x++ )
378 {
379 fan = mesh_get_tile_int( PMesh, x, y );
380 mesh_add_fx( PMesh, fan, ppass->mask );
381 }
382 }
383
384 return btrue;
385 }
386
387 //--------------------------------------------------------------------------------------------
clear_all_passages()388 void clear_all_passages()
389 {
390 /// @details ZZ@> This function clears the passage list ( for doors )
391
392 PassageStack_free_all();
393 ShopStack_free_all();
394 }
395
396 //--------------------------------------------------------------------------------------------
add_shop_passage(const CHR_REF owner,const PASS_REF passage)397 void add_shop_passage( const CHR_REF owner, const PASS_REF passage )
398 {
399 /// @details ZZ@> This function creates a shop passage
400
401 SHOP_REF ishop;
402 CHR_REF ichr;
403
404 if ( !VALID_PASSAGE( passage ) ) return;
405
406 if ( !INGAME_CHR( owner ) || !ChrList.lst[owner].alive ) return;
407
408 ishop = ShopStack_get_free();
409 if ( !VALID_SHOP( ishop ) ) return;
410
411 // The passage exists...
412 ShopStack.lst[ishop].passage = passage;
413 ShopStack.lst[ishop].owner = owner;
414
415 // flag every item in the shop as a shop item
416 for ( ichr = 0; ichr < MAX_CHR; ichr++ )
417 {
418 chr_t * pchr;
419
420 if ( !INGAME_CHR( ichr ) ) continue;
421 pchr = ChrList.lst + ichr;
422
423 if ( pchr->isitem )
424 {
425 if ( object_is_in_passage( ShopStack.lst[ishop].passage, pchr->pos.x, pchr->pos.y, pchr->bump_1.size ) )
426 {
427 pchr->isshopitem = btrue; // Full value
428 pchr->iskursed = bfalse; // Shop items are never kursed
429 pchr->nameknown = btrue;
430 }
431 }
432 }
433 }
434
435 //--------------------------------------------------------------------------------------------
add_passage(passage_t * pdata)436 void add_passage( passage_t * pdata )
437 {
438 /// @details ZZ@> This function creates a passage area
439
440 PASS_REF ipass;
441 passage_t * ppass;
442
443 if ( NULL == pdata ) return;
444
445 ipass = PasageStack_get_free();
446
447 if ( ipass >= MAX_PASS ) return;
448 ppass = PassageStack.lst + ipass;
449
450 ppass->area.left = CLIP( pdata->area.left, 0, PMesh->info.tiles_x - 1 );
451 ppass->area.top = CLIP( pdata->area.top, 0, PMesh->info.tiles_y - 1 );
452
453 ppass->area.right = CLIP( pdata->area.right, 0, PMesh->info.tiles_x - 1 );
454 ppass->area.bottom = CLIP( pdata->area.bottom, 0, PMesh->info.tiles_y - 1 );
455
456 ppass->mask = pdata->mask;
457 ppass->music = pdata->music;
458
459 // Is it open or closed?
460 if ( pdata->open )
461 {
462 ppass->open = btrue;
463 }
464 else
465 {
466 close_passage( ipass );
467 }
468 }
469
470 //--------------------------------------------------------------------------------------------
activate_passages_file_vfs()471 void activate_passages_file_vfs()
472 {
473 /// @details ZZ@> This function reads the passage file
474 passage_t tmp_passage;
475 vfs_FILE *fileread;
476
477 // Reset all of the old passages
478 clear_all_passages();
479
480 // Load the file
481 fileread = vfs_openRead( "mp_data/passage.txt" );
482 if ( NULL == fileread ) return;
483
484 while ( scan_passage_file( fileread, &tmp_passage ) )
485 {
486 add_passage( &tmp_passage );
487 }
488
489 vfs_close( fileread );
490 }
491
492 //--------------------------------------------------------------------------------------------
shop_get_owner(int ix,int iy)493 CHR_REF shop_get_owner( int ix, int iy )
494 {
495 /// ZZ@> This function returns the owner of a item in a shop
496
497 SHOP_REF cnt;
498 CHR_REF owner = ( CHR_REF )SHOP_NOOWNER;
499
500 for ( cnt = 0; cnt < ShopStack.count; cnt++ )
501 {
502 PASS_REF passage;
503 passage_t * ppass;
504 shop_t * pshop;
505
506 pshop = ShopStack.lst + cnt;
507
508 passage = pshop->passage;
509
510 if ( INVALID_PASSAGE( passage ) ) continue;
511 ppass = PassageStack.lst + passage;
512
513 if ( irect_point_inside( &( ppass->area ), ix, iy ) )
514 {
515 // if there is SHOP_NOOWNER, someone has been murdered!
516 owner = pshop->owner;
517 break;
518 }
519 }
520
521 return owner;
522 }