00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
00033
00034 sint monstcanmove(sshort monst, sshort x, sshort y)
00035 {
00036 int target;
00037
00038
00039 if(x==w->m[monst].x && y==w->m[monst].y)
00040 return 1;
00041
00042
00043 if(distancesquare(w->m[monst].x, w->m[monst].y, x, y) > 2)
00044 return 0;
00045
00046
00047 if(x==w->plr.x && y==w->plr.y)
00048 return 1;
00049
00050
00051 if(w->t[y][x].type == TILE_CDOOR
00052 && MDESC(monst)->flags & MONSTFLAG_HASHANDS)
00053 return 1;
00054
00055
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
00065
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
00147
00148
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
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
00225
00226
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
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
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
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
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
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 )
00496 return 1;
00497 else if( (w->t[w->m[i].y][w->m[i].x].flags & TFLAG_LIT) )
00498
00499
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 ) &&
00536 distancesquare(xi, yi, w->plr.x, w->plr.y) < best_val )
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 ) &&
00558 distancesquare(xi, yi, w->plr.x, w->plr.y) > best_val )
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