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