monsters.cc

Go to the documentation of this file.
00001 /*
00002  *  monsters.cc - Monsters.
00003  *
00004  *  Copyright (C) 2000-2001  The Exult Team
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00019  */
00020 
00021 #ifdef HAVE_CONFIG_H
00022 #  include <config.h>
00023 #endif
00024 
00025 #include "monsters.h"
00026 #include "monstinf.h"
00027 #include "gamewin.h"
00028 #include "animate.h"
00029 #include "schedule.h"
00030 #include "chunks.h"
00031 #include "Audio.h"
00032 #include "gamemap.h"
00033 #include "game.h"
00034 #include "effects.h"
00035 
00036 #ifndef UNDER_CE
00037 using std::rand;
00038 #endif
00039 
00040 /*
00041  *  Slimes move differently.
00042  */
00043 class Slime_actor : public Monster_actor
00044   {
00045   void update_frames(Tile_coord src, Tile_coord dest);
00046 public:
00047   Slime_actor(const std::string &nm, int shapenum, int num = -1, 
00048               int uc = -1)
00049     : Monster_actor(nm, shapenum, num, uc)
00050     {  }
00051           // Step onto an (adjacent) tile.
00052   virtual int step(Tile_coord t, int frame);
00053           // Remove/delete this object.
00054   virtual void remove_this(int nodel = 0);
00055           // Move to new abs. location.
00056   virtual void move(int newtx, int newty, int newlift);
00057   };
00058 
00059 /*
00060  *  Monsters (like cyclops) that cause quakes when they walk:
00061  */
00062 class Quaking_actor : public Monster_actor
00063   {
00064   int qsteps;     // # steps between quakes.
00065   int steps;      // # steps taken.
00066 public:
00067   Quaking_actor(const std::string &nm, int shapenum, int qs = 5,
00068             int num = -1, int uc = -1)
00069     : Monster_actor(nm, shapenum, num, uc), qsteps(qs), steps(0)
00070     {  }
00071           // Step onto an (adjacent) tile.
00072   virtual int step(Tile_coord t, int frame);
00073   };
00074 
00075 Monster_actor *Monster_actor::in_world = 0;
00076 
00077 /*
00078  *  Add to global list (if not already there).
00079  */
00080 
00081 void Monster_actor::link_in
00082   (
00083   )
00084   {
00085   if (prev_monster || in_world == this)
00086     return;     // Already in list.
00087   if (in_world)     // Add to head.
00088     in_world->prev_monster = this;
00089   next_monster = in_world;
00090   in_world = this;
00091   }
00092 
00093 /*
00094  *  Remove from global list (if in list).
00095  */
00096 
00097 void Monster_actor::link_out
00098   (
00099   )
00100   {
00101   if (next_monster)
00102     next_monster->prev_monster = prev_monster;
00103   if (prev_monster)
00104     prev_monster->next_monster = next_monster;
00105   else        // We're at start of list.
00106     if (in_world == this)
00107       in_world = next_monster;
00108   next_monster = prev_monster = 0;
00109   }
00110 
00111 /*
00112  *  Create monster.
00113  */
00114 
00115 Monster_actor::Monster_actor
00116   (
00117   const std::string &nm, 
00118   int shapenum, 
00119   int num,      // Generally -1.
00120   int uc
00121   ) : Npc_actor(nm, shapenum, num, uc), prev_monster(0),
00122       next_monster(0), animator(0)
00123   {
00124           // Check for animated shape.
00125   Shape_info& info = get_info();
00126   if (info.is_animated())
00127     animator = Animator::create(this);
00128   }
00129 
00130 /*
00131  *  Delete.
00132  */
00133 
00134 Monster_actor::~Monster_actor
00135   (
00136   )
00137   {
00138   delete animator;
00139   link_out();     // Remove from chain.
00140   }
00141 
00142 /*
00143  *  Another set of constants that should be in a data file (or that's
00144  *  probably already in one of the U7 data files), this one correlating
00145  *  a 'monster' shape with the food frame you get when you kill it.
00146  */
00147 static int Monster_food[] = {
00148   498, 10,      // Chicken.
00149   500,  9,      // Cow - beef steaks.
00150   502,  14,     // Deer - meat.
00151   509, 12,      // Fish.
00152   811, 9,       // Rabbit - same as cow?
00153   970, 8,       // Sheep - mutton.
00154   727, 23,      // Horse - ribs.
00155   329, 11       // Boar - ham.
00156   };
00157 
00158 /*
00159  *  Find food frame for given monster shape.
00160  *
00161  *  Output: Frame if found, else a random frame (0-31).
00162  */
00163 
00164 static int Find_monster_food
00165   (
00166   int shnum     // Monster shape.
00167   )
00168   {
00169   const int cnt = sizeof(Monster_food)/(2*sizeof(Monster_food[0]));
00170   for (int i = 0; i < cnt; i++)
00171     if (Monster_food[2*i] == shnum)
00172       return Monster_food[2*i + 1];
00173   return rand()%32;
00174   }
00175 
00176 /*
00177  *  Create an instance of a monster.
00178  */
00179 
00180 Monster_actor *Monster_actor::create
00181   (
00182   int shnum     // Shape to use.
00183   )
00184   {
00185   if (shnum == 529)   // Slime?
00186           // Usecode = shape.
00187     return new Slime_actor("", shnum, -1, shnum);
00188   else if (shnum == 501 || (shnum == 380 && GAME_BG))
00189     return new Quaking_actor("", shnum, 5, -1, shnum);
00190   else
00191     return new Monster_actor("", shnum, -1, shnum);
00192   }
00193 
00194 /*
00195  *  Create an instance of a monster and initialize from monstinf.dat.
00196  */
00197 
00198 Monster_actor *Monster_actor::create
00199   (
00200   int shnum,      // Shape to use.
00201   Tile_coord pos,     // Where to place it.  If pos.tx < 0,
00202           //   it's not placed in the world.
00203   int sched,      // Schedule type.
00204   int align,      // Alignment.
00205   bool temporary,
00206   bool equipment
00207   )
00208   {
00209           // Get 'monsters.dat' info.
00210   const Monster_info *inf = ShapeID::get_info(shnum).get_monster_info();
00211   if (!inf)
00212     inf = Monster_info::get_default();
00213   Monster_actor *monster = create(shnum);
00214   monster->set_alignment(align == static_cast<int>(Actor::neutral)
00215             ? inf->alignment : align);
00216   // Movement flags
00217   if ((inf->flags >> Monster_info::fly)&1)
00218   {
00219     monster->set_type_flag(Actor::tf_fly);
00220   }
00221   if ((inf->flags >> Monster_info::swim)&1)
00222   {
00223     monster->set_type_flag(Actor::tf_swim);
00224   }
00225   if ((inf->flags >> Monster_info::walk)&1)
00226   {
00227     monster->set_type_flag(Actor::tf_walk);
00228   }
00229   if ((inf->flags >> Monster_info::ethereal)&1)
00230   {
00231     monster->set_type_flag(Actor::tf_ethereal);
00232   }
00233   monster->set_property(Actor::strength, inf->strength);
00234           // Max. health = strength.
00235   monster->set_property(Actor::health, inf->strength);
00236   monster->set_property(Actor::dexterity, inf->dexterity);
00237   monster->set_property(Actor::intelligence, inf->intelligence);
00238   monster->set_property(Actor::combat, inf->combat);
00239 
00240   // Set temporary
00241   if (temporary) monster->set_flag (Obj_flags::is_temporary);
00242   monster->set_invalid();   // Place in world.
00243   if (pos.tx >= 0)
00244     monster->move(pos.tx, pos.ty, pos.tz);
00245   if (equipment) {
00246           // Get equipment.
00247     int equip_offset = inf->equip_offset;
00248     Equip_record *equip = inf->equip;
00249     if (equip_offset && equip_offset - 1 < inf->equip_cnt)
00250     {
00251       Equip_record& rec = equip[equip_offset - 1];
00252       for (size_t i = 0;
00253          i < sizeof(equip->elements)/sizeof(
00254               equip->elements[0]);
00255          i++)
00256       {   // Give equipment.
00257         Equip_element& elem = rec.elements[i];
00258         if (!elem.shapenum || 1 + rand()%100 > 
00259             elem.probability)
00260           continue;// You lose.
00261         int frnum = (elem.shapenum == 377) ? 
00262           Find_monster_food(shnum) : 0;
00263         monster->create_quantity(elem.quantity, 
00264             elem.shapenum, c_any_qual, 
00265               frnum, temporary);
00266       }
00267     }
00268   }
00269 
00270   if (sched < 0)      // Set sched. AFTER equipping.
00271     sched = static_cast<int>(Schedule::loiter);
00272   monster->set_schedule_type(sched);
00273   return (monster);
00274   }
00275 
00276 /*
00277  *  Delete all monsters.  (Should only be called after deleting chunks.)
00278  */
00279 
00280 void Monster_actor::delete_all
00281   (
00282   )
00283   {
00284   while (in_world)
00285     delete in_world;
00286   }
00287 
00288 /*
00289  *  Render.
00290  */
00291 
00292 void Monster_actor::paint
00293   (
00294   )
00295   {
00296   // Animate first
00297   if (animator)     // Be sure animation is on.
00298     animator->want_animation();
00299   Npc_actor::paint();   // Draw on screen.
00300   }
00301 
00302 /*
00303  *  Step onto an adjacent tile.
00304  *
00305  *  Output: 0 if blocked.
00306  *    Dormant is set if off screen.
00307  */
00308 
00309 int Monster_actor::step
00310   (
00311   Tile_coord t,     // Tile to step onto.
00312   int frame     // New frame #.
00313   )
00314   {
00315   // If move not allowed do I remove or change destination?
00316   // I'll do nothing for now
00317   if (!gwin->emulate_is_move_allowed(t.tx, t.ty))
00318     return (0);
00319   if (get_flag(Obj_flags::paralyzed))
00320     return 0;
00321           // Store old chunk.
00322   int old_cx = get_cx(), old_cy = get_cy();
00323           // Get chunk.
00324   int cx = t.tx/c_tiles_per_chunk, cy = t.ty/c_tiles_per_chunk;
00325           // Get ->new chunk.
00326   Map_chunk *nlist = gmap->get_chunk(cx, cy);
00327   nlist->setup_cache();   // Setup cache if necessary.
00328           // Blocked?
00329   if (is_blocked(t))
00330     {
00331     if (schedule)   // Tell scheduler.
00332       schedule->set_blocked(t);
00333     stop();
00334     if (!gwin->add_dirty(this))
00335       dormant = true; // Off-screen.
00336     return (0);   // Done.
00337     }
00338           // Check for scrolling.
00339   gwin->scroll_if_needed(this, t);
00340   add_dirty();      // Set to repaint old area.
00341           // Get old chunk.
00342   Map_chunk *olist = gmap->get_chunk(old_cx, old_cy);
00343           // Move it.
00344           // Get rel. tile coords.
00345   int tx = t.tx%c_tiles_per_chunk, ty = t.ty%c_tiles_per_chunk;
00346   movef(olist, nlist, tx, ty, frame, t.tz);
00347   if (!add_dirty(1) &&
00348           // And > a screenful away?
00349       distance(gwin->get_camera_actor()) > 1 + 320/c_tilesize)
00350     {     // No longer on screen.
00351     stop();
00352     dormant = true;
00353     return (0);
00354     }
00355   return (1);     // Add back to queue for next time.
00356   }
00357 
00358 /*
00359  *  Remove an object from its container, or from the world.
00360  *  The object is deleted.
00361  */
00362 
00363 void Monster_actor::remove_this
00364   (
00365   int nodel     // 1 to not delete.
00366   )
00367   {
00368   link_out();     // Remove from list.
00369   Npc_actor::remove_this(nodel);
00370   }
00371 
00372 /*
00373  *  Move (teleport) to a new spot.
00374  */
00375 
00376 void Monster_actor::move
00377   (
00378   int newtx, 
00379   int newty, 
00380   int newlift
00381   )
00382   {
00383   Npc_actor::move(newtx, newty, newlift);
00384   link_in();      // Insure it's in global list.
00385   }
00386 
00387 /*
00388  *  Add an object.
00389  *
00390  *  Output: 1, meaning object is completely contained in this,
00391  *    0 if not enough space.
00392  */
00393 
00394 bool Monster_actor::add
00395   (
00396   Game_object *obj,
00397   bool dont_check,    // 1 to skip volume check.
00398   bool combine      // True to try to combine obj.  MAY
00399           //   cause obj to be deleted.
00400   )
00401   {
00402           // Try to add to 'readied' spot.
00403   if (Npc_actor::add(obj, true, combine))
00404     return (true);    // Successful.
00405           // Just add anything.
00406   return Container_game_object::add(obj, true, combine);
00407   }
00408 
00409 /*
00410  *  Get total value of armor being worn.
00411  */
00412 
00413 int Monster_actor::get_armor_points
00414   (
00415   )
00416   {
00417   Monster_info *inf = get_info().get_monster_info();
00418           // Kind of guessing here.
00419   return Actor::get_armor_points() + (inf ? inf->armor : 0);
00420   }
00421 
00422 /*
00423  *  Get weapon value.
00424  */
00425 
00426 Weapon_info *Monster_actor::get_weapon
00427   (
00428   int& points,
00429   int& shape
00430   )
00431   {
00432   Monster_info *inf = get_info().get_monster_info();
00433           // Kind of guessing here.
00434   Weapon_info *winf = Actor::get_weapon(points, shape);
00435   if (!winf)      // No readied weapon?
00436     {     // Look up monster itself.
00437     shape = 0;
00438     winf = get_info().get_weapon_info();
00439     if (winf)
00440       {
00441       shape = get_shapenum();
00442       points = winf->get_damage();
00443       }
00444     else if (inf)   // Builtin (claws?):
00445       points = inf->weapon;
00446     }
00447   return winf;
00448   }
00449 
00450 /*
00451  *  We're dead.  We're removed from the world, but not deleted.
00452  */
00453 
00454 void Monster_actor::die
00455   (
00456   Actor *attacker
00457   )
00458   {
00459   Actor::die(attacker);
00460           // Got to delete this somewhere, but
00461           //   doing it here crashes.
00462   }
00463 
00464 /*
00465  *  Get the tiles where slimes adjacent to one in a given position should
00466  *  be found.
00467  */
00468 
00469 static void Get_slime_neighbors
00470   (
00471   Tile_coord pos,     // Position to look around.
00472   Tile_coord *neighbors   // N,E,S,W tiles returned.
00473   )
00474   {
00475           // Offsets to neighbors 2 tiles away.
00476   static int offsets[8] = {0,-2, 2,0, 0,2, -2,0};
00477   for (int dir = 0; dir < 4; dir++)
00478     neighbors[dir] = pos +Tile_coord(offsets[2*dir],
00479                 offsets[2*dir + 1], 0);
00480   }
00481 
00482 /*
00483  *  Find whether a slime is a neighbor of a given spot.
00484  *
00485  *  Output: Direction (0-3 for N,E,S,W), or -1 if not found.
00486  */
00487 
00488 int Find_neighbor
00489   (
00490   Game_object *slime,
00491   Tile_coord *neighbors   // Neighboring spots to check.
00492   )
00493   {
00494   Tile_coord pos = slime->get_tile();
00495   for (int dir = 0; dir < 4; dir++)
00496     if (pos == neighbors[dir])
00497       return dir;
00498   return -1;      // Not found.
00499   }
00500 
00501 /*
00502  *  Update the frame of a slime and its neighbors after it has been moved.
00503  *  The assumption is that slimes are 2x2 tiles, and that framenum/2 is
00504  *  based on whether there are adjoining slimes to the N, W, S, or E, with
00505  *  bit 0 being random.
00506  */
00507 
00508 void Slime_actor::update_frames
00509   (
00510   Tile_coord src,     // May be invalid (tx = -1).
00511   Tile_coord dest     // May be invalid.  If src & dest are
00512           //   both valid, we assume they're at
00513           //   most 2 tiles apart.
00514   )
00515   {
00516   Tile_coord neighbors[4];  // Gets surrounding spots for slimes.
00517   int dir;      // Get direction of neighbor.
00518   Game_object_vector nearby;    // Get nearby slimes.
00519   if (src.tx != -1)
00520     if (dest.tx != -1)  // Assume within 2 tiles.
00521       Game_object::find_nearby(nearby, dest, 529, 4, 8);
00522     else
00523       Game_object::find_nearby(nearby, src, 529, 2, 8);
00524   else        // Assume they're both not invalid.
00525     Game_object::find_nearby(nearby, dest, 529, 2, 8);
00526   if (src.tx != -1)   // Update neighbors we moved from.
00527     {
00528     Get_slime_neighbors(src, neighbors);
00529     for (Game_object_vector::const_iterator it = nearby.begin();
00530             it != nearby.end(); ++it)
00531       {
00532       Game_object *slime = *it;
00533       if (slime != this && 
00534           (dir = Find_neighbor(slime, neighbors)) >= 0)
00535         {
00536         int ndir = (dir+2)%4;
00537           // Turn off bit (1<<ndir)*2, and set
00538           //   bit 0 randomly.
00539         slime->change_frame((slime->get_framenum()&
00540           ~(((1<<ndir)*2)|1)) |(rand()%2));
00541         }
00542       }
00543     }
00544   if (dest.tx != -1)    // Update neighbors we moved to.
00545     {
00546     int frnum = 0;    // Figure our new frame too.
00547     Get_slime_neighbors(dest, neighbors);
00548     for (Game_object_vector::const_iterator it = nearby.begin();
00549             it != nearby.end(); ++it)
00550       {
00551       Game_object *slime = *it;
00552       if (slime != this && 
00553           (dir = Find_neighbor(slime, neighbors)) >= 0)
00554         { // In a neighboring spot?
00555         frnum |= (1<<dir)*2;
00556         int ndir = (dir+2)%4;
00557           // Turn on bit (1<<ndir)*2, and set
00558           //   bit 0 randomly.
00559         slime->change_frame((slime->get_framenum()&~1)|
00560             ((1<<ndir)*2)|(rand()%2));
00561         }
00562       }
00563     change_frame(frnum|(rand()%2));
00564     }
00565   }
00566 
00567 /*
00568  *  Step onto an adjacent tile.
00569  *
00570  *  Output: 0 if blocked.
00571  *    Dormant is set if off screen.
00572  */
00573 
00574 int Slime_actor::step
00575   (
00576   Tile_coord t,     // Tile to step onto.
00577   int /* frame */     // New frame # (ignored).
00578   )
00579   {
00580           // Save old pos.
00581   Tile_coord oldpos = get_tile();
00582   int ret = Monster_actor::step(t, -1);
00583           // Update surrounding frames (& this).
00584   Tile_coord newpos = get_tile();
00585   update_frames(oldpos, newpos);
00586   Game_object_vector blood; // Place blood in old spot.
00587   if (newpos != oldpos && rand()%9 == 0 &&
00588       !find_nearby(blood, oldpos, 912, 1, 0))
00589     {
00590           // Frames 4-11 are green.
00591     Game_object *b = gmap->create_ireg_object(912, 4 + rand()%8);
00592     b->set_flag(Obj_flags::is_temporary);
00593     b->move(oldpos);
00594     }
00595   return ret;
00596   }
00597 
00598 /*
00599  *  Remove an object from its container, or from the world.
00600  *  The object is deleted.
00601  */
00602 
00603 void Slime_actor::remove_this
00604   (
00605   int nodel     // 1 to not delete.
00606   )
00607   {
00608   Tile_coord pos = get_tile();
00609   Monster_actor::remove_this(nodel);
00610           // Update surrounding slimes.
00611   update_frames(pos, Tile_coord(-1, -1, -1));
00612   }
00613 
00614 /*
00615  *  Move (teleport) to a new spot.  I assume slimes only use this when
00616  *  being added to the world.
00617  */
00618 
00619 void Slime_actor::move
00620   (
00621   int newtx, 
00622   int newty, 
00623   int newlift
00624   )
00625   {
00626           // Save old pos.
00627   Tile_coord pos = get_tile();
00628   if (get_cx() == 255)    // Invalid?
00629     pos = Tile_coord(-1, -1, -1);
00630   Monster_actor::move(newtx, newty, newlift);
00631           // Update surrounding frames (& this).
00632   update_frames(pos, get_tile());
00633   }
00634 
00635 /*
00636  *  Step onto an adjacent tile.
00637  *
00638  *  Output: 0 if blocked.
00639  *    Dormant is set if off screen.
00640  */
00641 
00642 int Quaking_actor::step
00643   (
00644   Tile_coord t,     // Tile to step onto.
00645   int frame     // New frame #.
00646   )
00647   {
00648   int ret = Monster_actor::step(t, frame);
00649   if (ret)
00650     {
00651     steps = (steps + 1)%qsteps;
00652     if (!steps)   // Time to roll?
00653       gwin->get_tqueue()->add(Game::get_ticks() + 10,
00654           new Earthquake(2), 0L);
00655     }
00656   return ret;
00657   }

Generated on Mon Jul 9 14:42:48 2007 for ExultEngine by  doxygen 1.5.1