1 /***************************************************************************
2                       battle.cpp  -  Battle turn class
3                              -------------------
4     begin                : Sat May 3 2003
5     copyright            : (C) 2003 by Gabor Torok
6     email                : cctorok@yahoo.com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "common/constants.h"
19 #include "battle.h"
20 #include "render/renderlib.h"
21 #include "rpg/rpglib.h"
22 #include "item.h"
23 #include "projectile.h"
24 #include "creature.h"
25 #include "shapepalette.h"
26 #include "debug.h"
27 #include "sqbinding/sqbinding.h"
28 #include "pathmanager.h"
29 
30 using namespace std;
31 
32 #define MIN_FUMBLE_RANGE 4.0f
33 
34 bool Battle::debugBattle = DEBUG_BATTLE;
35 
36 char *Battle::sound[] = {
37 	"sound/weapon-swish/handheld/sw1.wav",
38 	"sound/weapon-swish/handheld/sw2.wav",
39 	"sound/weapon-swish/handheld/sw3.wav",
40 
41 	"sound/weapon-swish/bows/swb2.wav",
42 	"sound/weapon-swish/bows/swb3.wav",
43 
44 	"sound/potion/pd1.wav"
45 };
46 
47 int Battle::handheldSwishSoundStart = 0;
48 int Battle::handheldSwishSoundCount = 3;
49 int Battle::bowSwishSoundStart = 3;
50 int Battle::bowSwishSoundCount = 2;
51 int Battle::potionSoundStart = 5;
52 int Battle::potionSoundCount = 1;
53 
54 enum {
55 	NO_ACTION = 0,
56 	LOITER,
57 	ATTACK,
58 	MOVE,
59 	NO_TARGET,
60 	WAIT,
61 };
62 
63 /// A no-op turn of battle.
64 
Battle()65 Battle::Battle() {
66 	this->creature = NULL;
67 }
68 
69 /// A Battle is a round of battle between 'creature' and 'creature->getTargetCreature()'
70 
Battle(Session * session,Creature * creature)71 Battle::Battle( Session *session, Creature *creature ) {
72 	this->session = session;
73 	this->creature = creature;
74 	this->item = NULL;
75 	this->creatureInitiative = 0;
76 	this->initiativeCheck = false;
77 	this->speed = 0;
78 	this->dist = creature->getDistanceToTarget();
79 	this->spell = NULL;
80 	this->empty = false;
81 
82 	this->needsReset = true;
83 	this->nextTurn = 0;
84 	this->weaponWait = 0;
85 	this->startingAp = this->ap = 0;
86 	if ( debugBattle ) cerr << "*** constructor, creature=" << creature->getName() << endl;
87 }
88 
~Battle()89 Battle::~Battle() {
90 }
91 
reset(bool keepPaused,bool keepAP)92 void Battle::reset( bool keepPaused, bool keepAP ) {
93 	if ( debugBattle ) cerr << "*** reset: creature=" << creature->getName() << endl;
94 	this->steps = 0;
95 	if ( !keepAP ) this->startingAp = this->ap = toint( creature->getMaxAP() );
96 	this->projectileHit = false;
97 	if ( !keepPaused ) this->paused = false;
98 	this->weaponWait = 0;
99 	this->range = 0.0f;
100 	creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_STAND ) );
101 	( ( AnimatedShape* )( creature->getShape() ) )->setAttackEffect( false );
102 }
103 
104 /// This method sets up and creates battle turns (Battle objects) in order of initiative.
105 
setupBattles(Session * session,Battle * battle[],int count,vector<Battle * > * turns)106 void Battle::setupBattles( Session *session, Battle *battle[], int count, vector<Battle *> *turns ) {
107 	// for now put all battles into the vector
108 	// need to order by initiative
109 	for ( int i = 0; i < count; i++ ) {
110 		// reset for the first time
111 		// (to avoid whack skill numbers for un-initialized creatures.)
112 		if ( battle[i]->needsReset ) {
113 			if ( debugBattle ) cerr << "*** Reset 1" << endl;
114 			battle[i]->reset();
115 			battle[i]->needsReset = false;
116 		}
117 		bool handled = false;
118 		for ( vector<Battle*>::iterator e = turns->begin(); e != turns->end(); ++e ) {
119 			Battle *b = *e;
120 			if ( b->getCreature()->getInitiative() > battle[i]->getCreature()->getInitiative() ) {
121 				turns->insert( e, battle[i] );
122 				handled = true;
123 				break;
124 			}
125 		}
126 		if ( !handled ) turns->push_back( battle[i] );
127 	}
128 
129 	/*
130 	cerr << "Battle order:" << endl;
131 	for( vector<Battle*>::iterator e = turns->begin(); e != turns->end(); ++e ) {
132 	  Battle *b = *e;
133 	  cerr << "\t" << b->getCreature()->getName() <<
134 	    " INIT:" << b->getCreature()->getInitiative() <<
135 	    " speed:" << b->getCreature()->getSkill( Constants::SPEED ) <<
136 	    endl;
137 	}
138 	cerr << "-----------------------------------" << endl;
139 	*/
140 }
141 
waitingOnAnimation(Creature * creature)142 bool Battle::waitingOnAnimation( Creature *creature ) {
143 	if( creature ) {
144 		// in TB battle, wait for the animations to end before ending turn
145 		if ( session->getPreferences()->isBattleTurnBased() ) {
146 			int a = ( ( AnimatedShape* )( creature->getShape() ) )->getCurrentAnimation();
147 			if ( !( a == MD2_STAND || a == MD2_RUN ) ) {
148 				return true;
149 			}
150 		} else {
151 			int a = ( ( AnimatedShape* )( creature->getShape() ) )->getCurrentAnimation();
152 			if ( !( a == MD2_STAND || a == MD2_RUN || a == MD2_PAIN1 || a == MD2_PAIN2 || a == MD2_PAIN3 ) ) {
153 				return true;
154 			}
155 		}
156 	}
157 	return false;
158 }
159 
160 /// Executes a creature's battle turn.
161 
fightTurn()162 bool Battle::fightTurn() {
163 	if ( debugBattle ) cerr << "TURN: creature=" << creature->getName() <<
164 		" ap=" << ap <<
165 		" wait=" << weaponWait <<
166 		" nextTurn=" << nextTurn <<
167 		" hasTarget=" << creature->hasTarget() <<
168 		" validTarget=" << creature->isTargetValid() <<
169 		" action=" << creature->getAction() <<
170 		" motion=" << creature->getMotion() <<
171 		" animation=" << ( ( AnimatedShape* )creature->getShape() )->getCurrentAnimation() <<
172 		" attacking=" << ( ( AnimatedShape* )creature->getShape() )->getAttackEffect() << endl;
173 
174 	// are we alive?
175 	if ( !creature || creature->getStateMod( StateMod::dead ) ||
176 	        ( creature->getAction() == Constants::ACTION_NO_ACTION &&
177 	          !creature->isAutoControlled() && !getAvailablePartyTarget() ) ) {
178 		if ( debugBattle ) cerr << "*** Reset 2" << endl;
179 		reset();
180 		return true;
181 	}
182 
183 	// done with this creature's turn
184 	if ( ap <= 0 ) {
185 		if ( weaponWait > 0 ) {
186 			nextTurn = weaponWait;
187 			if ( debugBattle ) cerr << "Carries over into next turn." << endl;
188 		}
189 		// in TB battle, wait for the animations to end before ending turn
190 		if( waitingOnAnimation( creature ) || waitingOnAnimation( creature->getTargetCreature() ) ) {
191 			return false;
192 		}
193 		if ( debugBattle ) cerr << "*** Reset 3" << endl;
194 		reset();
195 		return true;
196 	}
197 
198 	// wait for projectile to finish
199 	if ( creature->getProjectiles() &&
200 	        !creature->getProjectiles()->empty() ) return false;
201 
202 	// wait for the animation to finish
203 	if ( waitingOnAnimation( creature ) ) return false;
204 
205 
206 	// waiting to cast a spell?
207 	if ( creature->getAction() == Constants::ACTION_CAST_SPELL ||
208 	        creature->getAction() == Constants::ACTION_SPECIAL ) {
209 		creature->startEffect( Constants::EFFECT_CAST_SPELL,
210 		                       Constants::DAMAGE_DURATION * 4 );
211 	}
212 
213 	if ( pauseBeforePlayerTurn() ) return false;
214 
215 	// in TB combat, light the nearest player
216 	if ( creature->isAutoControlled() && session->getPreferences()->isBattleTurnBased() ) {
217 		Creature *p = session->getParty()->getClosestPlayer( toint( creature->getX() ),
218 		              toint( creature->getY() ),
219 		              creature->getShape()->getWidth(),
220 		              creature->getShape()->getDepth(),
221 		              CREATURE_SIGHT_RADIUS );
222 		if ( p ) {
223 			for ( int i = 0; i < session->getParty()->getPartySize(); i++ ) {
224 				if ( p == session->getParty()->getParty( i ) &&
225 				        p != session->getParty()->getPlayer() ) {
226 					session->getParty()->setPlayer( i );
227 				}
228 			}
229 		}
230 	}
231 
232 	initTurnStep( true );
233 
234 	if ( creature->getAction() == Constants::ACTION_EAT_DRINK ) {
235 		executeEatDrinkAction();
236 	}
237 
238 	if ( creature->hasTarget() &&
239 	        creature->isTargetValid() ) {
240 		if ( dist < range ) {
241 			executeAction();
242 		} else {
243 			stepCloserToTarget();
244 		}
245 	} else {
246 		moveCreature();
247 	}
248 
249 
250 	if( creature->isAutoControlled() ) {
251 		creature->decideAction();
252 	}
253 
254 
255 	// not done yet with creature's turn
256 	return false;
257 }
258 
259 /// Swaps the turn between monsters and the party.
260 
endTurn()261 void Battle::endTurn() {
262 	ap = 0;
263 	session->getParty()->toggleRound( false );
264 	//paused = false;
265 }
266 
267 /// Handles the pre-turn pause in turn based combat.
268 
pauseBeforePlayerTurn()269 bool Battle::pauseBeforePlayerTurn() {
270 	// go to single-player mode
271 	if ( session->getPreferences()->isBattleTurnBased() &&
272 	        !session->getParty()->isPlayerOnly() ) {
273 		session->getParty()->togglePlayerOnly( true );
274 	}
275 
276 	// pause if this is a player's first step
277 	if ( !steps &&
278 	        !paused &&
279 	        session->getPreferences()->isBattleTurnBased() ) {
280 		if ( !creature->isAutoControlled() ) {
281 			if ( debugBattle ) cerr << "Pausing for round start. Turn: " << creature->getName() << endl;
282 
283 			// center on player
284 			for ( int i = 0; i < session->getParty()->getPartySize(); i++ ) {
285 				if ( session->getParty()->getParty( i ) == creature &&
286 				        !creature->getStateMod( StateMod::possessed ) ) {
287 					session->getParty()->setPlayer( i );
288 					break;
289 				}
290 			}
291 			// FIXME: only center if not on-screen
292 			session->getMap()->refresh();
293 			session->getMap()->center( toint( creature->getX() ), toint( creature->getY() ), true );
294 
295 			// pause the game
296 			if ( getAvailablePartyTarget() ) {
297 				session->getParty()->toggleRound( true );
298 				paused = true;
299 				return true;
300 			}
301 		} else {
302 			// FIXME: only center if not on-screen
303 			session->getMap()->refresh();
304 			session->getMap()->center( toint( creature->getX() ), toint( creature->getY() ), true );
305 		}
306 	}
307 	steps++;
308 	paused = false;
309 	return false;
310 }
311 
312 /// Calculates the current creature's radius of action.
313 
calculateRange(Item * item)314 int Battle::calculateRange( Item *item ) {
315 	int range;
316 	if ( creature->getActionSkill() ) {
317 		//range = MIN_DISTANCE;
318 		range = creature->getActionSkill()->getDistance();
319 	} else if ( creature->getActionSpell() ) {
320 		//range = MIN_DISTANCE;
321 		range = creature->getActionSpell()->getDistance();
322 	} else {
323 		range = static_cast<int>( MIN_DISTANCE );
324 		if ( item ) range = item->getRange();
325 	}
326 	return range;
327 }
328 
getAdjustedWait(int originalWait)329 int Battle::getAdjustedWait( int originalWait ) {
330 	if( creature->isPartyMember() && SQUIRREL_ENABLED ) {
331 		getSession()->getSquirrel()->setGlobalVariable( "turnWait", originalWait );
332 		if ( creature->getActionSkill() ) {
333 			getSession()->getSquirrel()->callSkillEvent( creature,
334 			    creature->getActionSkill()->getName(),
335 			    "waitHandlerSkill" );
336 		} else if ( creature->getActionSpell() ) {
337 			getSession()->getSquirrel()->callSpellEvent( creature,
338 			    creature->getActionSpell(),
339 			    "waitHandlerSpell" );
340 		} else {
341 			getSession()->getSquirrel()->callItemEvent( creature,
342 			    item,
343 			    "waitHandlerItem" );
344 		}
345 		int newWait = static_cast<int>( getSession()->getSquirrel()->getGlobalVariable( "turnWait" ) );
346 		//if( originalWait != newWait ) {
347 		//cerr << "turnWait was " << originalWait << " and now is " << newWait << endl;
348 		//}
349 		return newWait;
350 	} else {
351 		return originalWait;
352 	}
353 }
354 
355 /// Initializes a creature's battle turn.
356 
initTurnStep(bool callScript)357 void Battle::initTurnStep( bool callScript ) {
358 	dist = creature->getDistanceToTarget();
359 
360 	// select the best weapon only once
361 	if ( weaponWait <= 0 ) {
362 		if ( debugBattle ) cerr << "*** initTurnStep, creature=" << creature->getName() << " wait=" << weaponWait << " nextTurn=" << nextTurn << endl;
363 
364 		if ( creature->getActionSkill() ) {
365 			range = calculateRange();
366 			if ( nextTurn > 0 ) weaponWait = nextTurn;
367 			else weaponWait = getAdjustedWait( creature->getActionSkill()->getSpeed() );
368 			nextTurn = 0;
369 			if ( debugBattle ) cerr << "\tUsing capability: " << creature->getActionSkill()->getDisplayName() << endl;
370 		} else if ( creature->getActionSpell() ) {
371 			range = calculateRange();
372 			if ( nextTurn > 0 ) weaponWait = nextTurn;
373 			else weaponWait = getAdjustedWait( creature->getActionSpell()->getSpeed() );
374 			nextTurn = 0;
375 			if ( debugBattle ) cerr << "\tUsing spell: " << creature->getActionSpell()->getDisplayName() << endl;
376 		} else {
377 			item = creature->getBestWeapon( dist, callScript );
378 			range = calculateRange( item );
379 			if ( item ) {
380 				if ( debugBattle ) cerr << "\tUsing item: " << item->getRpgItem()->getName() << " ap=" << ap << endl;
381 			} else {
382 				if ( debugBattle ) cerr << "\tUsing bare hands." << endl;
383 			}
384 			// How many steps to wait before being able to use the weapon.
385 			weaponWait = getAdjustedWait( toint( creature->getWeaponAPCost( item ) ) );
386 			// Make turn-based mode a little snappier
387 			//if( session->getPreferences()->isBattleTurnBased() ) {
388 			//  weaponWait /= 2;
389 			//}
390 		}
391 		if ( nextTurn > 0 ) weaponWait = nextTurn;
392 		nextTurn = 0;
393 		if ( debugBattle ) cerr << "\tname=" << creature->getName() << " Distance=" << dist << " range=" << range << " wait=" << weaponWait << endl;
394 
395 	}
396 }
397 
398 /// Simple action script for auto-controlled creatures.
399 
executeAction()400 void Battle::executeAction() {
401 	ap--;
402 	if ( weaponWait > 0 ) {
403 		weaponWait--;
404 		if ( weaponWait > 0 ) return;
405 	}
406 
407 	// don't carry to next turn
408 	nextTurn = 0;
409 
410 	// attack
411 	if ( debugBattle ) cerr << "\t\t *** Attacking." << endl;
412 	if ( creature->getActionSkill() ) {
413 		useSkill();
414 	} else if ( creature->getActionSpell() ) {
415 		// casting a spell for the first time
416 		castSpell();
417 	} else if ( item && item->getRpgItem()->isRangedWeapon() ) {
418 		launchProjectile();
419 	} else {
420 		hitWithItem();
421 	}
422 	creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_ATTACK ), true );
423 	( ( AnimatedShape* )( creature->getShape() ) )->setAngle( creature->getTargetAngle() );
424 
425 	// fight back!
426 	if( !session->getPreferences()->isBattleTurnBased() && creature->getTargetCreature() ) {
427 		if( !( creature->getTargetCreature()->anyMovesLeft() || creature->getTargetCreature()->isBusy() ) ) {
428 			creature->getTargetCreature()->setTargetCreature( creature );
429 		}
430 	}
431 
432 	// pause after each hit
433 	steps = 0;
434 }
435 
436 /// Handles moving towards a target creature.
437 
stepCloserToTarget()438 void Battle::stepCloserToTarget() {
439 	// out of range: take 1 step closer
440 
441 	// re-select the best weapon
442 	weaponWait = nextTurn = 0;
443 
444 	// Set the movement mode; otherwise character won't move
445 	creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_RUN ) );
446 
447 	if ( debugBattle ) cerr << "\t\tTaking a step." << endl;
448 	if ( creature->getTargetCreature() ) {
449 		if ( debugBattle ) cerr << "\t\t\tto target creature: " << creature->getTargetCreature()->getName() << endl;
450 
451 		// has the target creature moved?
452 		int tx = toint( creature->getTargetCreature()->getX() );
453 		int tw = creature->getTargetCreature()->getShape()->getWidth();
454 		int ty = toint( creature->getTargetCreature()->getY() );
455 		int th = creature->getTargetCreature()->getShape()->getDepth();
456 		if ( !( creature->getSelX() >= tx && creature->getSelX() < tx + tw &&
457 		        creature->getSelY() <= ty && creature->getSelY() > ty - th ) ) {
458 			/*
459 			    int tx = toint(creature->getTargetCreature()->getX() +
460 			                   creature->getTargetCreature()->getShape()->getWidth() / 2);
461 			    int ty = toint(creature->getTargetCreature()->getY() -
462 			                   creature->getTargetCreature()->getShape()->getDepth() / 2);
463 			    if(!(creature->getSelX() == tx && creature->getSelY() == ty)) {
464 			*/
465 
466 			// Try to move to the target creature.
467 			// For monsters, if this is not possible, select a new target.
468 			if ( !creature->setSelCreature( creature->getTargetCreature(), range, false ) && creature->isAutoControlled() ) {
469 				creature->cancelTarget();
470 				//creature->decideAction();
471 				ap--;
472 				if ( debugBattle )
473 					cerr << "*** " << creature->getName() <<
474 					" selecting new target" << endl;
475 				return;
476 			}
477 		}
478 	} else if ( !( creature->getSelX() == creature->getTargetX() &&
479 	               creature->getSelY() == creature->getTargetY() ) ) {
480 		if ( debugBattle ) cerr << "\t\t\tto target location: " << creature->getTargetX() <<
481 			creature->getTargetY() << endl;
482 		// if we are here, it means:
483 		//  a) we have no target creature
484 		//  b) we are going somewhere
485 		//  c) the place we are going is not showing a "Selected" symbol
486 
487 		// this can happen when casting Dori's Tumblers in a battle with no creatures
488 	}
489 
490 	// wait for animation to end
491 	if ( waitingOnAnimation( creature ) ) {
492 		if ( debugBattle ) cerr << "\t\t\tWaiting for animation to end." << endl;
493 		return;
494 	}
495 
496 	GLfloat oldX = creature->getX();
497 	GLfloat oldY = creature->getY();
498 	bool moved = ( creature->moveToLocator() == NULL );
499 	if ( !( toint( oldX ) == toint( creature->getX() ) &&
500 	        toint( oldY ) == toint( creature->getY() ) ) ) {
501 		ap--;
502 	}
503 	//if( !moved ) moveCreature();
504 	if ( !moved ) {
505 		if ( debugBattle ) {
506 			cerr << "*** Warning: not moving closer to target and not in range. " <<
507 			" x,y=" << creature->getX() << "," << creature->getY() <<
508 			" selX,selY=" << creature->getSelX() << "," << creature->getSelY() <<
509 			" dist=" << dist << " range=" << range <<
510 			" item=" << ( item ? item->getRpgItem()->getName() : "none" ) <<
511 			" creature=" << creature->getName() <<
512 			" target=" << ( creature->getTargetCreature() ?
513 			                creature->getTargetCreature()->getName() :
514 			                ( creature->getTargetItem() ? creature->getTargetItem()->getRpgItem()->getName() :
515 			                  ( creature->getTargetX() > -1 ? "location" : "no-target" ) ) ) << endl;
516 			if ( creature->getTargetCreature() ) {
517 				cerr << "Target creature=" << creature->getTargetCreature()->getX() <<
518 				"," << creature->getTargetCreature()->getY() << endl;
519 			}
520 		}
521 
522 		// guess a new path (once in a while)
523 		if ( 1 == Util::dice( 4 ) ) {
524 			if ( creature->getTargetCreature() ) //new path to the creature, if we have one targetted
525 				creature->setSelCreature( creature->getTargetCreature(), range,  false );
526 			else //new path to our selected location
527 				creature->setSelXY( creature->getSelX(), creature->getSelY(), false );
528 		}
529 
530 		if ( creature->isAutoControlled() ) {
531 			ap--;
532 		} else {
533 			if ( session->getPreferences()->isBattleTurnBased() ) {
534 				if ( getAvailablePartyTarget() ) session->getParty()->toggleRound( true );
535 			} else {
536 				ap--;
537 			}
538 		}
539 	}
540 }
541 
542 /// Moves a creature one step towards its target. Also handles loss of target.
543 
moveCreature()544 bool Battle::moveCreature() {
545 
546 	// Set the movement mode; otherwise character won't move
547 	if ( creature->anyMovesLeft() ) {
548 		creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_RUN ) );
549 	} else {
550 		creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_STAND ) );
551 	}
552 
553 	// take 1 step closer
554 	if ( debugBattle ) cerr << "\t\tTaking a non-battle step." << endl;
555 
556 	GLfloat oldX = creature->getX();
557 	GLfloat oldY = creature->getY();
558 	if ( creature->isAutoControlled() ) {
559 		session->getGameAdapter()->moveCreature( creature );
560 		if ( !( toint( oldX ) == toint( creature->getX() ) &&
561 		        toint( oldY ) == toint( creature->getY() ) ) ) {
562 			ap--;
563 		}
564 
565 		if ( oldX == creature->getX() &&
566 		        oldY == creature->getY() ) {
567 			if ( debugBattle ) cerr << "*** WARNING: monster not moving." << endl;
568 			ap--;
569 		}
570 		return false;
571 	} else {
572 
573 		// in RT mode if the player has no target, make party follow player
574 		// (ie. they are running away)
575 		bool playerHasTarget =
576 		  ( session->getPreferences()->isBattleTurnBased() ||
577 		    ( session->getParty()->getPlayer()->hasTarget() &&
578 		    session->getParty()->getPlayer()->isTargetValid() ) ? true : false );
579 		if ( playerHasTarget ) {
580 
581 			// someone killed our target, try to pick another one
582 			if ( creature->hasTarget() && !creature->isTargetValid() ) {
583 				//cerr << "*** Character has invalid target, selecting new one." << endl;
584 				if ( selectNewTarget() ) return true;
585 			}
586 
587 			// wait for animation to end
588 			if ( waitingOnAnimation( creature ) ) return false;
589 
590 			if ( creature->getSelX() != -1 ) {
591 				bool moved = ( creature->moveToLocator() == NULL );
592 				if ( !( toint( oldX ) == toint( creature->getX() ) &&
593 				        toint( oldY ) == toint( creature->getY() ) ) ) {
594 					ap--;
595 				}
596 
597 				if ( !moved ) {
598 					if ( debugBattle ) {
599 						cerr << "*** WARNING: character not moving." <<
600 						" x,y=" << creature->getX() << "," << creature->getY() <<
601 						" selX,selY=" << creature->getSelX() << "," << creature->getSelY() <<
602 						" dist=" << dist << " range=" << range <<
603 						" item=" << ( item ? item->getRpgItem()->getName() : "none" ) <<
604 						" creature=" << creature->getName() <<
605 						" target=" << ( creature->getTargetCreature() ?
606 						                creature->getTargetCreature()->getName() :
607 						                ( creature->getTargetItem() ? creature->getTargetItem()->getRpgItem()->getName() :
608 						                  ( creature->getTargetX() > -1 ? "location" : "no-target" ) ) ) << endl;
609 						if ( creature->getTargetCreature() ) {
610 							cerr << "Target creature=" << creature->getTargetCreature()->getX() <<
611 							"," << creature->getTargetCreature()->getY() << endl;
612 						}
613 					}
614 
615 					// guess a new path
616 					if ( 1 == Util::dice( 4 ) ) {
617 						if ( creature->getTargetCreature() ) //new path to the creature, if we have one targetted
618 							creature->setSelCreature( creature->getTargetCreature(), range, false );
619 						else //new path to our selected location
620 							creature->setSelXY( creature->getSelX(), creature->getSelY(), false );
621 					}
622 					if ( session->getPreferences()->isBattleTurnBased() ) {
623 						if ( getAvailablePartyTarget() ) session->getParty()->toggleRound( true );
624 					} else {
625 						// is the below line needed?
626 						ap--;
627 					}
628 				}
629 			} else {
630 				//cerr << "*** Character has no location, selecting new target." << endl;
631 				return selectNewTarget();
632 			}
633 		} else {
634 			// don't select a new target
635 			// cerr << "*** Character just moving after player." << endl;
636 			creature->follow( session->getParty()->getPlayer() );
637 			// actually take a step
638 			creature->moveToLocator();
639 			if ( creature == session->getParty()->getPlayer() ) {
640 				session->getMap()->center( toint( creature->getX() ),
641 				                           toint( creature->getY() ) );
642 			}
643 		}
644 	}
645 	return false;
646 }
647 
648 /// Returns a hostile target near the party.
649 
getAvailablePartyTarget()650 Creature *Battle::getAvailablePartyTarget() {
651 	for ( int i = 0; i < session->getParty()->getPartySize(); i++ ) {
652 		bool visible = ( session->getMap()->isLocationVisible( toint( session->getParty()->getParty( i )->getX() ),
653 		                 toint( session->getParty()->getParty( i )->getY() ) ) &&
654 		                 session->getMap()->isLocationInLight( toint( session->getParty()->getParty( i )->getX() ),
655 		                     toint( session->getParty()->getParty( i )->getY() ),
656 		                     session->getParty()->getParty( i )->getShape() ) );
657 		if ( visible && !session->getParty()->getParty( i )->getStateMod( StateMod::dead ) ) {
658 			Creature *c = session->getParty()->getParty( i )->getBattle()->getAvailableTarget();
659 			if ( c ) return c;
660 		}
661 	}
662 	return NULL;
663 }
664 
665 /// Returns the closest hostile target of a character.
666 
getAvailableTarget()667 Creature *Battle::getAvailableTarget() {
668 	if ( creature->isMonster() ) {
669 		cerr << "*** Error: Battle::getAvailableTarget() should only be called for characters!" << endl;
670 		return NULL;
671 	}
672 	Creature *target;
673 	if ( creature->getStateMod( StateMod::possessed ) ) {
674 		target = session->getParty()->getClosestPlayer( toint( creature->getX() ),
675 		         toint( creature->getY() ),
676 		         creature->getShape()->getWidth(),
677 		         creature->getShape()->getDepth(),
678 		         CREATURE_SIGHT_RADIUS );
679 	} else {
680 		target = session->getClosestMonster( toint( creature->getX() ),
681 		         toint( creature->getY() ),
682 		         creature->getShape()->getWidth(),
683 		         creature->getShape()->getDepth(),
684 		         CREATURE_SIGHT_RADIUS );
685 	}
686 	return target;
687 }
688 
689 /// Selects a new target for player characters.
690 
selectNewTarget()691 bool Battle::selectNewTarget() {
692 	// select a new target
693 	if ( creature->isAutoControlled() ) {
694 		cerr << "*** Error Battle::selectNewTarget should not be called for monsters." << endl;
695 		return false;
696 	} else {
697 		// select a new target
698 		Creature *target = getAvailableTarget();
699 		if ( target ) {
700 			if ( debugBattle ) cerr << "\tSelected new target: " << target->getName() << ", with range " << range << endl;
701 			creature->setTargetCreature( target, true, range );
702 		} else {
703 			creature->setTargetCreature( NULL );
704 			if ( debugBattle ) cerr << "\t\tCan't find new target." << endl;
705 		}
706 
707 		// let the player override in TB mode
708 		if ( target && session->getPreferences()->isBattleTurnBased() ) {
709 			if ( getAvailablePartyTarget() ) session->getParty()->toggleRound( true );
710 		}
711 
712 		return ( target ? true : false );
713 	}
714 }
715 
716 /// Makes a creature use a special capability.
717 
useSkill()718 void Battle::useSkill() {
719 	snprintf( message, MESSAGE_SIZE, _( "%1$s uses capability %2$s!" ),
720 	          creature->getName(),
721 	          creature->getActionSkill()->getDisplayName() );
722 	if ( creature->getCharacter() ) {
723 		session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERMAGIC );
724 	} else {
725 		session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCMAGIC );
726 	}
727 	( ( AnimatedShape* )( creature->getShape() ) )->setAttackEffect( true );
728 
729 	char *err =
730 	  creature->useSpecialSkill( creature->getActionSkill(),
731 	                             true );
732 	if ( err ) {
733 		session->getGameAdapter()->writeLogMessage( err, Constants::MSGTYPE_FAILURE );
734 		//showMessageDialog( err );
735 	}
736 
737 	// cancel action
738 	creature->cancelTarget();
739 	// also cancel path
740 	if ( !creature->isAutoControlled() ) creature->setSelXY( -1, -1 );
741 }
742 
743 /// Spell casting (using a known spell, item or scroll).
744 
castSpell(bool alwaysSucceeds)745 void Battle::castSpell( bool alwaysSucceeds ) {
746 	int casterLevel;
747 	// use up some MP (not when using a scroll)
748 	if ( !creature->getActionItem() ) {
749 		int n = creature->getMp() - creature->getActionSpell()->getMp();
750 		if ( n < 0 ) n = 0;
751 		creature->setMp( n );
752 		casterLevel = creature->getLevel();
753 	} else {
754 		casterLevel = creature->getActionItem()->getLevel();
755 		// try to destroy the scroll or use up a charge
756 		int itemIndex = creature->findInBackpack( creature->getActionItem() );
757 		if ( itemIndex > -1 ) {
758 			if ( creature->getActionItem()->getRpgItem()->getMaxCharges() > 0 ) {
759 				if ( creature->getActionItem()->getCurrentCharges() <= 0 ) {
760 					snprintf( message, MESSAGE_SIZE, _( "Your %s is out of charges." ), creature->getActionItem()->getItemName() );
761 					session->getGameAdapter()->writeLogMessage( message );
762 					creature->cancelTarget();
763 					// also cancel path
764 					if ( !creature->isAutoControlled() ) creature->setSelXY( -1, -1 );
765 					return;
766 				} else {
767 					creature->getActionItem()->setCurrentCharges(
768 					  creature->getActionItem()->getCurrentCharges() - 1 );
769 					snprintf( message, MESSAGE_SIZE, _( "Your %s feels lighter." ), creature->getActionItem()->getItemName() );
770 					session->getGameAdapter()->writeLogMessage( message );
771 				}
772 			} else {
773 				creature->removeFromBackpack( itemIndex );
774 				snprintf( message, MESSAGE_SIZE, _( "%s crumbles into dust." ), creature->getActionItem()->getItemName() );
775 				session->getGameAdapter()->writeLogMessage( message );
776 			}
777 			if ( !session->getGameAdapter()->isHeadless() )
778 				session->getGameAdapter()->refreshBackpackUI();
779 		} else {
780 			// scroll was removed from backpack before casting
781 			snprintf( message, MESSAGE_SIZE, _( "Couldn't find scroll, cancelled spell." ) );
782 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
783 			creature->cancelTarget();
784 			// also cancel path
785 			if ( !creature->isAutoControlled() ) creature->setSelXY( -1, -1 );
786 			return;
787 		}
788 	}
789 
790 	snprintf( message, MESSAGE_SIZE, _( "%1$s casts %2$s!" ),
791 	          creature->getName(),
792 	          creature->getActionSpell()->getDisplayName() );
793 	if ( creature->getCharacter() ) {
794 		session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERMAGIC );
795 	} else {
796 		session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCMAGIC );
797 	}
798 	( ( AnimatedShape* )( creature->getShape() ) )->setAttackEffect( true );
799 
800 	// spell succeeds?
801 	// FIXME: use stats like IQ here to modify spell success rate...
802 	SpellCaster *sc = new SpellCaster( this, creature->getActionSpell(), false, casterLevel );
803 
804 	/*
805 	  apply state_mods:
806 	  blessed,
807 	 empowered,
808 	 enraged,
809 	 ac_protected,
810 	 magic_protected,
811 
812 	 drunk,
813 
814 	 poisoned,
815 	 cursed,
816 	 possessed,
817 	 blinded,
818 	 charmed,
819 	 changed,
820 	 overloaded,
821 	*/
822 	float delta = 0.0f;
823 	if ( creature->getStateMod( StateMod::blessed ) ) {
824 		delta += Util::roll( 0.0f, 10.0f );
825 	}
826 	if ( creature->getStateMod( StateMod::empowered ) ) {
827 		delta += Util::roll( 5.0f, 15.0f );
828 	}
829 	if ( creature->getStateMod( StateMod::enraged ) ) {
830 		delta -= Util::roll( 0.0f, 10.0f );
831 	}
832 	if ( creature->getStateMod( StateMod::drunk ) ) {
833 		delta += Util::roll( -7.0f, 7.0f );
834 	}
835 	if ( creature->getStateMod( StateMod::cursed ) ) {
836 		delta += Util::roll( 7.0f, 15.0f );
837 	}
838 	if ( creature->getStateMod( StateMod::blinded ) ) {
839 		delta -= Util::roll( 0.0f, 10.0f );
840 	}
841 	if ( creature->getStateMod( StateMod::overloaded ) ) {
842 		delta -= Util::roll( 0.0f, 8.0f );
843 	}
844 	if ( creature->getStateMod( StateMod::invisible ) ) {
845 		delta += Util::roll( 5.0f, 10.0f );
846 	}
847 
848 	// Like with max cth, max skill is closer to the skill to avoid a lot of misses
849 	int skill = creature->getSkill( creature->getActionSpell()->getSchool()->getSkill() );
850 	int maxSkill = skill * 2;
851 	if ( maxSkill > 100 ) maxSkill = 100;
852 	if ( maxSkill < 40 ) maxSkill = 40;
853 
854 	bool failed = false;
855 	if ( !alwaysSucceeds && !projectileHit ) {
856 		if ( Util::dice( maxSkill ) > skill + delta ) {
857 			snprintf( message, MESSAGE_SIZE, _( "...%s needs more practice." ), creature->getName() );
858 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
859 			failed = true;
860 		} else if ( Util::dice( 100 ) + delta < creature->getActionSpell()->getFailureRate() ) {
861 			session->getGameAdapter()->writeLogMessage( _( "...the magic fails inexplicably!" ), Constants::MSGTYPE_FAILURE );
862 			failed = true;
863 		}
864 	}
865 
866 	if ( failed ) {
867 		sc->spellFailed();
868 	} else {
869 
870 		// get exp for casting the spell
871 		if ( !creature->isAutoControlled() ) {
872 			creature->addExperienceWithMessage( creature->getActionSpell()->getExp() );
873 		}
874 
875 		sc->spellSucceeded();
876 	}
877 	delete sc;
878 
879 	// cancel action
880 	creature->cancelTarget();
881 	// also cancel path
882 	if ( !creature->isAutoControlled() ) creature->setSelXY( -1, -1 );
883 
884 }
885 
886 /// Makes a creature fire a projectile.
887 
launchProjectile()888 void Battle::launchProjectile() {
889 	if ( !Projectile::addProjectile( creature, creature->getTargetCreature(), item,
890 	        new ShapeProjectileRenderer( session->getShapePalette()->findShapeByName( "ARROW" ) ),
891 	        creature->getMaxProjectileCount( item ) ) ) {
892 		snprintf( message, MESSAGE_SIZE, _( "...%s has finished firing a volley" ), creature->getName() );
893 		if ( creature->getCharacter() ) {
894 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
895 		} else {
896 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
897 		}
898 	} else {
899 		snprintf( message, MESSAGE_SIZE, _( "...%s shoots a projectile" ), creature->getName() );
900 		if ( creature->getCharacter() ) {
901 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
902 		} else {
903 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
904 		}
905 	}
906 
907 	if ( creature->isMonster() &&
908 	        0 == Util::dice( session->getPreferences()->getSoundFreq() ) ) {
909 		int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
910 		session->playSound( creature->getMonster()->getRandomSound( Constants::SOUND_TYPE_ATTACK ), panning );
911 	}
912 
913 	int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
914 	session->playSound( getRandomSound( bowSwishSoundStart, bowSwishSoundCount ), panning );
915 }
916 
917 /// Call these when a projectile weapon finally hits. It sets up a turn and plays it.
918 
projectileHitTurn(Session * session,Projectile * proj,Creature * target)919 void Battle::projectileHitTurn( Session *session, Projectile *proj, Creature *target ) {
920 	if ( debugBattle ) cerr << "*** Projectile hit (target): creature=" << proj->getCreature()->getName() << endl;
921 	Creature *oldTarget = ( ( Creature* )( proj->getCreature() ) )->getTargetCreature();
922 	( ( Creature* )( proj->getCreature() ) )->setTargetCreature( target );
923 	Battle *battle = ( ( Creature* )( proj->getCreature() ) )->getBattle();
924 	battle->projectileHit = true;
925 	if ( proj->getItem() ) {
926 		//battle->initItem(proj->getItem());
927 		battle->item = ( ( Item* )( proj->getItem() ) );
928 		battle->hitWithItem();
929 	} else if ( proj->getSpell() ) {
930 //    battle->spell = proj->getSpell();
931 //    battle->castSpell();
932 
933 		SpellCaster *sc = new SpellCaster( battle,
934 		    proj->getSpell(),
935 		    true,
936 		    proj->getCasterLevel() );
937 
938 		sc->spellSucceeded();
939 		delete sc;
940 	}
941 	battle->projectileHit = false;
942 	battle->spell = NULL;
943 	( ( Creature* )( proj->getCreature() ) )->cancelTarget();
944 	( ( Creature* )( proj->getCreature() ) )->setTargetCreature( oldTarget );
945 	// also cancel path
946 	if ( !proj->getCreature()->isAutoControlled() )
947 		( ( Creature* )( proj->getCreature() ) )->setSelXY( -1, -1 );
948 	if ( debugBattle ) cerr << "*** Projectile hit ends." << endl;
949 }
950 
projectileHitTurn(Session * session,Projectile * proj,int x,int y)951 void Battle::projectileHitTurn( Session *session, Projectile *proj, int x, int y ) {
952 	if ( debugBattle ) cerr << "*** Projectile hit (x,y): creature=" << proj->getCreature()->getName() << endl;
953 	// configure a turn
954 	( ( Creature* )( proj->getCreature() ) )->setTargetLocation( x, y, 0 );
955 	Battle *battle = ( ( Creature* )( proj->getCreature() ) )->getBattle();
956 	battle->projectileHit = true;
957 	if ( proj->getItem() ) {
958 		battle->item = ( ( Item* )( proj->getItem() ) );
959 		battle->hitWithItem();
960 	} else if ( proj->getSpell() &&
961 	            proj->getSpell()->isLocationTargetAllowed() ) {
962 
963 		SpellCaster *sc = new SpellCaster( battle,
964 		    proj->getSpell(),
965 		    true,
966 		    proj->getCasterLevel() );
967 
968 		sc->spellSucceeded();
969 		delete sc;
970 	}
971 	battle->projectileHit = false;
972 	battle->spell = NULL;
973 	( ( Creature* )( proj->getCreature() ) )->cancelTarget();
974 	if ( !proj->getCreature()->isAutoControlled() )
975 		( ( Creature* )( proj->getCreature() ) )->setSelXY( -1, -1 );
976 	if ( debugBattle ) cerr << "*** Projectile hit ends." << endl;
977 }
978 
979 
980 
981 /// Message to user about battle turn.
982 
prepareToHitMessage()983 void Battle::prepareToHitMessage() {
984 	if ( item ) {
985 		if ( session->getPreferences()->getCombatInfoDetail() > 0 ) {
986 			snprintf( message, MESSAGE_SIZE, _( "%1$s attacks %2$s with %3$s!" ),
987 			          creature->getName(),
988 			          creature->getTargetCreature()->getName(),
989 			          item->getItemName() );
990 		} else {
991 			snprintf( message, MESSAGE_SIZE, _( "%1$s attacks %2$s with %3$s!" ),
992 			          creature->getName(),
993 			          creature->getTargetCreature()->getName(),
994 			          item->getItemName() );
995 		}
996 		if ( creature->getCharacter() ) {
997 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
998 		} else {
999 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1000 		}
1001 		( ( AnimatedShape* )( creature->getShape() ) )->setAttackEffect( true );
1002 
1003 		// play item sound
1004 		if ( creature->isMonster() &&
1005 		        0 == Util::dice( session->getPreferences()->getSoundFreq() ) ) {
1006 			int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
1007 			session->playSound( creature->getMonster()->getRandomSound( Constants::SOUND_TYPE_ATTACK ), panning );
1008 		}
1009 		int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
1010 		session->playSound( getRandomSound( handheldSwishSoundStart, handheldSwishSoundCount ), panning );
1011 
1012 	} else {
1013 		snprintf( message, MESSAGE_SIZE, _( "%1$s attacks %2$s with bare hands!" ),
1014 		          creature->getName(),
1015 		          creature->getTargetCreature()->getName() );
1016 		if ( creature->getCharacter() ) {
1017 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1018 		} else {
1019 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1020 		}
1021 		( ( AnimatedShape* )( creature->getShape() ) )->setAttackEffect( true );
1022 	}
1023 }
1024 
1025 /// Handles low attack rolls.
1026 
1027 /// Special actions for very low attack roll. If the attack roll
1028 /// is below 10% of the max and the attack range is greater than 4pts,
1029 /// roll to see if there is a fumble.
1030 
handleLowAttackRoll(float attack,float min,float max)1031 bool Battle::handleLowAttackRoll( float attack, float min, float max ) {
1032 	if ( max - min >= MIN_FUMBLE_RANGE && creature->getTargetCreature() && attack - min < ( ( ( max - min ) / 100.0f ) * 5.0f ) ) {
1033 		if ( 0 == Util::dice( 3 ) ) {
1034 
1035 			Creature *fumbleTarget;
1036 			if ( creature->isMonster() ) {
1037 				fumbleTarget = session->getClosestMonster( toint( creature->getX() ), toint( creature->getY() ), creature->getShape()->getWidth(), creature->getShape()->getDepth(), CREATURE_SIGHT_RADIUS );
1038 			} else {
1039 				fumbleTarget = session->getClosestGoodGuy( toint( creature->getX() ), toint( creature->getY() ), creature->getShape()->getWidth(), creature->getShape()->getDepth(), CREATURE_SIGHT_RADIUS );
1040 			}
1041 
1042 			if ( fumbleTarget ) {
1043 				// play item sound
1044 				int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
1045 				if ( item ) session->playSound( item->getRandomSound(), panning );
1046 				snprintf( message, MESSAGE_SIZE, _( "...fumble: hits %s instead!" ), fumbleTarget->getName() );
1047 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
1048 				Creature *oldTarget = creature->getTargetCreature();
1049 				creature->setTargetCreature( fumbleTarget );
1050 
1051 				char tmp[255];
1052 				snprintf( tmp, 255, ( creature->getSex() == Constants::SEX_MALE ? _( "%s his own fumbling hands" ) : _( "%s her fumbling hands" ) ), Constants::getMessage( Constants::CAUSE_OF_DEATH ) );
1053 				creature->setPendingCauseOfDeath( tmp );
1054 
1055 				dealDamage( Util::roll( 0.5f * MIN_FUMBLE_RANGE, 1.5f * MIN_FUMBLE_RANGE ) );
1056 				creature->setTargetCreature( oldTarget );
1057 				return true;
1058 			}
1059 
1060 		}
1061 	}
1062 	return false;
1063 }
1064 
1065 /// Modify the damage for high attack roll.
1066 
applyHighAttackRoll(float * damage,float attack,float min,float max)1067 void Battle::applyHighAttackRoll( float *damage, float attack, float min, float max ) {
1068 	float percent = ( attack - min ) / ( ( max - min ) / 100.0f );
1069 	// special actions for very high tohits
1070 	if ( max - min >= MIN_FUMBLE_RANGE &&
1071 	        percent > 95 ) {
1072 		int mul = Util::dice( 8 );
1073 		switch ( mul ) {
1074 		case 2:
1075 			strcpy( message, _( "...precise hit: double damage!" ) );
1076 			if ( creature->getCharacter() ) {
1077 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1078 			} else {
1079 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1080 			}
1081 			( *damage ) *= mul;
1082 			break;
1083 
1084 		case 3:
1085 			strcpy( message, _( "...precise hit: triple damage!" ) );
1086 			if ( creature->getCharacter() ) {
1087 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1088 			} else {
1089 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1090 			}
1091 			( *damage ) *= mul;
1092 			break;
1093 
1094 		case 4:
1095 			strcpy( message, _( "...precise hit: quad damage!" ) );
1096 			if ( creature->getCharacter() ) {
1097 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1098 			} else {
1099 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1100 			}
1101 			( *damage ) *= mul;
1102 			break;
1103 
1104 		case 0:
1105 			if ( percent >= 98 ) {
1106 				strcpy( message, _( "...precise hit: instant kill!" ) );
1107 				if ( creature->getCharacter() ) {
1108 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDEATH );
1109 				} else {
1110 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDEATH );
1111 				}
1112 				( *damage ) = 10000;
1113 			}
1114 			break;
1115 
1116 		default:
1117 			break;
1118 		}
1119 	}
1120 }
1121 
1122 /// Modify the damage according to the properties of a magic item used.
1123 
applyMagicItemDamage(float * damage)1124 void Battle::applyMagicItemDamage( float *damage ) {
1125 	// magical weapons
1126 	if ( item && item->isMagicItem() ) {
1127 		int mul = 1;
1128 		if ( item->getMonsterType() && creature->getTargetCreature() &&
1129 		        !strcmp( item->getMonsterType(),
1130 		                 creature->getTargetCreature()->getModelName() ) ) {
1131 			mul = item->getDamageMultiplier();
1132 		} else if ( !item->getMonsterType() ) {
1133 			mul = item->getDamageMultiplier();
1134 		}
1135 		if ( mul < 1 ) mul = 1;
1136 		if ( mul == 2 ) {
1137 			strcpy( message, _( "...double damage!" ) );
1138 			if ( creature->getCharacter() ) {
1139 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1140 			} else {
1141 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1142 			}
1143 		} else if ( mul == 3 ) {
1144 			strcpy( message, _( "...triple damage!" ) );
1145 			if ( creature->getCharacter() ) {
1146 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1147 			} else {
1148 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1149 			}
1150 		} else if ( mul == 4 ) {
1151 			strcpy( message, _( "...quad damage!" ) );
1152 			if ( creature->getCharacter() ) {
1153 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1154 			} else {
1155 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1156 			}
1157 		} else if ( mul > 4 ) {
1158 			snprintf( message, MESSAGE_SIZE, _( "...%d-times damage!" ), mul );
1159 			if ( creature->getCharacter() ) {
1160 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1161 			} else {
1162 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1163 			}
1164 		}
1165 		( *damage ) *= mul;
1166 	}
1167 }
1168 
1169 /// Applies the effects of a magical item containing a spell.
1170 
applyMagicItemSpellDamage()1171 float Battle::applyMagicItemSpellDamage() {
1172 	// magical damage
1173 	if ( item &&
1174 	        item->isMagicItem() &&
1175 	        item->getSchool() &&
1176 	        creature->getTargetCreature() &&
1177 	        creature->isTargetValid() ) {
1178 
1179 		// roll for the spell damage
1180 		float damage = creature->rollMagicDamagePercent( item );
1181 
1182 		// check for resistance
1183 		int resistance =
1184 		  creature->getTargetCreature()->
1185 		  getSkill( item->getSchool()->getResistSkill() );
1186 
1187 		// reduce the magic attack by the resistance %.
1188 		damage -= ( damage / 100.0f ) * static_cast<float>( resistance );
1189 		if ( damage < 0 ) damage = 0;
1190 
1191 		char msg[ MESSAGE_SIZE ];
1192 		snprintf( msg, MESSAGE_SIZE, _( "...%1$s attacks %2$s with %3$s magic." ),
1193 		          creature->getName(),
1194 		          creature->getTargetCreature()->getName(),
1195 		          item->getSchool()->getShortName() );
1196 		if ( creature->getCharacter() ) {
1197 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERMAGIC );
1198 		} else {
1199 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCMAGIC );
1200 		}
1201 		if ( resistance > 0 ) {
1202 			snprintf( msg, MESSAGE_SIZE, _( "%s resists the magic with %d." ),
1203 			          creature->getTargetCreature()->getName(),
1204 			          resistance );
1205 			if ( creature->getTargetCreature()->getCharacter() ) {
1206 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1207 			} else {
1208 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1209 			}
1210 		}
1211 
1212 		return damage;
1213 	}
1214 	return -1.0f;
1215 }
1216 
1217 /// Handles physical attacks.
1218 
hitWithItem()1219 void Battle::hitWithItem() {
1220 	prepareToHitMessage();
1221 
1222 	float cth, skill;
1223 	creature->getCth( item, &cth, &skill );
1224 
1225 	if ( cth <= skill ) {
1226 		float dodge = creature->getTargetCreature()->getDodge( creature, item );
1227 		if ( cth <= skill - dodge ) {
1228 			// a hit?
1229 
1230 			// Shield/weapon parry
1231 			Item *parryItem = NULL;
1232 			float parry = creature->getTargetCreature()->getParry( &parryItem );
1233 			if ( parry > cth ) {
1234 				snprintf( message, MESSAGE_SIZE, _( "...%1$s blocks attack with %2$s!" ),
1235 				          creature->getTargetCreature()->getName(),
1236 				          parryItem->getName() );
1237 				if ( creature->getTargetCreature()->getCharacter() ) {
1238 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1239 				} else {
1240 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1241 				}
1242 			} else {
1243 				// a hit!
1244 
1245 				// calculate attack value
1246 				float max, min;
1247 				float attack = creature->getAttack( item, &max, &min, true );
1248 				float delta = creature->getAttackerStateModPercent();
1249 				float extra = ( attack / 100.0f ) * delta;
1250 				attack += extra;
1251 
1252 				// cursed items
1253 				if ( item && item->isCursed() ) {
1254 					session->getGameAdapter()->writeLogMessage( _( "...Using cursed item!" ), Constants::MSGTYPE_FAILURE );
1255 					attack -= ( attack / 3.0f );
1256 				}
1257 
1258 				snprintf( message, MESSAGE_SIZE, _( "...%s attacks for %d points." ),
1259 				          creature->getName(), toint( attack ) );
1260 				if ( creature->getCharacter() ) {
1261 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1262 				} else {
1263 					session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1264 				}
1265 				if ( session->getPreferences()->getCombatInfoDetail() > 0 ) {
1266 					snprintf( message, MESSAGE_SIZE, _( "...DAM:%.2f-%.2f extra:%.2f" ),
1267 					          min, max, extra );
1268 					if ( creature->getCharacter() ) {
1269 						session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1270 					} else {
1271 						session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1272 					}
1273 				}
1274 
1275 				// very low attack rolls (fumble)
1276 				if ( handleLowAttackRoll( attack, min, max ) ) return;
1277 
1278 				// get the armor
1279 				float armor, dodgePenalty;
1280 				creature->getTargetCreature()->
1281 				getArmor( &armor, &dodgePenalty,
1282 				          item ? item->getRpgItem()->getDamageType() : 0,
1283 				          item );
1284 				if ( toint( armor ) > 0 ) {
1285 					snprintf( message, MESSAGE_SIZE, _( "...%s's armor blocks %d points" ),
1286 					          creature->getTargetCreature()->getName(), toint( armor ) );
1287 					if ( creature->getTargetCreature()->getCharacter() ) {
1288 						session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1289 					} else {
1290 						session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1291 					}
1292 				}
1293 
1294 				float damage = ( armor > attack ? 0 : attack - armor );
1295 				if ( damage > 0 ) {
1296 					// play item sound
1297 					int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
1298 					if ( item ) session->playSound( item->getRandomSound(), panning );
1299 
1300 					applyMagicItemDamage( &damage );
1301 
1302 					//applyHighAttackRoll( &damage, attack, min, max );
1303 				}
1304 
1305 				// item attack event handler
1306 				if ( item && creature->isPartyMember() && SQUIRREL_ENABLED ) {
1307 					getSession()->getSquirrel()->setGlobalVariable( "damage", damage );
1308 					getSession()->getSquirrel()->callItemEvent( creature, item, "damageHandler" );
1309 					damage = getSession()->getSquirrel()->getGlobalVariable( "damage" );
1310 				}
1311 
1312 				char tmp[255];
1313 				strcpy( tmp, Constants::getMessage( Constants::CAUSE_OF_DEATH ) );
1314 				strcat( tmp, " " );
1315 				if ( creature->isMonster() ) {
1316 					strcat( tmp, getAn( creature->getMonster()->getType() ) );
1317 					strcat( tmp, " " );
1318 					strcat( tmp, creature->getMonster()->getType() );
1319 				} else {
1320 					strcat( tmp, creature->getName() );
1321 				}
1322 				if ( item ) {
1323 					strcat( tmp, _( " weilding " ) );
1324 					strcat( tmp, getAn( item->getName() ) );
1325 					strcat( tmp, " " );
1326 					strcat( tmp, item->getName() );
1327 				} else {
1328 					strcat( tmp, _( " in a flurry of blows" ) );
1329 				}
1330 				creature->getTargetCreature()->setPendingCauseOfDeath( tmp );
1331 
1332 				dealDamage( damage );
1333 
1334 				//lordtoran's testing cheat
1335 				//if ( !creature->isMonster() ) dealDamage( 9999 );
1336 
1337 				if ( damage > 0 ) {
1338 					// apply extra spell-like damage of magic items
1339 					float spellDamage = applyMagicItemSpellDamage();
1340 					if ( spellDamage > -1 ) {
1341 
1342 						strcpy( tmp, Constants::getMessage( Constants::CAUSE_OF_DEATH ) );
1343 						strcat( tmp, _( " magical damage by " ) );
1344 						if ( creature->isMonster() ) {
1345 							strcat( tmp, getAn( creature->getMonster()->getType() ) );
1346 							strcat( tmp, " " );
1347 							strcat( tmp, creature->getMonster()->getType() );
1348 						} else {
1349 							strcat( tmp, creature->getName() );
1350 						}
1351 						creature->getTargetCreature()->setPendingCauseOfDeath( tmp );
1352 
1353 						dealDamage( damage, Constants::EFFECT_GREEN, true );
1354 					}
1355 				}
1356 			}
1357 		} else {
1358 			// a miss
1359 			snprintf( message, MESSAGE_SIZE, _( "...%s dodges the attack." ),
1360 			          creature->getTargetCreature()->getName() );
1361 			if ( creature->getTargetCreature()->getCharacter() ) {
1362 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1363 			} else {
1364 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1365 			}
1366 		}
1367 	} else {
1368 		// a miss
1369 		snprintf( message, MESSAGE_SIZE, _( "...%s misses the target." ), creature->getName() );
1370 		session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
1371 	}
1372 }
1373 
1374 /// Deals the actual damage after an attack.
1375 
dealDamage(float damage,int effect,bool magical,GLuint delay)1376 void Battle::dealDamage( float damage, int effect, bool magical, GLuint delay ) {
1377 
1378 	// AI creatures fight back if not occupied with something else.
1379 	if ( creature->getTargetCreature() && creature->getTargetCreature()->isAutoControlled() && !creature->getTargetCreature()->isBusy() ) {
1380 		if ( debugBattle )
1381 			cerr << creature->getTargetCreature()->getName() << " fights back!" << endl;
1382 		creature->getTargetCreature()->setTargetCreature( creature );
1383 	}
1384 
1385 	if ( toint( damage ) > 0 ) {
1386 		// also affects spell attacks
1387 		float delta = creature->getDefenderStateModPercent( magical );
1388 		float extra = ( static_cast<float>( damage ) / 100.0f ) * delta;
1389 		damage += extra;
1390 
1391 		snprintf( message, MESSAGE_SIZE, _( "...%s hits for %d points of damage" ), creature->getName(), toint( damage ) );
1392 		if ( creature->getTargetCreature()->getCharacter() ) {
1393 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDAMAGE );
1394 		} else {
1395 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDAMAGE );
1396 		}
1397 
1398 		// play hit sound
1399 		if ( damage > 0 ) {
1400 			if ( creature->getTargetCreature()->isPartyMember() || creature->getTargetCreature()->isWanderingHero() ) {
1401 				//session->playSound(creature->getTargetCreature()->getCharacter()->getRandomSound(Constants::SOUND_TYPE_HIT));
1402 				int panning = session->getMap()->getPanningFromMapXY( creature->getTargetCreature()->getX(), creature->getTargetCreature()->getY() );
1403 				creature->getTargetCreature()->playCharacterSound( GameAdapter::HIT_SOUND, panning );
1404 			} else {
1405 				char const*soundname = creature->getTargetCreature()->getMonster()->getRandomSound( Constants::SOUND_TYPE_HIT );
1406 				if ( soundname ) {
1407 					int panning = session->getMap()->getPanningFromMapXY( creature->getTargetCreature()->getX(), creature->getTargetCreature()->getY() );
1408 					session->playSound( soundname, panning );
1409 				}
1410 			}
1411 		}
1412 
1413 		/*
1414 		Note: in case of a creature hitting itself (like a spell fumble)
1415 		creature->getTargetCreature() will return null after creature death.
1416 		Having a ptr to the original is needed.
1417 		*/
1418 		Creature *tc = creature->getTargetCreature();
1419 
1420 		// target creature death
1421 		if ( tc->takeDamage( toint( damage ), effect, delay ) ) {
1422 			// only in RT mode... otherwise in TB mode character won't move
1423 			if ( !session->getPreferences()->isBattleTurnBased() )
1424 				creature->getShape()->setCurrentAnimation( static_cast<int>( MD2_TAUNT ) );
1425 
1426 			snprintf( message, MESSAGE_SIZE, _( "...%s is killed!" ), tc->getName() );
1427 			if ( session->getParty()->isPartyMember( creature ) ) {
1428 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERDEATH );
1429 			} else {
1430 				session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCDEATH );
1431 			}
1432 			if ( session->getParty()->isPartyMember( tc ) )
1433 				session->getGameAdapter()->writeLogMessage( tc->getCauseOfDeath(), Constants::MSGTYPE_PLAYERDEATH );
1434 
1435 			// add exp. points and money
1436 			if ( !creature->isAutoControlled() ) {
1437 
1438 				// FIXME: try to move to party.cpp
1439 				for ( int i = 0; i < session->getParty()->getPartySize(); i++ ) {
1440 					// Add the exp for the killed creature
1441 					session->getParty()->getParty( i )->addExperience( tc );
1442 					if ( tc->isBoss() ) {
1443 						// Bosses give double the experience
1444 						session->getParty()->getParty( i )->addExperience( tc );
1445 					}
1446 					if ( !session->getParty()->getParty( i )->getStateMod( StateMod::dead ) ) {
1447 						int n = session->getParty()->getParty( i )->addMoney( tc );
1448 						if ( n > 0 ) {
1449 							snprintf( message, MESSAGE_SIZE, _( "%s finds %d coins!" ), session->getParty()->getParty( i )->getName(), n );
1450 							session->getGameAdapter()->writeLogMessage( message );
1451 						}
1452 					}
1453 				}
1454 				// end of FIXME
1455 
1456 				if ( tc->isBoss() ) {
1457 					session->getGameAdapter()->writeLogMessage( _( "You have defeated the dungeon boss!" ), Constants::MSGTYPE_MISSION );
1458 					session->getGameAdapter()->writeLogMessage( _( "...the news spreads quickly and his minions cower before you." ), Constants::MSGTYPE_MISSION );
1459 				}
1460 
1461 				// see if this is a mission objective
1462 				if ( session->getCurrentMission() && tc->getMonster() && session->getCurrentMission()->creatureSlain( tc ) ) {
1463 					session->getGameAdapter()->missionCompleted();
1464 				}
1465 			}
1466 		}
1467 	} else {
1468 		snprintf( message, MESSAGE_SIZE, _( "...no damaged caused." ) );
1469 		if ( creature->getCharacter() ) {
1470 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_PLAYERBATTLE );
1471 		} else {
1472 			session->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_NPCBATTLE );
1473 		}
1474 	}
1475 }
1476 
1477 /*
1478 void Battle::initItem(Item *item) {
1479   this->item = item;
1480 
1481   // (!item) is a bare-hands attack
1482   speed = (item ? item->getSpeed() : Constants::HAND_WEAPON_SPEED) *
1483           (session->getPreferences()->getGameSpeedTicks() + 80);
1484   // (scourge->getPreferences()->getGameSpeedTicks() + 80);
1485 
1486   creatureInitiative = creature->getInitiative(item);
1487 }
1488 */
1489 
1490 /// Makes a creature eat or drink the action item.
1491 
executeEatDrinkAction()1492 void Battle::executeEatDrinkAction() {
1493 	// is it still in the backpack?
1494 	int index = creature->findInBackpack( creature->getActionItem() );
1495 	if ( index > -1 ) {
1496 		int panning = session->getMap()->getPanningFromMapXY( creature->getX(), creature->getY() );
1497 		session->playSound( getRandomSound( potionSoundStart, potionSoundCount ), panning );
1498 		if ( creature->eatDrink( creature->getActionItem() ) ) {
1499 			creature->removeFromBackpack( index );
1500 			if ( !session->getGameAdapter()->isHeadless() )
1501 				session->getGameAdapter()->refreshBackpackUI();
1502 		}
1503 	}
1504 	// cancel action
1505 	creature->cancelTarget();
1506 	if ( !creature->isAutoControlled() ) creature->setSelXY( -1, -1 );
1507 }
1508 
1509 /// Ends a battle turn.
1510 
invalidate()1511 void Battle::invalidate() {
1512 	if ( debugBattle ) cerr << "*** invalidate: creature=" << getCreature()->getName() << endl;
1513 	nextTurn = weaponWait = 0;
1514 }
1515 
getRandomSound(int start,int count)1516 char *Battle::getRandomSound( int start, int count ) {
1517 	if ( count )
1518 		return sound[ start + Util::dice( count ) ];
1519 	else return NULL;
1520 }
1521 
1522 /// Describes the current type of attack to be later used as part of a log message.
1523 
describeAttack(Creature * target,char * buff,size_t buffSize,Color * color,bool includeActions)1524 bool Battle::describeAttack( Creature *target, char *buff, size_t buffSize, Color *color, bool includeActions ) {
1525 
1526 	initTurnStep();
1527 
1528 	// info for the player?
1529 	if ( includeActions && session->getParty()->getPlayer() == creature ) {
1530 		if ( creature->getAction() == Constants::ACTION_CAST_SPELL ) {
1531 			snprintf( buff, buffSize, "%s: %d",
1532 			          creature->getActionSpell()->getDisplayName(),
1533 			          ( nextTurn > 0 ? nextTurn : weaponWait ) );
1534 			color->r = 0.6f;
1535 			color->g = 0.1f;
1536 			color->b = 0.6f;
1537 			return true;
1538 		} else if ( creature->getAction() == Constants::ACTION_EAT_DRINK ) {
1539 			snprintf( buff, buffSize, "%s: %d",
1540 			          creature->getActionItem()->getRpgItem()->getDisplayName(),
1541 			          ( nextTurn > 0 ? nextTurn : weaponWait ) );
1542 			color->r = 0;
1543 			color->g = 0.6f;
1544 			color->b = 0;
1545 			return true;
1546 		} else if ( creature->getAction() == Constants::ACTION_SPECIAL ) {
1547 			snprintf( buff, buffSize, "%s: %d",
1548 			          creature->getActionSkill()->getDisplayName(),
1549 			          ( nextTurn > 0 ? nextTurn : weaponWait ) );
1550 			color->r = 0;
1551 			color->g = 0.3f;
1552 			color->b = 0.6f;
1553 			return true;
1554 		}
1555 	}
1556 
1557 	// info for the target
1558 	bool sameTarget = ( creature->getTargetCreature() == target );
1559 	if ( !target || !creature->canAttack( target ) ) return false;
1560 
1561 	if ( sameTarget ) {
1562 		if ( creature->getAction() == Constants::ACTION_CAST_SPELL ) {
1563 			snprintf( buff, buffSize, _( "Targeted" ) );
1564 			color->r = 0.6f;
1565 			color->g = 0.1f;
1566 			color->b = 0.6f;
1567 			return true;
1568 		} else if ( creature->getAction() == Constants::ACTION_SPECIAL ) {
1569 			snprintf( buff, buffSize, _( "Targeted" ) );
1570 			color->r = 0;
1571 			color->g = 0.3f;
1572 			color->b = 0.6f;
1573 			return true;
1574 		}
1575 	}
1576 
1577 	Creature *tmp = NULL;
1578 	if ( !sameTarget ) {
1579 		tmp = creature->getTargetCreature();
1580 		creature->setTargetCreature( target );
1581 	}
1582 	float dist = creature->getDistanceToTarget();
1583 	Item *item = creature->getBestWeapon( dist );
1584 	if ( !sameTarget ) creature->setTargetCreature( tmp );
1585 
1586 	// out of range
1587 	if ( ( !item || item->getRange() <= dist ) && dist > MIN_DISTANCE ) {
1588 		if ( sameTarget ) {
1589 			color->r = 0.5f;
1590 			color->g = 0.2f;
1591 			color->b = 0;
1592 			//does the path get us in range?
1593 			if ( ( !item && creature->getPathManager()->isPathToTargetCreature() ) || ( item && creature->getPathManager()->isPathTowardTargetCreature( item->getRange() ) ) ) {
1594 				snprintf( buff, buffSize, _( "Out of Range. Move: %d" ), static_cast<int>( creature->getPathManager()->getPathRemainingSize() ) );
1595 			} else {
1596 				snprintf( buff, buffSize, _( "Out of Range" ) );
1597 			}
1598 		} else {
1599 			color->r = 0.3f;
1600 			color->g = 0.3f;
1601 			color->b = 0.3f;
1602 			// could still be in range for spell or skill
1603 			snprintf( buff, buffSize, ( range >= dist ? _( "In Range" ) : _( "Out of Range" ) ) );
1604 		}
1605 		return true;
1606 	}
1607 
1608 	// How many steps to wait before being able to use the weapon.
1609 	int weaponWait = toint( creature->getWeaponAPCost( item ) );
1610 	//if( session->getPreferences()->isBattleTurnBased() ) {
1611 //    weaponWait /= 2;
1612 //  }
1613 
1614 	snprintf( buff, buffSize, "%s: %d",
1615 	          ( item ? item->getRpgItem()->getDisplayName() : _( "Bare Hands" ) ),
1616 	          ( sameTarget && nextTurn > 0 ? nextTurn : weaponWait ) );
1617 	color->r = 0.6f;
1618 	color->g = 0;
1619 	color->b = 0;
1620 	return true;
1621 }
1622 
1623 /// Returns whether the target is within weapon range.
1624 
isInRangeOfTarget()1625 bool Battle::isInRangeOfTarget() {
1626 	// FIXME: should also work with target item, location
1627 	if ( !creature->getTargetCreature() ) return false;
1628 	float dist = creature->getDistanceToTarget();
1629 	Item *item = creature->getBestWeapon( dist );
1630 	return( ( !item && dist <= MIN_DISTANCE ) ||
1631 	        ( item && item->getRange() >= dist ) );
1632 }
1633