combat.cc

Go to the documentation of this file.
00001 /*
00002  *  combat.h - Combat scheduling.
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 "combat.h"
00026 #include "combat_opts.h"
00027 #include "gamewin.h"
00028 #include "gameclk.h"
00029 #include "gamemap.h"
00030 #include "actors.h"
00031 #include "paths.h"
00032 #include "Astar.h"
00033 #include "actions.h"
00034 #include "items.h"
00035 #include "effects.h"
00036 #include "Audio.h"
00037 #include "ready.h"
00038 #include "game.h"
00039 #include "monstinf.h"
00040 #include "ucmachine.h"
00041 #include "game.h"
00042 #include "Gump_manager.h"
00043 #include "spellbook.h"
00044 
00045 #ifndef UNDER_CE
00046 using std::cout;
00047 using std::endl;
00048 using std::rand;
00049 #endif
00050 
00051 unsigned long Combat_schedule::battle_time = (unsigned long) (-30000);
00052 unsigned long Combat_schedule::battle_end_time = 0;
00053 
00054 bool Combat::paused = false;
00055 int Combat::difficulty = 0;
00056 Combat::Mode Combat::mode = Combat::original;
00057 bool Combat::show_hits = false;
00058 
00059 extern bool combat_trace;
00060 
00061 /*
00062  *  Is a given ammo shape in a given family.
00063  */
00064 
00065 bool In_ammo_family(int shnum, int family)
00066   {
00067   if (shnum == family)
00068     return true;
00069   Ammo_info *ainf = ShapeID::get_info(shnum).get_ammo_info();
00070   return (ainf != 0 && ainf->get_family_shape() == family);
00071   }
00072 
00073 /*
00074  *  Start music if battle has recently started.
00075  */
00076 
00077 void Combat_schedule::start_battle
00078   (
00079   )
00080   {
00081   if (started_battle)
00082     return;
00083           // But only if Avatar is main char.
00084   if (gwin->get_camera_actor() != gwin->get_main_actor())
00085     return;
00086   unsigned long curtime = Game::get_ticks();
00087           // .5 minute since last start?
00088   if (curtime - battle_time >= 30000)
00089     {
00090     Audio::get_ptr()->start_music_combat(rand()%2 ? 
00091           CSAttacked1 : CSAttacked2, 0);
00092     battle_time = curtime;
00093     battle_end_time = curtime - 1;
00094     }
00095   started_battle = true;
00096   }
00097 
00098 /*
00099  *  This (static) method is called when a monster dies.  It checks to
00100  *  see if there are still hostile NPC's around.  If not, it plays
00101  *  'victory' music.
00102  */
00103 
00104 void Combat_schedule::monster_died
00105   (
00106   )
00107   {
00108   if (battle_end_time >= battle_time)// Battle raging?
00109     return;     // No, it's over.
00110   Actor_queue nearby;   // Get all nearby NPC's.
00111   gwin->get_nearby_npcs(nearby);
00112   for (Actor_queue::const_iterator it = nearby.begin(); 
00113             it != nearby.end(); ++it)
00114     {
00115     Actor *actor = *it;
00116     if (!actor->is_dead() && 
00117       actor->get_attack_mode() != Actor::flee &&
00118         actor->get_alignment() >= Npc_actor::hostile)
00119       return;   // Still possible enemies.
00120     }
00121   battle_end_time = Game::get_ticks();
00122           // Figure #seconds battle lasted.
00123   unsigned long len = (battle_end_time - battle_time)/1000;
00124   bool hard = len > 15 && (rand()%60 < len);
00125   Audio::get_ptr()->start_music_combat (hard ? CSBattle_Over
00126               : CSVictory, 0);
00127   }
00128 
00129 /*
00130  *  Can a given shape teleport?
00131  */
00132 
00133 inline bool Can_teleport
00134   (
00135   Actor *npc
00136   )
00137   {
00138   switch (npc->get_shapenum())
00139     {
00140   case 534:     // Wisp.
00141   case 445:
00142   case 446:     // Mages.
00143   case 519:     // Liche.
00144     return true;
00145   default:
00146     return false;
00147     }
00148   }
00149 
00150 /*
00151  *  Certain monsters (wisps, mages) can teleport during battle.
00152  */
00153 
00154 bool Combat_schedule::teleport
00155   (
00156   )
00157   {
00158   Game_object *trg = npc->get_target(); // Want to get close to targ.
00159   if (!trg)
00160     return false;
00161   unsigned int curtime = SDL_GetTicks();
00162   if (curtime < teleport_time)
00163     return false;
00164   teleport_time = curtime + 2000 + rand()%2000;
00165   Tile_coord dest = trg->get_tile();
00166   dest.tx += 4 - rand()%8;
00167   dest.ty += 4 - rand()%8;
00168   dest = Map_chunk::find_spot(dest, 3, npc, 1);
00169   if (dest.tx == -1)
00170     return false;   // No spot found.
00171   Tile_coord src = npc->get_tile();
00172   if (dest.distance(src) > 6 && rand()%5 != 0)
00173     return false;   // Got to give Avatar a chance to
00174           //   get away.
00175           // Create fire-field where he was.
00176   src.tz = npc->get_chunk()->get_highest_blocked(src.tz,
00177       src.tx%c_tiles_per_chunk, src.ty%c_tiles_per_chunk);
00178   if (src.tz < 0)
00179     src.tz = 0;
00180   eman->add_effect(new Fire_field_effect(src));
00181   npc->move(dest.tx, dest.ty, dest.tz);
00182           // Show the stars.
00183   eman->add_effect(new Sprites_effect(7, npc, 0, 0, 0, 0));
00184   return true;
00185   }
00186 
00187 /*
00188  *  Off-screen?
00189  */
00190 
00191 inline bool Off_screen
00192   (
00193   Game_window *gwin,
00194   Game_object *npc
00195   )
00196   {
00197           // See if off screen.
00198   Tile_coord t = npc->get_tile();
00199   Rectangle screen = gwin->get_win_tile_rect().enlarge(2);
00200   return (!screen.has_point(t.tx, t.ty));
00201   }
00202 
00203 /*
00204  *  Find nearby opponents in the 9 surrounding chunks.
00205  */
00206 
00207 void Combat_schedule::find_opponents
00208   (
00209   )
00210 {
00211   opponents.clear();
00212   Game_window *gwin = Game_window::get_instance();
00213   if (npc->get_alignment() >= Npc_actor::hostile)
00214   {
00215     Actor *party[10];
00216     int cnt = gwin->get_party(party, 1);
00217     Actor *cact = gwin->get_camera_actor();
00218           // Watch for companion on List Field.
00219     if (cact && cact != gwin->get_main_actor() &&
00220         cact->get_alignment() == Npc_actor::friendly &&
00221         cact->get_flag(Obj_flags::si_tournament))
00222       party[cnt++] = cact;
00223     for (int i = 0; i < cnt; i++)
00224           // But ignore invisible ones.
00225       if (!party[i]->get_flag(Obj_flags::invisible) &&
00226           party[i] != npc)
00227         opponents.push(party[i]);
00228     return;
00229   }
00230   Actor_queue nearby;     // Get all nearby NPC's.
00231   gwin->get_nearby_npcs(nearby);
00232           // See if we're a party member.
00233   bool in_party = npc->is_in_party();
00234   for (Actor_queue::const_iterator it = nearby.begin(); 
00235             it != nearby.end(); ++it)
00236   {
00237     Actor *actor = *it;
00238     if (actor->is_dead() || actor->get_flag(Obj_flags::invisible))
00239       continue; // Dead or invisible.
00240     if (actor->get_alignment() >= Npc_actor::hostile)
00241     {
00242       opponents.push(actor);
00243     }
00244     else if (in_party)
00245       {   // Attacking party member?
00246       Game_object *t = actor->get_target();
00247       if (t && t->get_flag(Obj_flags::in_party))
00248         opponents.push(actor);
00249       }
00250   }
00251           // None found?  Use Avatar's.
00252   if (opponents.empty() && npc->is_in_party() &&
00253       npc != gwin->get_main_actor())
00254   {
00255     Game_object *opp = gwin->get_main_actor()->get_target();
00256     Actor *oppnpc = opp ? opp->as_actor() : 0;
00257     if (oppnpc && oppnpc != npc)
00258       opponents.push(oppnpc);
00259   }
00260 }   
00261 
00262 /*
00263  *  Find 'protected' party member's attackers.
00264  *
00265  *  Output: ->attacker, or 0 if not found.
00266  */
00267 
00268 Actor *Combat_schedule::find_protected_attacker
00269   (
00270   )
00271   {
00272   if (!npc->is_in_party())  // Not in party?
00273     return 0;
00274   Game_window *gwin = Game_window::get_instance();
00275   Actor *party[9];    // Get entire party, including Avatar.
00276   int cnt = gwin->get_party(party, 1);
00277   Actor *prot_actor = 0;
00278   for (int i = 0; i < cnt; i++)
00279     if (party[i]->is_combat_protected())
00280       {
00281       prot_actor = party[i];
00282       break;
00283       }
00284   if (!prot_actor)    // Not found?
00285     return 0;
00286           // Find closest attacker.
00287   int dist, best_dist = 4*c_tiles_per_chunk;
00288   Actor *best_opp = 0;
00289   for (Actor_queue::const_iterator it = opponents.begin(); 
00290             it != opponents.end(); ++it)
00291     {
00292     Actor *opp = *it;
00293     if (opp->get_target() == prot_actor &&
00294         (dist = npc->distance(opp)) < best_dist)
00295       {
00296       best_dist = dist;
00297       best_opp = opp;
00298       }
00299     }
00300   if (!best_opp)
00301     return 0;
00302   if (failures < 5 && yelled && rand()%2 && npc != prot_actor)
00303     npc->say(first_will_help, last_will_help);
00304   return best_opp;
00305   }
00306 
00307 /*
00308  *  Find a foe.
00309  *
00310  *  Output: Opponent that was found.
00311  */
00312 
00313 Game_object *Combat_schedule::find_foe
00314   (
00315   int mode      // Mode to use.
00316   )
00317 {
00318   if (combat_trace) {
00319     cout << "'" << npc->get_name() << "' is looking for a foe" << endl;
00320   }
00321           // Remove any that died.
00322   for (Actor_queue::const_iterator it = opponents.begin(); 
00323             it != opponents.end(); )
00324     {
00325     Actor_queue::const_iterator next = it;
00326     ++next;     // Point to next.
00327     if ((*it)->is_dead())
00328       opponents.remove(*it);
00329     it = next;
00330     }
00331   if (opponents.empty())  // No more from last scan?
00332     {
00333     find_opponents(); // Find all nearby.
00334     if (practice_target)  // For dueling.
00335       return practice_target;
00336     }
00337   Actor *new_opponent = 0;
00338   switch ((Actor::Attack_mode) mode)
00339     {
00340   case Actor::weakest:
00341     {
00342     int str, least_str = 100;
00343     for (Actor_queue::const_iterator it = opponents.begin(); 
00344           it != opponents.end(); ++it)
00345       {
00346       Actor *opp = *it;
00347       str = opp->get_property(Actor::strength);
00348       if (str < least_str)
00349         {
00350         least_str = str;
00351         new_opponent = opp;
00352         }
00353       }
00354     break;
00355     }
00356   case Actor::strongest:
00357     {
00358     int str, best_str = -100;
00359     for (Actor_queue::const_iterator it = opponents.begin(); 
00360             it != opponents.end(); ++it)
00361       {
00362       Actor *opp = *it;
00363       str = opp->get_property(Actor::strength);
00364       if (str > best_str)
00365         {
00366         best_str = str;
00367         new_opponent = opp;
00368         }
00369       }
00370     break;
00371     }
00372   case Actor::nearest:
00373     {
00374     int best_dist = 4*c_tiles_per_chunk;
00375     for (Actor_queue::const_iterator it = opponents.begin(); 
00376             it != opponents.end(); ++it)
00377       {
00378       Actor *opp = *it;
00379       int dist = npc->distance(opp);
00380       if (opp->get_attack_mode() == Actor::flee)
00381         dist += 16; // Avoid fleeing.
00382       if (dist < best_dist)
00383         {
00384         best_dist = dist;
00385         new_opponent = opp;
00386         }
00387       }
00388     break;
00389     }
00390   case Actor::protect:
00391     new_opponent = find_protected_attacker();
00392     if (new_opponent)
00393       break;    // Found one.
00394           // FALL THROUGH to 'random'.
00395   case Actor::random:
00396   default:      // Default to random.
00397     if (!new_opponent && !opponents.empty())
00398       new_opponent = opponents.front();
00399     break;
00400     }
00401   if (new_opponent)
00402     opponents.remove(new_opponent);
00403   return new_opponent;
00404   }
00405 
00406 /*
00407  *  Find a foe.
00408  *
00409  *  Output: Opponent that was found.
00410  */
00411 
00412 inline Game_object *Combat_schedule::find_foe
00413   (
00414   )
00415   {
00416   if (npc->get_attack_mode() == Actor::manual)
00417     return 0;   // Find it yourself.
00418   return find_foe(static_cast<int>(npc->get_attack_mode()));
00419   }
00420 
00421 /*
00422  *  Handle the 'approach' state.
00423  */
00424 
00425 void Combat_schedule::approach_foe
00426   (
00427   )
00428   {
00429   Game_object *opponent = npc->get_target();
00430           // Find opponent.
00431   if (!opponent && !(opponent = find_foe()))
00432     {
00433     failures++;
00434     npc->start(200, 400); // Try again in 2/5 sec.
00435     return;     // No one left to fight.
00436     }
00437   npc->set_target(opponent);
00438   Actor::Attack_mode mode = npc->get_attack_mode();
00439   Game_window *gwin = Game_window::get_instance();
00440           // Time to run?
00441   if (mode == Actor::flee || 
00442       (mode != Actor::beserk && 
00443           (npc->get_type_flags()&MOVE_ALL) != 0 &&
00444     npc != gwin->get_main_actor() &&
00445           npc->get_property(Actor::health) < 3))
00446     {
00447     run_away();
00448     return;
00449     }
00450   if (Can_teleport(npc) && rand()%4 == 0 && // Try 1/4 to teleport.
00451       teleport())
00452     {
00453     start_battle();
00454     npc->start(gwin->get_std_delay(), gwin->get_std_delay());
00455     return;
00456     }
00457   PathFinder *path = new Astar();
00458           // Try this for now:
00459   Monster_pathfinder_client cost(npc, max_range, opponent);
00460   Tile_coord pos = npc->get_tile();
00461   if (!path->NewPath(pos, opponent->get_tile(), &cost))
00462     {     // Failed?  Try nearest opponent.
00463     failures++;
00464     bool retry_ok = false;
00465     if (npc->get_attack_mode() != Actor::manual)
00466       {
00467       Game_object *closest = find_foe(Actor::nearest);
00468       if (closest && closest != opponent)
00469         {
00470         opponent = closest;
00471         npc->set_target(opponent);
00472         Monster_pathfinder_client cost(npc, max_range, 
00473                 opponent);
00474         retry_ok = (opponent != 0 && path->NewPath(
00475           pos, opponent->get_tile(), &cost));
00476         }
00477       }
00478     if (!retry_ok)
00479       {
00480       delete path;  // Really failed.  Try again in 
00481           //  after wandering.
00482           // Just try to walk somewhere.
00483       Tile_coord pos = opponent->get_tile();
00484       if (rand()%3 == 0)
00485         pos = pos + Tile_coord(rand()%12 - 6,
00486               rand()%12 - 6, 0);
00487       npc->walk_to_tile(pos, 2*gwin->get_std_delay(), 
00488               500 + rand()%500);
00489       failures++;
00490       return;
00491       }
00492     }
00493   failures = 0;     // Clear count.  We succeeded.
00494   start_battle();     // Music if first time.
00495   if (combat_trace) {
00496     cout << npc->get_name() << " is pursuing " << opponent->get_name()
00497        << endl;
00498   }
00499           // First time (or 256th), visible?
00500   if (!yelled && gwin->add_dirty(npc))
00501     {
00502     yelled++;
00503     if (can_yell && rand()%2)// Half the time.
00504       {
00505           // Goblin?
00506       if (Game::get_game_type() == SERPENT_ISLE &&
00507          (npc->get_shapenum() == 0x1de ||
00508           npc->get_shapenum() == 0x2b3 ||
00509           npc->get_shapenum() == 0x2d5 ||
00510           npc->get_shapenum() == 0x2e8))
00511         npc->say(0x4c9, 0x4d1);
00512           else
00513         npc->say(first_to_battle, last_to_battle);
00514       }
00515     }
00516           // Walk there, & check half-way.
00517   npc->set_action(new Approach_actor_action(path, opponent));
00518           // Start walking.  Delay a bit if
00519           //   opponent is off-screen.
00520   npc->start(gwin->get_std_delay(), Off_screen(gwin, opponent) ? 
00521     5*gwin->get_std_delay() : gwin->get_std_delay());
00522   }
00523 
00524 /*
00525  *  Swap weapon with the one in the belt.
00526  *
00527  *  Output: 1 if successful.
00528  */
00529 
00530 static int Swap_weapons
00531   (
00532   Actor *npc
00533   )
00534   {
00535   Game_object *bobj = npc->get_readied(Actor::belt);
00536   if (!bobj)
00537     {
00538     bobj = npc->get_readied(Actor::back2h_spot);
00539     if (!bobj)
00540       return 0;
00541     }
00542   Shape_info& info = bobj->get_info();
00543   Weapon_info *winf = info.get_weapon_info();
00544   if (!winf)
00545     return 0;   // Not a weapon.
00546   int ammo = winf->get_ammo_consumed();
00547   if (ammo)     // Check for readied ammo.
00548     {//+++++Needs improvement.  Ammo could be in pack.
00549     Game_object *aobj = npc->get_readied(Actor::ammo);
00550     if (!aobj || !In_ammo_family(aobj->get_shapenum(), ammo))
00551       return 0;
00552     }
00553   if (info.get_ready_type() == two_handed_weapon &&
00554       npc->get_readied(Actor::rhand) != 0)
00555     return 0;   // Needs two free hands.
00556   Game_object *oldweap = npc->get_readied(Actor::lhand);
00557   if (oldweap)
00558     npc->remove(oldweap);
00559   npc->remove(bobj);
00560   npc->add(bobj, 1);    // Should go into weapon hand.
00561   if (oldweap)
00562     npc->add(oldweap, 1); 
00563   return 1;
00564   }
00565 
00566 /*
00567  *  Begin a strike at the opponent.
00568  */
00569 
00570 void Combat_schedule::start_strike
00571   (
00572   )
00573   {
00574   Game_object *opponent = npc->get_target();
00575   Rectangle npctiles = npc->get_footprint(),
00576       opptiles = opponent->get_footprint();
00577   Rectangle stiles = npctiles,  // Get copy for weapon range.
00578       ptiles = npctiles;
00579           // Get difference in lift.
00580   int dz = npc->get_lift() - opponent->get_lift();
00581   if (dz < 0)
00582     dz = -dz;
00583           // Close enough to strike?
00584   if (strike_range && dz < 5 && // Same floor?
00585     stiles.enlarge(strike_range).intersects(opptiles))
00586     state = strike;
00587   else if (dz >= 5 ||   // FOR NOW, since is_straight_path()
00588           //   doesn't check z-coord.
00589      !projectile_range ||
00590           // Enlarge to projectile range.
00591      !ptiles.enlarge(projectile_range).intersects(opptiles))
00592     {
00593     state = approach;
00594     approach_foe();   // Get a path.
00595     return;
00596     }
00597   else        // See if we can fire spell/projectile.
00598     {
00599     Game_object *aobj;
00600     bool weapon_dead = false;
00601     if (spellbook)
00602       weapon_dead = !spellbook->can_do_spell(npc);
00603     else if (ammo_shape &&
00604         (!(aobj = npc->get_readied(Actor::ammo)) ||
00605       !In_ammo_family(aobj->get_shapenum(), ammo_shape)))
00606       weapon_dead = true;
00607     if (weapon_dead)
00608       {   // Out of ammo/reagents.
00609       if (npc->get_schedule_type() != Schedule::duel)
00610         { // Look in pack for ammo.
00611         if (spellbook || !npc->ready_ammo())
00612           Swap_weapons(npc);
00613         Combat_schedule::set_weapon();
00614         }
00615       state = approach;
00616       npc->set_target(0);
00617       npc->start(200, 500);
00618       return;
00619       }
00620     Tile_coord pos = npc->get_tile();
00621     Tile_coord opos = opponent->get_tile();
00622     if (opos.tx < pos.tx) // Going left?
00623       pos.tx = npctiles.x;
00624     else      // Right?
00625       opos.tx = opptiles.x;
00626     if (opos.ty < pos.ty) // Going north?
00627       pos.ty = npctiles.y;
00628     else      // South.
00629       opos.ty = opptiles.y;
00630     if (!no_blocking &&
00631         !Fast_pathfinder_client::is_straight_path(pos, opos))
00632       {   // Blocked.  Find another spot.
00633       pos.tx += rand()%7 - 3;
00634       pos.ty += rand()%7 - 3;
00635       npc->walk_to_tile(pos, gwin->get_std_delay(), 0);
00636       state = approach;
00637       npc->set_target(0); // And try another enemy.
00638       return;
00639       }
00640     if (!started_battle)
00641       start_battle(); // Play music if first time.
00642     state = fire;   // Clear to go.
00643     }
00644   if (combat_trace) {
00645     cout << npc->get_name() << " attacks " << opponent->get_name() << endl;
00646   }
00647   int dir = npc->get_direction(opponent);
00648   signed char frames[12];   // Get frames to show.
00649   int cnt = npc->get_attack_frames(weapon_shape, projectile_range > 0,
00650               dir, frames);
00651   if (cnt)
00652     npc->set_action(new Frames_actor_action(frames, cnt));
00653   npc->start();     // Get back into time queue.
00654   int sfx;      // Play sfx.
00655   Game_window *gwin = Game_window::get_instance();
00656   Weapon_info *winf = ShapeID::get_info(weapon_shape).get_weapon_info();
00657   if (winf && (sfx = winf->get_sfx()) >= 0 &&
00658           // But only if Ava. involved.
00659       (npc == gwin->get_main_actor() || 
00660         opponent == gwin->get_main_actor()))
00661     Audio::get_ptr()->play_sound_effect(sfx);
00662   }
00663 
00664 /*
00665  *  Run away.
00666  */
00667 
00668 void Combat_schedule::run_away
00669   (
00670   )
00671   {
00672   Game_window *gwin = Game_window::get_instance();
00673   fleed++;
00674           // Might be nice to run from opp...
00675   int rx = rand();    // Get random position away from here.
00676   int ry = rand();
00677   int dirx = 2*(rx%2) - 1;  // Get 1 or -1.
00678   int diry = 2*(ry%2) - 1;
00679   Tile_coord pos = npc->get_tile();
00680   pos.tx += dirx*(8 + rx%8);
00681   pos.ty += diry*(8 + ry%8);
00682   npc->walk_to_tile(pos, gwin->get_std_delay(), 0);
00683   if (fleed == 1 && !npc->get_flag(Obj_flags::si_tournament) &&
00684           rand()%3 && gwin->add_dirty(npc))
00685     {
00686     yelled++;
00687     if (can_yell)
00688       npc->say(first_flee, last_flee);
00689     }
00690   }
00691 
00692 /*
00693  *  See if a spellbook is readied with a spell
00694  *  available.
00695  *
00696  *  Output: ->spellbook if so, else 0.
00697  */
00698 
00699 Spellbook_object *Combat_schedule::readied_spellbook
00700   (
00701   )
00702   {
00703   Spellbook_object *book = 0;
00704           // Check both hands.
00705   Game_object *obj = npc->get_readied(Actor::lhand);
00706   if (obj && obj->get_info().get_shape_class() == Shape_info::spellbook)
00707     {
00708     book = static_cast<Spellbook_object*> (obj);
00709     if (book->can_do_spell(npc))
00710       return book;
00711     }
00712   obj = npc->get_readied(Actor::rhand);
00713   if (obj && obj->get_info().get_shape_class() == Shape_info::spellbook)
00714     {
00715     book = static_cast<Spellbook_object*> (obj);
00716     if (book->can_do_spell(npc))
00717       return book;
00718     }
00719   return 0;
00720   }
00721 
00722 /*
00723  *  Set weapon 'max_range' and 'ammo'.  Ready a new weapon if needed.
00724  */
00725 
00726 void Combat_schedule::set_weapon
00727   (
00728   )
00729   {
00730   int points;
00731   spellbook = 0;
00732   Weapon_info *info = npc->get_weapon(points, weapon_shape);
00733   if (!info &&      // No weapon?
00734       !(spellbook = readied_spellbook()) && // No spellbook?
00735           // Not dragging?
00736       !gwin->is_dragging() &&
00737           // And not dueling?
00738       npc->get_schedule_type() != Schedule::duel &&
00739       state != wait_return) // And not waiting for boomerang.
00740     {
00741     npc->ready_best_weapon();
00742     info = npc->get_weapon(points, weapon_shape);
00743     }
00744   if (!info)      // Still nothing.
00745     {
00746     projectile_shape = ammo_shape = 0;
00747     projectile_range = 0;
00748     strike_range = 1; // Can always bite.
00749     is_thrown = returns = no_blocking = false;
00750     if (spellbook)    // Did we find a spellbook?
00751       {
00752       projectile_range = 10;  // Guessing.
00753       no_blocking = true;
00754       }
00755     }
00756   else
00757     {
00758     projectile_shape = info->get_projectile();
00759     ammo_shape = info->get_ammo_consumed();
00760     strike_range = info->get_striking_range();
00761     projectile_range = info->get_projectile_range();
00762 
00763     returns = info->returns();
00764     is_thrown = info->is_thrown();
00765     no_blocking = info->no_blocking();
00766     if (ammo_shape)
00767       {
00768       Ammo_info *ainfo = 
00769         ShapeID::get_info(ammo_shape).get_ammo_info();
00770       if (ainfo)
00771         no_blocking = 
00772           no_blocking || ainfo->no_blocking();
00773       }
00774     }
00775   max_range = projectile_range > strike_range ? projectile_range
00776           : strike_range;
00777   if (state == strike || state == fire)
00778     state = approach; // Got to restart attack.
00779   }
00780 
00781 /*
00782  *  See if we need a new opponent.
00783  */
00784 
00785 inline int Need_new_opponent
00786   (
00787   Game_window *gwin,
00788   Actor *npc
00789   )
00790   {
00791   Game_object *opponent = npc->get_target();
00792   Actor *act;
00793           // Nonexistent or dead?
00794   if (!opponent || 
00795       ((act = opponent->as_actor()) != 0 && act->is_dead()) ||
00796           // Or invisible?
00797       opponent->get_flag(Obj_flags::invisible))
00798     return 1;
00799           // See if off screen.
00800   return Off_screen(gwin, opponent) && !Off_screen(gwin, npc);
00801   }
00802 
00803 /*
00804  *  Use one unit of ammo.
00805  *
00806  *  Output: Actual ammo shape.
00807  *    0 if failed.
00808  */
00809 
00810 static int Use_ammo
00811   (
00812   Actor *npc,
00813   int ammo,     // Ammo family shape.
00814   int proj      // Projectile shape.
00815   )
00816   {
00817   Game_object *aobj = npc->get_readied(Actor::ammo);
00818   if (!aobj)
00819     return 0;
00820   int actual_ammo = aobj->get_shapenum();
00821   if (!In_ammo_family(actual_ammo, ammo))
00822     return 0;
00823   npc->remove(aobj);    // Remove all.
00824   int quant = aobj->get_quantity();
00825   aobj->modify_quantity(-1);  // Reduce amount.
00826   if (quant > 1)      // Still some left?  Put back.
00827     npc->add_readied(aobj, Actor::ammo);
00828           // Use actual shape unless a different
00829           //   projectile was specified.
00830   return ammo == proj ? actual_ammo : proj;
00831   }
00832 
00833 
00834 /*
00835  *  Create.
00836  */
00837 
00838 Combat_schedule::Combat_schedule
00839   (
00840   Actor *n, 
00841   Schedule_types 
00842   prev_sched
00843   ) : Schedule(n), state(initial), prev_schedule(prev_sched),
00844     weapon_shape(0),
00845     ammo_shape(0), projectile_shape(0), spellbook(0),
00846     strike_range(0), projectile_range(0), max_range(0),
00847     practice_target(0), is_thrown(false), yelled(0),
00848     no_blocking(false),
00849     started_battle(false), fleed(0), failures(0), teleport_time(0)
00850   {
00851   Combat_schedule::set_weapon();
00852           // Cache some data.
00853   Game_window *gwin = Game_window::get_instance();
00854   Monster_info *minf = npc->get_info().get_monster_info();
00855   can_yell = !minf || !minf->cant_yell();
00856   }
00857 
00858 
00859 /*
00860  *  Previous action is finished.
00861  */
00862 
00863 void Combat_schedule::now_what
00864   (
00865   )
00866   {
00867   Game_window *gwin = Game_window::get_instance();
00868   if (state == initial)   // Do NOTHING in initial state so
00869     {     //   usecode can, e.g., set opponent.
00870           // Way far away (50 tiles)?
00871     if (npc->distance(gwin->get_camera_actor()) > 50)
00872       {
00873       npc->set_dormant();
00874       return;   // Just go dormant.
00875       }
00876     state = approach;
00877     npc->start(200, 200);
00878     return;
00879     }
00880   if (npc->get_flag(Obj_flags::asleep))
00881     {
00882     npc->start(200, 1000);  // Check again in a second.
00883     return;
00884     }
00885           // Running away?
00886   if (npc->get_attack_mode() == Actor::flee)
00887     {     // If not in combat, stop running.
00888     if (fleed > 2 && !gwin->in_combat() && 
00889             npc->get_party_id() >= 0)
00890           // WARNING:  Destroys ourself.
00891       npc->set_schedule_type(Schedule::follow_avatar);
00892     else
00893       run_away();
00894     return;
00895     }
00896           // Check if opponent still breathes.
00897   if (Need_new_opponent(gwin, npc))
00898     {
00899     npc->set_target(0);
00900     state = approach;
00901     }
00902   Game_object *opponent = npc->get_target();
00903           // Flag for slimes:
00904   bool strange = npc->get_info().has_strange_movement() != false;
00905   switch (state)      // Note:  state's action has finished.
00906     {
00907   case approach:
00908     if (opponent)
00909       start_strike();
00910     else
00911       approach_foe();
00912     break;
00913   case strike:      // He hasn't moved away?
00914     state = approach;
00915           // Back into queue.
00916     npc->start(gwin->get_std_delay(), strange 
00917       ? 4*gwin->get_std_delay() : gwin->get_std_delay());
00918     if (npc->get_footprint().enlarge(strike_range).intersects(
00919           opponent->get_footprint()))
00920       {
00921       int dir = npc->get_direction(opponent);
00922       if (!strange) // Avoid messing up slimes.
00923         npc->change_frame(npc->get_dir_framenum(dir,
00924               Actor::standing));
00925           // Glass sword?  Only 1 use.
00926       if (weapon_shape == 604)
00927         {
00928         npc->remove_quantity(1, weapon_shape,
00929             c_any_qual, c_any_framenum);
00930         Combat_schedule::set_weapon();
00931         }
00932           // This may delete us!
00933       Actor *safenpc = npc;
00934       safenpc->set_target(opponent->attacked(npc));
00935           // Strike but once at objects.
00936       Game_object *newtarg = safenpc->get_target();
00937       if (newtarg && !newtarg->as_actor())
00938         safenpc->set_target(0);
00939       return;   // We may no longer exist!
00940       }
00941     break;
00942   case fire:      // Range weapon.
00943     {
00944     failures = 0;
00945     state = approach;
00946           // Save shape (it might change).
00947     int ashape = ammo_shape, wshape = weapon_shape,
00948         pshape = projectile_shape;
00949     int delay = (strange || spellbook) ? 6*gwin->get_std_delay() 
00950         : gwin->get_std_delay();
00951     if (is_thrown)    // Throwing the weapon?
00952       {
00953       if (returns)  // Boomerang?
00954         {
00955         ashape = wshape;
00956         delay = (1 + npc->distance(opponent))*
00957               gwin->get_std_delay();
00958         state = wait_return;
00959         }
00960       if (npc->remove_quantity(1, wshape,
00961           c_any_qual, c_any_framenum) == 0)
00962         {
00963         npc->add_dirty();
00964         ashape = wshape;
00965         Combat_schedule::set_weapon();
00966         }
00967       }
00968     else if (spellbook)
00969       {   // Cast the spell.
00970       ashape = 0; // Just to be on the safe side...
00971       if (!spellbook->do_spell(npc, true))
00972         Combat_schedule::set_weapon();
00973       }
00974     else      // Ammo required?
00975       ashape = ashape ? Use_ammo(npc, ashape, pshape)
00976         : (pshape ? pshape : wshape);
00977     if (ashape > 0)
00978       gwin->get_effects()->add_effect(
00979         new Projectile_effect(npc, opponent,
00980               ashape, wshape));
00981     npc->start(gwin->get_std_delay(), delay);
00982     break;
00983     }
00984   case wait_return:   // Boomerang should have returned.
00985     state = approach;
00986     npc->start(gwin->get_std_delay(), gwin->get_std_delay());
00987     break;
00988   default:
00989     break;
00990     }
00991   if (failures > 5 && npc != gwin->get_camera_actor())
00992   {     // Too many failures.  Give up for now.
00993     if (combat_trace) {
00994       cout << npc->get_name() << " is giving up" << endl;
00995     }
00996     if (npc->get_party_id() >= 0)
00997       {   // Party member.
00998       npc->walk_to_tile(
00999         gwin->get_main_actor()->get_tile(),
01000             gwin->get_std_delay());
01001           // WARNING:  Destroys ourself.
01002       npc->set_schedule_type(Schedule::follow_avatar);
01003       }
01004     else if (!gwin->get_win_rect().intersects(
01005             gwin->get_shape_rect(npc)))
01006       {   // Off screen?  Stop trying.
01007       gwin->get_tqueue()->remove(npc);
01008       npc->set_dormant();
01009       }
01010     else if (npc->get_alignment() == Npc_actor::friendly &&
01011         prev_schedule != Schedule::combat)
01012       {   // Return to normal schedule.
01013       npc->update_schedule(gclock->get_hour()/3, 7);
01014       if (npc->get_schedule_type() == Schedule::combat)
01015         npc->set_schedule_type(prev_schedule);
01016       }
01017     else
01018       {   // Wander randomly.
01019       Tile_coord t = npc->get_tile();
01020       int dist = 2+rand()%3;
01021       int newx = t.tx - dist + rand()%(2*dist);
01022       int newy = t.ty - dist + rand()%(2*dist);
01023           // Wait a bit.
01024       npc->walk_to_tile(newx, newy, t.tz, 
01025           2*gwin->get_std_delay(), rand()%1000);
01026       }
01027     }
01028   }
01029 
01030 /*
01031  *  Npc just went dormant (probably off-screen).
01032  */
01033 
01034 void Combat_schedule::im_dormant
01035   (
01036   )
01037   {
01038   if (npc->get_alignment() == Npc_actor::friendly && 
01039     prev_schedule != npc->get_schedule_type() && npc->is_monster())
01040           // Friendly, so end combat.
01041     npc->set_schedule_type(prev_schedule);
01042   }
01043 
01044 /*
01045  *  Leaving combat.
01046  */
01047 
01048 void Combat_schedule::ending
01049   (
01050   int /* newtype */
01051   )
01052   {
01053   Game_window *gwin = Game_window::get_instance();
01054   if (gwin->get_main_actor() == npc && 
01055           // Not if called from usecode.
01056       !gwin->get_usecode()->in_usecode())
01057     {     // See if being a coward.
01058     find_opponents();
01059     bool found = false; // Find a close-by enemy.
01060     Tile_coord pos = npc->get_tile();
01061     for (Actor_queue::const_iterator it = opponents.begin(); 
01062             it != opponents.end(); ++it)
01063       {
01064       Actor *opp = *it;
01065       Tile_coord opppos = opp->get_tile();
01066       if (opppos.distance(pos) < (300/2)/c_tilesize &&
01067           Fast_pathfinder_client::is_grabable(pos, opppos))
01068         {
01069         found = true;
01070         break;
01071         }
01072       }
01073     if (found)
01074       Audio::get_ptr()->start_music_combat(CSRun_Away,
01075                 false);
01076     }
01077   }
01078 
01079 
01080 /*
01081  *  Create duel schedule.
01082  */
01083 
01084 Duel_schedule::Duel_schedule
01085   (
01086   Actor *n
01087   ) : Combat_schedule(n, duel), start(n->get_tile()),
01088     attacks(0)
01089   {
01090   started_battle = true;    // Avoid playing music.
01091   }
01092 
01093 /*
01094  *  Ready a bow-and-arrows.
01095  */
01096 
01097 void Ready_duel_weapon
01098   (
01099   Actor *npc,
01100   int wshape,     // Weapon shape.
01101   int ashape      // Ammo shape, or -1.
01102   )
01103   {
01104   Game_map *gmap = Game_window::get_instance()->get_map();
01105   Game_object *weap = npc->get_readied(Actor::lhand);
01106   if (!weap || weap->get_shapenum() != wshape)
01107     {     // Need a bow.
01108     Game_object *newweap = 
01109       npc->find_item(wshape, c_any_qual, c_any_framenum);
01110     if (newweap)    // Have it?
01111       newweap->remove_this(1);
01112     else      // Create new one.
01113       newweap = gmap->create_ireg_object(wshape, 0);
01114     if (weap)   // Remove old item.
01115       weap->remove_this(1);
01116     npc->add(newweap, 1); // Should go in correct spot.
01117     if (weap)
01118       npc->add(weap, 1);
01119     }
01120   if (ashape == -1)   // No ammo needed.
01121     return;
01122           // Now provide 1-3 arrows.
01123   Game_object *aobj = npc->get_readied(Actor::ammo);
01124   if (aobj)
01125     aobj->remove_this();  // Toss current ammo.
01126   Game_object *arrows = gmap->create_ireg_object(ashape, 0);
01127   int extra = rand()%3;   // Add 1 or 2.
01128   if (extra)
01129     arrows->modify_quantity(extra);
01130   npc->add(arrows, 1);    // Should go to right spot.
01131   }
01132 
01133 /*
01134  *  Find dueling opponents.
01135  */
01136 
01137 void Duel_schedule::find_opponents
01138   (
01139   )
01140   {
01141   opponents.clear();
01142   attacks = 0;
01143   practice_target = 0;
01144   int r = rand()%3;
01145   if (r == 0)     // First look for practice targets.
01146     {     // Archery target:
01147     practice_target = npc->find_closest(735);
01148     if (practice_target)  // Need bow-and-arrows.
01149       Ready_duel_weapon(npc, 597, 722);
01150     }
01151   if (!practice_target)   // Fencing dummy or dueling opponent.
01152     {
01153     Ready_duel_weapon(npc, 602, -1);
01154     if (r == 1)
01155       practice_target = npc->find_closest(860);
01156     }
01157   Combat_schedule::set_weapon();
01158   if (practice_target)
01159     {
01160     npc->set_target(practice_target);
01161     return;     // Just use that.
01162     }
01163   Actor_vector vec;     // Find all nearby NPC's.
01164   npc->find_nearby_actors(vec, c_any_shapenum, 24);
01165   for (Actor_vector::const_iterator it = vec.begin(); it != vec.end();
01166                    ++it)
01167     {
01168     Actor *opp = *it;
01169     Game_object *oppopp = opp->get_target();
01170     if (opp != npc && opp->get_schedule_type() == duel &&
01171         (!oppopp || oppopp == npc))
01172       if (rand()%2)
01173         opponents.push(opp);
01174       else
01175         opponents.push_front(opp);
01176     }
01177   }
01178 
01179 /*
01180  *  Previous action is finished.
01181  */
01182 
01183 void Duel_schedule::now_what
01184   (
01185   )
01186   {
01187   if (state == strike || state == fire)
01188     {
01189     attacks++;
01190           // Practice target full?
01191     if (practice_target && practice_target->get_shapenum() == 735
01192       && practice_target->get_framenum() > 0 &&
01193         practice_target->get_framenum()%3 == 0)
01194       {
01195       attacks = 0;  // Break off.
01196           //++++++Should walk there.
01197       practice_target->change_frame(0);
01198       }
01199     }
01200   else
01201     {
01202     Combat_schedule::now_what();
01203     return;
01204     }
01205   if (attacks%8 == 0)   // Time to break off.
01206     {
01207     npc->set_target(0);
01208     Tile_coord pos = start;
01209     pos.tx += rand()%24 - 12;
01210     pos.ty += rand()%24 - 12;
01211           // Find a free spot.
01212     Tile_coord dest = Map_chunk::find_spot(pos, 3, npc, 1);
01213     if (dest.tx == -1 || !npc->walk_path_to_tile(dest, 
01214           gwin->get_std_delay(), rand()%2000))
01215           // Failed?  Try again a little later.
01216       npc->start(250, rand()%3000);
01217     }
01218   else
01219     Combat_schedule::now_what();
01220   }
01221 
01222 /*
01223  *  Pause/unpause while in combat.
01224  */
01225 
01226 void Combat::toggle_pause
01227   (
01228   )
01229   {
01230   if (!paused && mode == original)
01231     return;     // Not doing that sort of thing.
01232   if (paused)
01233     {
01234     resume();   // Text is probably for debugging.
01235     eman->center_text("Combat resumed");
01236     }
01237   else
01238     {
01239     gwin->get_tqueue()->pause(SDL_GetTicks());
01240     eman->center_text("Combat paused");
01241     paused = true;
01242     }
01243   }
01244 
01245 /*
01246  *  Resume.
01247  */
01248 
01249 void Combat::resume
01250   (
01251   )
01252   {
01253   if (!paused)
01254     return;
01255   gwin->get_tqueue()->resume(SDL_GetTicks());
01256   paused = false;
01257   }

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