Main Page | Modules | Alphabetical List | Data Structures | File List | Data Fields | Globals | Related Pages

src/monst.c

Go to the documentation of this file.
00001 /* {{{
00002  * CalcRogue, a roguelike game for PCs, calculators and PDAs
00003  * Copyright (C) 2003 Jim Babcock
00004  * 
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  * 
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  * 
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00018  * }}} */
00019 // mont.c
00022 
00023 #include "crogue.h"
00024 
00025 void monstmove(sint i);
00026 
00027 sint monst_facing(sint monst);
00028 void monst_remove(uint monstid);
00029 
00030 static int monst_attack_plr(int attacknum, int monster);
00031 
00032 // Utility
00033 //{{{
00034 sint monstcanmove(sshort monst, sshort x, sshort y)
00035 {
00036     int target;
00037     
00038     // Standing still is always legal
00039     if(x==w->m[monst].x && y==w->m[monst].y)
00040         return 1;
00041     
00042     // Moving more than one square at a time is bad
00043     if(distancesquare(w->m[monst].x, w->m[monst].y, x, y) > 2)
00044         return 0;
00045     
00046     // Always allow attacking the player
00047     if(x==w->plr.x && y==w->plr.y)  
00048         return 1;
00049     
00050     // Only monsters that have hands can upen doors
00051     if(w->t[y][x].type == TILE_CDOOR
00052        && MDESC(monst)->flags & MONSTFLAG_HASHANDS)
00053         return 1;
00054     
00055     // Some monsters can move through walls, but most can't
00056     if(!(w->tiledescs[w->t[y][x].type].passable))
00057     {
00058         if(!(MDESC(monst)->flags & MONSTFLAG_PASSWALL))
00059             return 0;
00060         if(x<=0 || x+1>=MAPSIZE_X || y<=0 || y+1>=MAPSIZE_Y)
00061             return 0;
00062     }
00063     
00064     // Moving onto an occupied tile is allowed if and only if one monster is
00065     // a pet and the other isn't
00066     if(w->t[y][x].flags & TFLAG_OCCUPIED)
00067     {
00068         target=monstbytile(x, y);
00069         if(target < 0) return 0;
00070         if(monst_is_pet(target) && !monst_is_peaceful(monst))
00071             return 1;
00072         else if(!monst_is_peaceful(target) && monst_is_pet(monst))
00073             return 1;
00074         else
00075             return 0;
00076     }
00077     return 1;
00078 }
00079 //}}}
00080 //{{{
00081 int monst_is_aligned_with_plr(int m)
00082 {
00083     int dx = abs(w->m[m].x - w->plr.x);
00084     int dy = abs(w->m[m].y - w->plr.y);
00085     if(!tracevision(w->m[m].x, w->m[m].y, w->plr.x, w->plr.y))
00086         return 0;
00087     return dx==0 || dy==0 || dx==dy;
00088 }
00089 //}}}
00090 
00091 //{{{
00092 sint monstbytile(ushort x, ushort y)
00093 {
00094     int i;
00095     
00096     if(!w->t[y][x].flags & TFLAG_OCCUPIED)
00097         return -1;
00098     
00099     for(i=0; i<MONSTERS_MAX; i++)
00100     {
00101         if(w->m[i].x==x && w->m[i].y==y && !isNull(w->m[i].type))
00102             return i;
00103     }
00104     return -1;
00105 }
00106 //}}}
00107 //{{{
00112 void compress_monsters(void)
00113 {
00114     int i, j;
00115     
00116     for(i=j=0; i<MONSTERS_MAX; i++)
00117     {
00118         if(!isNull(w->m[i].type))
00119         {
00120             if(i != j)
00121             {
00122                 memcpy(&w->m[j], &w->m[i], sizeof(monster));
00123                 memset(&w->m[i], 0, sizeof(monster));
00124             }
00125             j++;
00126         }
00127         else
00128         {
00129             memset(&w->m[i], 0, sizeof(monster));
00130         }
00131     }
00132 }
00133 //}}}
00134 //{{{
00140 void place_monster(monster *monst, ushort x, ushort y)
00141 {
00142     uint ii, xi, yi, best_x=x, best_y=y;
00143     sint best_distance = -1;
00144     static int search_start = 0;
00145     
00146     // Find an open monster handle. Rather than a full-blown linked-list
00147     // allocator, we'll just remember where we last got free space, and
00148     // start searching from there.
00149     ii = search_start;
00150     do {
00151         if(isNull(w->m[ii].type))
00152             break;
00153         ii++;
00154         if(ii >= MONSTERS_MAX) ii=0;
00155     } while(ii != search_start);
00156     
00157     if(ii==search_start && !isNull(w->m[ii].type))
00158         return;
00159     
00160     search_start = ii;
00161     
00162     // Find the nearest open tile
00163     for(yi=1; yi<MAPSIZE_Y-1; yi++)
00164     for(xi=1; xi<MAPSIZE_X-1; xi++)
00165     {
00166         if(TILEDESC(w->t[yi][xi]).passable && !(w->t[yi][xi].flags & TFLAG_OCCUPIED) && (distancesquare(xi, yi, x, y) < best_distance || best_distance == -1))
00167         {
00168             best_distance = distancesquare(xi, yi, x, y);
00169             best_y = yi;
00170             best_x = xi;
00171         }
00172     }
00173     x = best_x;
00174     y = best_y;
00175     
00176     monst->x = x;
00177     monst->y = y;
00178     w->m[ii] = *monst;
00179     w->t[y][x].flags |= TFLAG_OCCUPIED;
00180     draw_tile(x, y);
00181 }
00182 //}}}
00183 
00184 //{{{
00185 #define NUM_HALLUNAMES 10
00186 static const char* hallunames[NUM_HALLUNAMES] = {
00187     gettext("bugblatter beast of Traal"),
00188     gettext("greater hell beast"),
00189     gettext("invisible Asmodeus"),
00190     gettext("power grid bug"),
00191     gettext("pet kitten"),
00192     gettext("Usenet troll"),
00193     gettext("quantum mechanic"),
00194     gettext("Santa Claus"),
00195     gettext("Grinch"),
00196     gettext("giant hair")
00197 };
00198 //}}}
00199 //{{{
00201 const char* monstname(sint n)
00202 {
00203     if(w->plr.extrinsic[STAT_FLAGS] & STAT_FLAG_HALLUCINATING)
00204         return hallunames[RANGE(NUM_HALLUNAMES-1, 0)];
00205     else
00206         return deref_file_ptr(MDESC(n)->name);
00207 }
00208 //}}}
00209 //{{{
00211 sint player_can_see(uint m)
00212 {
00213     if(!(w->t[w->m[m].y][w->m[m].x].flags & TFLAG_LIT))
00214         return 0;
00215     else if(MDESC(m)->flags & MONSTFLAG_INVISIBLE &&
00216             !(w->plr.extrinsic[STAT_FLAGS] & STAT_FLAG_SEEINVIS))
00217         return 0;
00218     else
00219         return 1;
00220 }
00221 //}}}
00222 
00223 //{{{
00224 // This is a pre-calculation. The function is (for x=to_hit - dv)
00225 // 28*atan(.15*x) + 50 + 0.1*x
00226 // This function was selected experimentally, not based on any theory.
00227 static const uint hit_chance[21] = {
00228         500, 542,
00229         583, 621,
00230         655, 685,
00231         711, 734,
00232         753, 770,
00233         785, 798,
00234         810, 820,
00235         829, 838,
00236         845, 852,
00237         859, 864,
00238         870
00239     };
00240 //}}}
00241 //{{{
00250 int calculate_hit(int to_hit, int defense)
00251 {
00252     uint chance;
00253     sint dv_difference = to_hit - defense;
00254     if(dv_difference>20) dv_difference = 20;
00255     if(dv_difference<-20) dv_difference = -20;
00256     if(dv_difference>=0) chance = hit_chance[dv_difference];
00257     else                 chance = 1000-hit_chance[-dv_difference];
00258     
00259     if(lrand()%1000 <= chance)
00260         return 1;
00261     else 
00262         return 0;
00263 
00264 }
00265 //}}}
00266 
00267 //{{{
00268 sint monst_facing(sint monst)
00269 {
00270     int df = delta_facing(player_facing(),
00271                           facing_towards(w->m[monst].x, w->m[monst].y));
00272     if(df > 2)
00273         return FACING_BEHIND;
00274     else if(df == 2)
00275         return FACING_FLANK;
00276     else
00277         return FACING_FRONT;
00278 }
00279 //}}}
00280 
00281 //{{{
00282 int monst_hit_plr(int attacknum, int monster)
00283 {
00284     return calculate_hit(
00285         MATTACK(monster, attacknum).tohit +
00286         (monst_facing(monster) == FACING_BEHIND)*3 +
00287         2*MDESC(monster)->level,
00288         w->plr.extrinsic[STAT_DV]);
00289 }
00290 //}}}
00291 //{{{
00292 int monst_hit_monst(int attacknum, int attacker, int target)
00293 {
00294     return calculate_hit(
00295         MATTACK(attacker, attacknum).tohit +
00296         2*MDESC(attacker)->level + 2,
00297         MDESC(target)->dv);
00298 }
00299 //}}}
00300 //{{{
00301 static int monst_attack_plr(int attacknum, int monster)
00302 {
00303     int damage;
00304     monstattack *attacks;
00305     
00306     attacks = MDESC(monster)->attacks;
00307     damage = rrandom( attacks[attacknum].damage );
00308     
00309 #ifdef DEBUG_BALANCE
00310     if(w->debug_mode)
00311         message("(%i,%i)v[%i,%i]", MATTACK(monster, attacknum).tohit +
00312             2*MDESC(monster)->level, damage,
00313             w->plr.extrinsic[STAT_DV], w->plr.extrinsic[STAT_PV]);
00314 #endif
00315     return call_attackfunc( attacks[attacknum].type, monster, damage, -1 );
00316 }
00317 //}}}
00318 
00319 //{{{
00320 int monst_attack_type(int trigger, int monst, int always_hit)
00321 {
00322     int i;
00323     int ret=0;
00324     
00325     for(i=0; i<MDESC(monst)->numattacks; i++)
00326     {
00327         if(MATTACK(monst, i).trigger == trigger)
00328         {
00329             if(always_hit || monst_hit_plr(i, monst)) {
00330                 if(monst_attack_plr(i, monst))
00331                     ret=1;
00332             } else {
00333                 ret = 1;
00334 #ifdef DEBUG_BALANCE
00335                 if(w->debug_mode)
00336                     message("(%i,-)v[%i,%i]",
00337                         MATTACK(monst,i).tohit + 2*MDESC(monst)->level,
00338                         w->plr.extrinsic[STAT_DV], w->plr.extrinsic[STAT_PV]);
00339 #endif
00340                 if(monst_facing(monst) == FACING_BEHIND)
00341                     message(gettext("The %s attacks from behind but misses!"),
00342                         monstname(monst));
00343                 else
00344                     message(gettext("The %s misses!"), monstname(monst));
00345             }
00346         }
00347         
00348         if(isNull(w->m[monst].type))
00349             return ret;
00350     }
00351     return ret;
00352 }
00353 //}}}
00354 
00355 //{{{
00356 void addrandommonst(void)
00357 {
00358     int x, y;
00359     
00360     // Find an open tile
00361     do
00362     {
00363         x = RANGE( MAPSIZE_X-1, 1);
00364         y = RANGE( MAPSIZE_Y-1, 1);
00365     }
00366     while( !TILEDESC(w->t[y][x]).passable || TILEDESC(w->t[y][x]).is_connection
00367            || (w->t[y][x].flags & TFLAG_OCCUPIED) );
00368     addmonster(x, y, MAPDESC_CURRENT.monstertab);
00369 }
00370 //}}}
00371 //{{{
00372 void addmonster(ushort x, ushort y, filelink type)
00373 {
00374     uint i;
00375     const filelink desc = deref_file_ptr_partial(type);
00376     int num_place = 1;
00377     monster monst;
00378     const monstdesc *ptr_desc = (const monstdesc*)deref_file_ptr(desc);
00379     
00380     if(ptr_desc->flags & MONSTFLAG_GROUP)
00381         num_place = 4;
00382     
00383     for(i=0; i<num_place; i++)
00384     {
00385         monst.type = desc;
00386         monst.hps = ptr_desc->hps_max;
00387         monst.energy = 0;
00388         monst.power = ptr_desc->power_max;
00389         monst.flags = ptr_desc->ai;
00390         place_monster(&monst, x, y);
00391     }
00392 }
00393 //}}}
00394 //{{{
00395 // For data file use (struct by value is too awkward)
00396 void addmonster_ptr(filelink *type, ushort x, ushort y)
00397 {
00398     addmonster(x, y, *type);
00399 }
00400 //}}}
00401 //{{{
00402 void monst_remove(uint monstid)
00403 {
00404     w->t[w->m[monstid].y][w->m[monstid].x].flags &= ~TFLAG_OCCUPIED;
00405     w->m[monstid].type = filelink_null;
00406     draw_tile(w->m[monstid].x, w->m[monstid].y);
00407 }
00408 //}}}
00409 //{{{
00410 void monst_takedamage(sint monstid, sint damage, sint is_melee)
00411 {
00412     if(damage<0)
00413         return;
00414     
00415     damage = max( damage-MDESC(monstid)->pv, damage/4 );
00416     w->m[monstid].hps -= damage;
00417     
00418     if( w->m[monstid].hps <= 0 )
00419     {
00420         if(player_can_see(monstid))
00421             message(gettext("The %s is killed!"), monstname(monstid));
00422         awardXP(MDESC(monstid)->xp);
00423         
00424         if(is_melee)
00425         {
00426             // Look for death-defense attacks
00427             monst_attack_type(MTRIGGER_DEATH, monstid, 0);
00428         }
00429         
00430         call_killfunc(MDESC(monstid)->deathfunc, monstid);
00431         monst_remove(monstid);
00432         return;
00433     } else {
00434         if(is_melee)
00435         {
00436             // Look for defensive attacks
00437             monst_attack_type(MTRIGGER_DEFENSE, monstid, 0);
00438         }
00439     }
00440     if(w->m[monstid].hps*4 <= MDESC(monstid)->hps_max && RANGE(1,0) &&
00441        !(MDESC(monstid)->flags))
00442         scare_monster(monstid);
00443 }
00444 //}}}
00445 //{{{
00446 void monst_heal(uint monstid, uint amt)
00447 {
00448     w->m[monstid].hps += amt;
00449     
00450     if(w->m[monstid].hps > MDESC(monstid)->hps_max)
00451         w->m[monstid].hps = MDESC(monstid)->hps_max;
00452 }
00453 //}}}
00454 
00455 // AI
00456 //{{{
00457 void monstmoveto(sint monst, sint x, sint y)
00458 {
00459     int target;
00460     if(w->t[y][x].flags & TFLAG_OCCUPIED)
00461     {
00462         if(x == w->plr.x && y == w->plr.y)
00463         {
00464             if(!monst_attack_type(MTRIGGER_NORMAL, monst, 0))
00465                 monst_attack_type(MTRIGGER_RANGED, monst, 1);
00466         }
00467         else
00468         {
00469             target = monstbytile(x,y);
00470             if(target >= 0 && target != monst)
00471                 monst_swing_monst(monst, target, MTRIGGER_NORMAL);
00472         }
00473     } else if(w->t[y][x].type == TILE_CDOOR)
00474     {
00475         w->t[y][x].type = TILE_ODOOR;
00476         draw_tile(x, y);
00477         calc_light();
00478     } else {
00479         w->t[w->m[monst].y][w->m[monst].x].flags &= ~TFLAG_OCCUPIED;
00480         draw_tile(w->m[monst].x, w->m[monst].y);
00481         w->m[monst].y = y;
00482         w->m[monst].x = x;
00483         w->t[y][x].flags |= TFLAG_OCCUPIED;
00484         draw_tile(x, y);
00485     }
00486 }
00487 //}}}
00488 //{{{
00489 ushort monst_detect_player(ushort i)
00490 {
00491     int d = distancesquare(w->m[i].x, w->m[i].y, w->plr.x, w->plr.y);
00492     
00493     if(d > 144)
00494         return 0;
00495     if( d<=16 ) // Can hear the player
00496         return 1;
00497     else if( (w->t[w->m[i].y][w->m[i].x].flags & TFLAG_LIT) )
00498         // Reasonably near the player (12 tiles) and can see the player.
00499         // FIXME: This limits vision to player's sight range
00500         return 1;
00501     else
00502         return 0;
00503 }
00504 //}}}
00505 //{{{
00506 ushort monst_can_attack_player(ushort i)
00507 {
00508     return distancesquare(w->m[i].x, w->m[i].y, w->plr.x, w->plr.y) <= 2;
00509 }
00510 //}}}
00511 
00512 //{{{
00513 void monstmoverandomly(sint i, uint attack)
00514 {
00515     int x, y;
00516     do
00517     {
00518         x = w->m[i].x + RANGE(2, 0) - 1;
00519         y = w->m[i].y + RANGE(2, 0) - 1;
00520     } while( !monstcanmove(i, x, y) ||
00521             (!attack && x==w->plr.x && y==w->plr.y) );
00522     monstmoveto(i, x, y);
00523 }
00524 //}}}
00525 //{{{
00526 void monstmovetowardsplayer(sint i)
00527 {
00528     uint best_val = 0xFFFF;
00529     sint best_x = w->m[i].x, best_y = w->m[i].y;
00530     sint xi, yi;
00531     
00532     for(yi = w->m[i].y-1; yi <= w->m[i].y+1; yi++)
00533         for(xi = w->m[i].x-1; xi <= w->m[i].x+1; xi++)
00534         {
00535             if( monstcanmove( i, xi, yi ) &&  // Move is legal
00536                 distancesquare(xi, yi, w->plr.x, w->plr.y) < best_val ) // And is an improvement
00537             {
00538                 best_val = distancesquare(xi, yi, w->plr.x, w->plr.y);
00539                 best_x = xi;
00540                 best_y = yi;
00541             }
00542         }
00543     
00544     monstmoveto(i, best_x, best_y);
00545 }
00546 //}}}
00547 //{{{
00548 void monstmoveawayfromplayer(sint i)
00549 {
00550     uint best_val = 0;
00551     sint best_x = w->m[i].x, best_y = w->m[i].y;
00552     sint xi, yi;
00553     
00554     for(yi = w->m[i].y-1; yi <= w->m[i].y+1; yi++)
00555         for(xi = w->m[i].x-1; xi <= w->m[i].x+1; xi++)
00556         {
00557             if( monstcanmove( i, xi, yi ) &&  // Move is legal
00558                 distancesquare(xi, yi, w->plr.x, w->plr.y) > best_val ) // And is an improvement
00559             {
00560                 best_val = distancesquare(xi, yi, w->plr.x, w->plr.y);
00561                 best_x = xi;
00562                 best_y = yi;
00563             }
00564         }
00565     
00566     monstmoveto(i, best_x, best_y);
00567 }
00568 //}}}
00569 //{{{
00570 int monst_is_peaceful(int monstid)
00571 {
00572     if(w->m[monstid].flags & (MONST_PET | MONST_FRIENDLY))
00573         return 1;
00574     else
00575         return 0;
00576 }
00577 //}}}
00578 //{{{
00579 int monst_is_pet(int monstid)
00580 {
00581     if(w->m[monstid].flags & MONST_PET)
00582          return 1;
00583     else return 0;
00584 }
00585 //}}}
00586 
00587 //{{{
00588 void monst_anger(int monstid)
00589 {
00590     int flags;
00591     
00592     flags = w->m[monstid].flags;
00593     if(flags & MONST_CUST_ANGER) {
00594         call_aifunc( MDESC(monstid)->ai_anger, monstid );
00595     } else if(flags & MONST_MIMIC) {
00596         w->m[monstid].flags |= MONST_ACTIVE;
00597     } else if(flags & (MONST_PEACEFUL|MONST_FRIENDLY)) {
00598         w->m[monstid].flags &= ~(MONST_PEACEFUL|MONST_FRIENDLY);
00599         w->m[monstid].flags |= MONST_ACTIVE;
00600         if(flags & MONST_FRIENDLY)
00601             message(gettext("The %s gets angry!"), monstname(monstid));
00602     }
00603 }
00604 //}}}
00605 //{{{
00606 void scare_monster(ushort monstid)
00607 {
00608     if(MDESC(monstid)->flags & MONSTFLAG_FEARLESS)
00609         return;
00610     scare_monster_force(monstid);
00611 }
00612 //}}}
00613 //{{{
00614 void scare_monster_force(int monstid)
00615 {
00616     if(player_can_see(monstid))
00617         message(gettext("The %s turns to flee!"), monstname(monstid));
00618     w->m[monstid].flags |= MONST_SCARED;
00619 }
00620 //}}}
00621 //{{{
00622 void confuse_monster(int monstid)
00623 {
00624     if(player_can_see(monstid))
00625         message(gettext("The %s is dazed."), monstname(monstid));
00626     w->m[monstid].flags |= MONST_CONFUSED;
00627 }
00628 //}}}
00629 
00630 //{{{
00631 void gather_wandering_monsters(void)
00632 {
00633     int ii;
00634     for(ii=0; ii<MONSTERS_MAX; ii++)
00635     {
00636         if(isNull(w->m[ii].type)) continue;
00637         if(monst_is_pet(ii) ||
00638             (monstcanmove(ii, w->plr.x, w->plr.y) && !monst_is_peaceful(ii)) )
00639         {
00640             w->wandering_monsters = debug_realloc(w->wandering_monsters,
00641                 (++w->wandering_monsters_num)*sizeof(monster));
00642             w->wandering_monsters[w->wandering_monsters_num-1] = w->m[ii];
00643             monst_remove(ii);
00644         }
00645     }
00646 }
00647 //}}}
00648 //{{{
00649 void place_wandering_monsters(void)
00650 {
00651     int ii;
00652     if(w->wandering_monsters)
00653     {
00654         for(ii=0; ii<w->wandering_monsters_num; ii++)
00655             place_monster(&w->wandering_monsters[ii], w->plr.x, w->plr.y);
00656         debug_free(w->wandering_monsters);
00657         w->wandering_monsters = NULL;
00658         w->wandering_monsters_num = 0;
00659     }
00660 }
00661 //}}}
00662 

Generated on Thu May 20 13:12:10 2004 for CalcRogue by doxygen 1.3.6