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 }