1 /***************************************************************************
2 spellcaster.cpp - Currently cast spell 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 #include "common/constants.h"
18 #include "spellcaster.h"
19 #include "render/renderlib.h"
20 #include "item.h"
21 #include "creature.h"
22 #include "events/event.h"
23 #include "events/potionexpirationevent.h"
24 #include "events/statemodexpirationevent.h"
25 #include "calendar.h"
26 #include "rpg/rpglib.h"
27 #include "projectile.h"
28 #include "shapepalette.h"
29 #include "sqbinding/sqbinding.h"
30 #include "debug.h"
31
32 using namespace std;
33
34 /*
35 In the future we can employ something more sophisticated than these if structures...
36 */
37
SpellCaster(Battle * battle,Spell * spell,bool projectileHit,int level)38 SpellCaster::SpellCaster( Battle *battle, Spell *spell, bool projectileHit, int level ) {
39 this->battle = battle;
40 this->spell = spell;
41 this->projectileHit = projectileHit;
42 this->level = level;
43
44 Creature *creature = battle->getCreature();
45
46 // calculate spell's power (0-100)
47 // Used only for HP/AC restore spells
48 power = level +
49 creature->getSkill( Skill::LUCK ) +
50 Util::roll( 0.0f, 30.0f );
51 }
52
~SpellCaster()53 SpellCaster::~SpellCaster() {
54 }
55
spellFailed()56 void SpellCaster::spellFailed() {
57 if ( !spell ) return;
58
59 // print patronizing message...
60 battle->getSession()->getGameAdapter()->writeLogMessage( Constants::getMessage( Constants::SPELL_FAILED_MESSAGE ), Constants::MSGTYPE_FAILURE );
61
62 int panning = battle->getSession()->getMap()->getPanningFromMapXY( battle->getCreature()->getX(), battle->getCreature()->getY() );
63 battle->getSession()->playSound( spell->getSound(), panning );
64
65 // put code here for spells with something spectacular when they fail...
66 // (fouled fireball decimates party, etc.)
67 if ( !strcasecmp( spell->getName(), "Burning stare" ) ||
68 !strcasecmp( spell->getName(), "Silent knives" ) ||
69 !strcasecmp( spell->getName(), "Stinging light" ) ||
70 !strcasecmp( spell->getName(), "Unholy Decimator" ) ||
71 !strcasecmp( spell->getName(), "Blast of Fury" ) ) {
72
73 Creature *fumbleTarget;
74 if ( battle->getCreature()->isMonster() ||
75 battle->getCreature()->getStateMod( StateMod::possessed ) ) {
76 fumbleTarget = battle->getSession()->getRandomNearbyMonster( toint( battle->getCreature()->getX() ),
77 toint( battle->getCreature()->getY() ),
78 battle->getCreature()->getShape()->getWidth(),
79 battle->getCreature()->getShape()->getDepth(),
80 CREATURE_SIGHT_RADIUS );
81 } else {
82 fumbleTarget = battle->getSession()->getRandomNearbyGoodGuy( toint( battle->getCreature()->getX() ),
83 toint( battle->getCreature()->getY() ),
84 battle->getCreature()->getShape()->getWidth(),
85 battle->getCreature()->getShape()->getDepth(),
86 CREATURE_SIGHT_RADIUS );
87 }
88 if ( fumbleTarget ) {
89 char message[200];
90 snprintf( message, 200, _( "...fumble: hits %s instead!" ), fumbleTarget->getName() );
91 battle->getSession()->getGameAdapter()->writeLogMessage( message, Constants::MSGTYPE_FAILURE );
92 Creature *oldTarget = battle->getCreature()->getTargetCreature();
93 battle->getCreature()->setTargetCreature( fumbleTarget );
94
95 causeDamage( true, 0, 0.5 );
96
97 battle->getCreature()->setTargetCreature( oldTarget );
98 }
99 } else if ( !strcasecmp( spell->getName(), "Teleportation" ) ) {
100 battle->getSession()->getGameAdapter()->teleport( false );
101 } else if ( !strcasecmp( spell->getName(), "Call for Help" ) ) {
102 summonCreature( false );
103 }
104 }
105
spellSucceeded()106 void SpellCaster::spellSucceeded() {
107 if ( !spell ) return;
108
109 int panning = battle->getSession()->getMap()->getPanningFromMapXY( battle->getCreature()->getX(), battle->getCreature()->getY() );
110 battle->getSession()->playSound( spell->getSound(), panning );
111
112 // cerr << "SUCCEEDED: " << spell->getName() << " power=" << power << endl;
113 if ( !strcasecmp( spell->getName(), "Lesser healing touch" ) ||
114 !strcasecmp( spell->getName(), "Greater healing touch" ) ||
115 !strcasecmp( spell->getName(), "Divine healing touch" ) ) {
116 increaseHP();
117 } else if ( !strcasecmp( spell->getName(), "Body of stone" ) ) {
118 increaseAC();
119 } else if ( !strcasecmp( spell->getName(), "Burning stare" ) ||
120 !strcasecmp( spell->getName(), "Silent knives" ) ) {
121 if ( projectileHit ) {
122 causeDamage();
123 } else {
124 launchProjectile( 1 );
125 }
126 } else if ( !strcasecmp( spell->getName(), "Stinging light" ) ) {
127 if ( projectileHit ) {
128 causeDamage( false );
129 } else {
130 launchProjectile( 0 );
131 }
132 } else if ( !strcasecmp( spell->getName(), "Invisibility" ) ) {
133 setStateMod( StateMod::invisible );
134 } else if ( !strcasecmp( spell->getName(), "Art of protection" ) ) {
135 setStateMod( StateMod::magic_protected );
136 } else if ( !strcasecmp( spell->getName(), "Divine Aggressor" ) ) {
137 setStateMod( StateMod::enraged );
138 } else if ( !strcasecmp( spell->getName(), "Protective clay" ) ) {
139 setStateMod( StateMod::ac_protected );
140 } else if ( !strcasecmp( spell->getName(), "Empower friend" ) ) {
141 setStateMod( StateMod::empowered );
142 } else if ( !strcasecmp( spell->getName(), "Bless group" ) ) {
143 setStateMod( StateMod::blessed );
144 } else if ( !strcasecmp( spell->getName(), "Flame of Azun" ) ) {
145 if ( projectileHit ) {
146 setStateMod( StateMod::blinded );
147 } else {
148 launchProjectile( 1, false );
149 }
150 } else if ( !strcasecmp( spell->getName(), "Poison of ignorance" ) ) {
151 if ( projectileHit ) {
152 setStateMod( StateMod::poisoned );
153 } else {
154 launchProjectile( 1, false );
155 }
156 } else if ( !strcasecmp( spell->getName(), "Transmute poison" ) ) {
157 setStateMod( StateMod::poisoned, false );
158 } else if ( !strcasecmp( spell->getName(), "Cursed ways" ) ) {
159 if ( projectileHit ) {
160 setStateMod( StateMod::cursed );
161 } else {
162 launchProjectile( 1, false );
163 }
164 } else if ( !strcasecmp( spell->getName(), "Remove curse" ) ) {
165 setStateMod( StateMod::cursed, false );
166 } else if ( !strcasecmp( spell->getName(), "Enthrall fiend" ) ) {
167 setStateMod( StateMod::possessed );
168 } else if ( !strcasecmp( spell->getName(), "Break from possession" ) ) {
169 setStateMod( StateMod::possessed, false );
170 } else if ( !strcasecmp( spell->getName(), "Ole Taffy's purty colors" ) ) {
171 viewInfo();
172 } else if ( !strcasecmp( spell->getName(), "Ring of Harm" ) ) {
173 circleAttack();
174 } else if ( !strcasecmp( spell->getName(), "Malice Storm" ) ) {
175 hailAttack();
176 } else if ( !strcasecmp( spell->getName(), "Blast of Fury" ) ) {
177 if ( projectileHit ) {
178 causeDamage();
179 } else {
180 Session *session = battle->getSession();
181 launchProjectile( 1, true,
182 new EffectProjectileRenderer( session->getMap(),
183 session->getPreferences(),
184 session->getShapePalette(),
185 spell->getEffect(),
186 3000 ) );
187 }
188 } else if ( !strcasecmp( spell->getName(), "Unholy Decimator" ) ) {
189 causeDamage();
190 } else if ( !strcasecmp( spell->getName(), "Recall to life" ) ) {
191 resurrect();
192 } else if ( !strcasecmp( spell->getName(), "Teleportation" ) ) {
193 battle->getSession()->getGameAdapter()->teleport();
194 } else if ( !strcasecmp( spell->getName(), "Dori's Tumblers" ) ) {
195 openLocked();
196 } else if ( !strcasecmp( spell->getName(), "Gust of wind" ) ) {
197 windAttack();
198 } else if ( !strcasecmp( spell->getName(), "Call for Help" ) ) {
199 summonCreature();
200 } else {
201 // default
202 cerr << "*** ERROR: Implement spell " << spell->getName() << endl;
203 }
204 }
205
206
207
viewInfo()208 void SpellCaster::viewInfo() {
209 Creature *creature = battle->getCreature();
210 Item *item = creature->getTargetItem();
211 creature->startEffect( spell->getEffect(), ( Constants::DAMAGE_DURATION * 4 ) );
212 if ( item ) {
213 if ( !battle->getSession()->getGameAdapter()->isHeadless() )
214 battle->getSession()->getGameAdapter()->showItemInfoUI( item,
215 creature->getSkill( Skill::IDENTIFY_ITEM ) +
216 creature->getSkill( spell->getSchool()->getSkill() ) );
217 } else {
218 int sx, sy, sz;
219 creature->getTargetLocation( &sx, &sy, &sz );
220 if ( sx > 0 && sy > 0 ) {
221
222 // move spell focus to selected area
223 int spellEffectSize = 4;
224 int radius = spell->getDistance();
225
226 // show radius effect
227 battle->getSession()->getMap()->startEffect( sx, sy, 1,
228 Constants::EFFECT_RING, ( Constants::DAMAGE_DURATION * 4 ),
229 radius, radius );
230
231 // walk around the circle
232 enum { MSG_SIZE = 200 };
233 char msg[ MSG_SIZE ];
234 DisplayInfo di;
235 di.red = 0.1f;
236 di.green = 0.1f;
237 di.blue = 0.2f;
238 for ( int r = spellEffectSize; r; r += spellEffectSize ) {
239 if ( r > radius ) r = radius;
240 for ( int angle = 0; angle < 360; angle += 10 ) {
241 int x = toint( sx + ( static_cast<float>( r ) * Constants::cosFromAngle( static_cast<float>( angle ) ) ) );
242 int y = toint( sy - ( static_cast<float>( r ) * Constants::sinFromAngle( static_cast<float>( angle ) ) ) );
243 if ( x >= 0 && x < MAP_WIDTH && y >= 0 && y < MAP_DEPTH ) {
244 battle->getSession()->getMap()->startEffect( x, y, 1, Constants::EFFECT_GREEN,
245 ( Constants::DAMAGE_DURATION * 4 ),
246 spellEffectSize, spellEffectSize,
247 ( GLuint )( r * 25 ), false, &di );
248
249 // check for secret doors
250 Location *pos = battle->getSession()->getMap()->getLocation( x, y, 0 );
251 if ( pos ) {
252 if ( battle->getSession()->getMap()->isSecretDoor( pos ) ) {
253 if ( !battle->getSession()->getMap()->isSecretDoorDetected( pos ) ) {
254 battle->getSession()->getMap()->setSecretDoorDetected( pos );
255 snprintf( msg, MSG_SIZE, _( "%s discovers a secret door!" ), creature->getName() );
256 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_MISSION );
257 }
258 }
259 }
260
261 // check for traps
262 int trapIndex = battle->getSession()->getMap()->getTrapAtLoc( x, y );
263 if ( trapIndex > -1 ) {
264 Trap *trap = battle->getSession()->getMap()->getTrapLoc( trapIndex );
265 if ( !trap->discovered ) {
266 trap->discovered = true;
267 snprintf( msg, MSG_SIZE, _( "%s discovers a trap!" ), creature->getName() );
268 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_MISSION );
269 }
270 }
271 }
272 }
273
274 if ( r >= radius ) break;
275 }
276 } else {
277 cerr << "*** Warning: implement ole' taffy for creature targets!" << endl;
278 }
279 }
280 }
281
increaseHP()282 void SpellCaster::increaseHP() {
283 Creature *creature = battle->getCreature();
284
285 int n = spell->getAction();
286 n += Util::dice( static_cast<int>( n * power / 100 ) );
287
288 if ( n + creature->getTargetCreature()->getHp() > creature->getTargetCreature()->getMaxHp() )
289 n = creature->getTargetCreature()->getMaxHp() - creature->getTargetCreature()->getHp();
290 creature->getTargetCreature()->setHp( static_cast<int>( creature->getTargetCreature()->getHp() + n ) );
291 char msg[200];
292 snprintf( msg, 200, _( "%s heals %d points." ), creature->getTargetCreature()->getName(), n );
293 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
294 if ( creature->getCharacter() ) {
295 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERMAGIC );
296 } else {
297 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCMAGIC );
298 }
299 creature->getTargetCreature()->startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
300 }
301
increaseAC()302 void SpellCaster::increaseAC() {
303 Creature *creature = battle->getCreature();
304 int n = spell->getAction();
305 n += Util::dice( static_cast<int>( n * power / 100 ) );
306
307 int timeInMin = 15 + ( level / 2 );
308
309 // cerr << "increaseAC: points=" << n << " time=" << timeInMin << " min-s." << endl;
310
311 creature->getTargetCreature()->setBonusArmor( creature->getTargetCreature()->getBonusArmor() + n );
312 char msg[200];
313 snprintf( msg, 200, _( "%s feels impervious to damage!" ), creature->getTargetCreature()->getName() );
314 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
315 creature->getTargetCreature()->startEffect( Constants::EFFECT_SWIRL, ( Constants::DAMAGE_DURATION * 4 ) );
316
317 // add calendar event to remove armor bonus
318 // (format : sec, min, hours, days, months, years)
319 Date d( 0, timeInMin, 0, 0, 0, 0 );
320 Event *e = new PotionExpirationEvent( battle->getSession()->getParty()->getCalendar()->getCurrentDate(),
321 d, creature->getTargetCreature(),
322 Constants::getPotionSkillByName( "AC" ), n,
323 battle->getSession(), 1 );
324 battle->getSession()->getParty()->getCalendar()->scheduleEvent( ( Event* )e ); // It's important to cast!!
325
326 }
327
launchProjectile(int count,bool stopOnImpact,ProjectileRenderer * renderer)328 Projectile *SpellCaster::launchProjectile( int count, bool stopOnImpact, ProjectileRenderer *renderer ) {
329 Creature *creature = battle->getCreature();
330
331 // maxcount for spells means number of projectiles
332 // (for missiles it means how many can be in the air at once.)
333 int n = count;
334 if ( n <= 0 ) {
335 n = level;
336 if ( n < 1 ) n = 1;
337 }
338
339 if ( !renderer ) {
340 renderer =
341 new ShapeProjectileRenderer( battle->getSession()->
342 getShapePalette()->
343 findShapeByName( "SPELL_FIREBALL" ) );
344 }
345
346 Projectile *p;
347 if ( creature->getTargetCreature() ) {
348 p = Projectile::addProjectile( creature,
349 creature->getTargetCreature(),
350 spell,
351 renderer,
352 n, stopOnImpact );
353 } else {
354 int x, y, z;
355 creature->getTargetLocation( &x, &y, &z );
356 p = Projectile::addProjectile( creature,
357 x, y,
358 1, 1,
359 spell,
360 renderer,
361 n, stopOnImpact );
362 }
363 if ( !p ) {
364 char msg[ 200 ];
365 snprintf( msg, 200, _( "...%s has finished firing a volley" ), creature->getName() );
366 if ( creature->getCharacter() ) {
367 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERMAGIC );
368 } else {
369 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCMAGIC );
370 }
371 } else {
372 p->setCasterLevel( level );
373 }
374 return p;
375 }
376
causeDamage(bool multiplyByLevel,GLuint delay,GLfloat mult)377 void SpellCaster::causeDamage( bool multiplyByLevel, GLuint delay, GLfloat mult ) {
378 Creature *creature = battle->getCreature();
379
380 // roll for the spell damage
381 float damage = 0;
382 for ( int i = 0; i <= ( level / 2 ); i++ ) {
383 damage += spell->getAction();
384 if ( !multiplyByLevel ) break;
385 }
386 damage *= mult;
387
388 // give early-game spells a chance
389 bool lowDamage = damage < 5;
390
391 // dodge saves for half damage
392 enum { MSG_SIZE = 200 };
393 char msg[ MSG_SIZE ];
394 float skill = Util::roll( 0.0f, creature->getSkill( spell->getSchool()->getSkill() ) );
395 float dodge = Util::roll( 0.0f, creature->getTargetCreature()->getDodge( creature ) );
396 if ( skill < dodge ) {
397 damage /= 2.0f;
398 snprintf( msg, MSG_SIZE, _( "%s dodges some of the damage." ),
399 creature->getTargetCreature()->getName() );
400 if ( creature->getTargetCreature()->getCharacter() ) {
401 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERBATTLE );
402 } else {
403 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCBATTLE );
404 }
405 }
406
407 // check for resistance
408 int resistance = creature->getTargetCreature()->getSkill( spell->getSchool()->getResistSkill() );
409 if ( resistance > 0 && !lowDamage ) {
410 damage -= ( ( static_cast<float>( damage ) / 150.0f ) * resistance );
411 }
412
413 snprintf( msg, MSG_SIZE, _( "%1$s attacks %2$s with %3$s." ),
414 creature->getName(),
415 creature->getTargetCreature()->getName(),
416 spell->getDisplayName() );
417 if ( creature->getCharacter() ) {
418 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERMAGIC );
419 } else {
420 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCMAGIC );
421 }
422 if ( resistance > 0 && !lowDamage ) {
423 snprintf( msg, MSG_SIZE, _( "%s resists the spell with %d." ),
424 creature->getTargetCreature()->getName(),
425 resistance );
426 if ( creature->getTargetCreature()->getCharacter() ) {
427 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERBATTLE );
428 } else {
429 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCBATTLE );
430 }
431 }
432
433 // script spell resistance
434 if( battle->getCreature()->isPartyMember() && SQUIRREL_ENABLED ) {
435 battle->getSession()->getSquirrel()->setGlobalVariable( "damage", damage );
436 battle->getSession()->getSquirrel()->callSpellEvent( creature, spell, "spellDamageHandler" );
437 damage = battle->getSession()->getSquirrel()->getGlobalVariable( "damage" );
438 }
439
440 char tmp[255];
441 snprintf( tmp, 255, _( "%s the %s spell" ),
442 Constants::getMessage( Constants::CAUSE_OF_DEATH ),
443 spell->getDisplayName() );
444 creature->getTargetCreature()->setPendingCauseOfDeath( tmp );
445
446 // cause damage, kill creature, gain levels, etc.
447 battle->dealDamage( damage,
448 spell->getEffect(),
449 true, delay );
450 }
451
resurrect()452 void SpellCaster::resurrect() {
453 for ( int i = 0; i < battle->getSession()->getParty()->getPartySize(); i++ ) {
454 if ( battle->getSession()->getParty()->getParty( i )->getStateMod( StateMod::dead ) ) {
455 battle->getSession()->getParty()->getParty( i )->
456 resurrect( toint( battle->getCreature()->getX() ),
457 toint( battle->getCreature()->getY() ) );
458 }
459 }
460 }
461
setStateMod(int mod,bool setting)462 void SpellCaster::setStateMod( int mod, bool setting ) {
463 Creature *targets[100];
464 int targetCount = 0;
465
466 if ( spell->isPartyTargetAllowed() ) {
467 for ( int i = 0; i < battle->getSession()->getParty()->getPartySize(); i++ ) {
468 if ( spell->isPartyTargetAllowed() ) {
469 targets[targetCount++] = battle->getSession()->getParty()->getParty( i );
470 }
471 }
472 } else if ( spell->getTargetType() == GROUP_TARGET ) {
473 int radius = level * 2;
474 if ( radius > 15 ) radius = 15;
475 if ( radius < 2 ) radius = 2;
476 // cerr << "radius=" << radius << endl;
477 // show radius effect
478 battle->getSession()->getMap()->startEffect( battle->getCreature()->getTargetX(),
479 battle->getCreature()->getTargetY(),
480 1,
481 Constants::EFFECT_RING,
482 ( Constants::DAMAGE_DURATION * 4 ),
483 radius, radius );
484
485 targetCount =
486 battle->getSession()->getMap()->
487 getCreaturesInArea( battle->getCreature()->getTargetX(),
488 battle->getCreature()->getTargetY(),
489 radius,
490 ( RenderedCreature** )targets );
491 // cerr << "targetCount=" << targetCount << endl;
492 } else {
493 targets[targetCount++] = battle->getCreature()->getTargetCreature();
494 }
495
496
497
498
499
500 for ( int i = 0; i < targetCount; i++ ) {
501 Creature *creature = targets[i];
502
503 bool protectiveItem = false;
504 if ( !StateMod::stateMods[ mod ]->isStateModTransitionWanted( setting ) ) {
505 //if(!Constants::isStateModTransitionWanted(mod, setting)) {
506
507 // bad effects should only happen to enemies
508 if ( !battle->getCreature()->canAttack( creature ) ) continue;
509
510 // roll for resistance
511 enum { MSG_SIZE = 200 };
512 char msg[ MSG_SIZE ];
513 if ( Util::dice( 100 ) < creature->getSkill( spell->getSchool()->getResistSkill() ) ) {
514 snprintf( msg, MSG_SIZE, _( "%s resists the spell! [%d]" ),
515 creature->getName(),
516 creature->getSkill( spell->getSchool()->getResistSkill() ) );
517 if ( creature->getCharacter() ) {
518 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERBATTLE );
519 } else {
520 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCBATTLE );
521 }
522 continue;
523 }
524
525 // check for magic item state mod protections
526 protectiveItem = creature->getProtectedStateMod( mod );
527 if ( protectiveItem && 0 == Util::dice( 2 ) ) {
528 snprintf( msg, MSG_SIZE, _( "%s resists the spell with magic item!" ),
529 creature->getName() );
530 if ( creature->getCharacter() ) {
531 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_PLAYERBATTLE );
532 } else {
533 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_NPCBATTLE );
534 }
535 continue;
536 }
537 }
538
539 if ( !strcasecmp( spell->getName(), "Remove curse" ) ) {
540 // remove cursed items
541 if ( creature->removeCursedItems() ) {
542 battle->getSession()->getGameAdapter()->writeLogMessage( _( "Cursed items have been doffed." ) );
543 }
544 }
545
546 int timeInMin = 5 * level;
547 if ( protectiveItem ) timeInMin /= 2;
548 creature->startEffect( spell->getEffect(), ( Constants::DAMAGE_DURATION * 4 ) );
549
550 // extend expiration event somehow if condition already exists
551 Event *e = creature->getStateModEvent( mod );
552 if ( creature->getStateMod( mod ) == setting ) {
553 if ( e ) {
554 // cerr << "Extending existing event." << endl;
555 e->setNbExecutionsToDo( timeInMin );
556 }
557 continue;
558 }
559 creature->setStateMod( mod, setting );
560 enum { MSG_SIZE = 200 };
561 char msg[ MSG_SIZE ];
562 if ( setting ) {
563 snprintf( msg, MSG_SIZE, StateMod::stateMods[mod]->getSetState(), creature->getName() );
564 } else {
565 snprintf( msg, MSG_SIZE, StateMod::stateMods[mod]->getUnsetState(), creature->getName() );
566 }
567 battle->getSession()->getGameAdapter()->writeLogMessage( msg, Constants::MSGTYPE_STATS );
568
569 // cancel existing event if any
570 if ( e ) {
571 // cerr << "Cancelling existing event." << endl;
572 battle->getSession()->getParty()->getCalendar()->cancelEvent( e );
573 }
574
575 if ( setting ) {
576 // add calendar event to remove condition
577 // (format : sec, min, hours, days, months, years)
578 Calendar *cal = battle->getSession()->getParty()->getCalendar();
579 // cerr << Constants::STATE_NAMES[mod] << " will expire in " << timeInMin << " minutes." << endl;
580 Date d( 0, 15, 0, 0, 0, 0 );
581 // cerr << "Creating new event." << endl;
582 e = new StateModExpirationEvent( cal->getCurrentDate(),
583 d, creature, mod, battle->getSession(),
584 timeInMin );
585 cal->scheduleEvent( ( Event* )e ); // It's important to cast!!
586 creature->setStateModEvent( mod, e );
587 }
588 }
589 }
590
openLocked()591 void SpellCaster::openLocked() {
592 Location *pos = battle->getSession()->getMap()->getLocation( battle->getCreature()->getTargetX(),
593 battle->getCreature()->getTargetY(),
594 battle->getCreature()->getTargetZ() );
595 if ( pos ) {
596 battle->getSession()->getGameAdapter()->useDoor( pos, true );
597 }
598 }
599
summonCreature(bool friendly)600 void SpellCaster::summonCreature( bool friendly ) {
601 Creature *c = battle->getCreature()->summonCreature( friendly );
602 if( !c ) {
603 battle->getSession()->getGameAdapter()->writeLogMessage( _( "Something goes wrong! No one answers your call for help!" ), Constants::MSGTYPE_PLAYERMAGIC );
604 } else if( c && !friendly ) {
605 battle->getSession()->getGameAdapter()->writeLogMessage( _( "Something goes horribly wrong! The summoned monster breaks free and attacks!" ), Constants::MSGTYPE_PLAYERMAGIC );
606 }
607 }
608
windAttack()609 void SpellCaster::windAttack() {
610
611 int spellEffectSize = 4;
612 float sx, sy;
613 int radius = getRadius( spellEffectSize, &sx, &sy );
614
615 // walk around the circle
616 set<Creature*> seen;
617 Creature *targets[100];
618 int targetCount = 0;
619 Creature *c = battle->getCreature()->getTargetCreature();
620 DisplayInfo di;
621 for ( int r = spellEffectSize; r; r += spellEffectSize ) {
622 if ( r > radius ) r = radius;
623
624 di.red = 0.1f;
625 di.green = 1 - static_cast<float>( r ) / static_cast<float>( radius );
626 di.blue = static_cast<float>( r ) / static_cast<float>( radius );
627
628 for ( int angle = 0; angle < 360; angle += 10 ) {
629 int x = toint( sx + ( static_cast<float>( r ) * Constants::cosFromAngle( static_cast<float>( angle ) ) ) );
630 int y = toint( sy - ( static_cast<float>( r ) * Constants::sinFromAngle( static_cast<float>( angle ) ) ) );
631 if ( x >= 0 && x < MAP_WIDTH && y >= 0 && y < MAP_DEPTH ) {
632 // Location *pos = battle->getSession()->getMap()->getLocation( x, y, 0 );
633 battle->getSession()->getMap()->startEffect( x, y, 1, Constants::EFFECT_GREEN,
634 ( Constants::DAMAGE_DURATION * 4 ),
635 spellEffectSize, spellEffectSize,
636 ( GLuint )( r * 25 ), false, &di );
637 targetCount = battle->getSession()->getMap()->getCreaturesInArea( x, y, spellEffectSize, ( RenderedCreature** )targets );
638 for ( int i = 0; i < targetCount; i++ ) {
639 if ( seen.find( targets[ i ] ) == seen.end() &&
640 battle->getCreature()->canAttack( targets[ i ] ) ) {
641 seen.insert( targets[ i ] );
642 battle->getCreature()->setTargetCreature( targets[ i ] );
643 causeDamage( true );
644 // knock the creature back
645 int cx = toint( targets[ i ]->getX() );
646 int cy = toint( targets[ i ]->getY() );
647 int px = toint( cx + ( 2.0f * Constants::cosFromAngle( static_cast<float>( angle ) ) ) );
648 int py = toint( cy - ( 2.0f * Constants::sinFromAngle( static_cast<float>( angle ) ) ) );
649 if ( !( battle->getSession()->getMap()->
650 moveCreature( cx, cy, 0,
651 px, py, 0,
652 targets[ i ] ) ) ) {
653 targets[ i ]->moveTo( px, py, 0 );
654 }
655 }
656 }
657 }
658 }
659
660 if ( r >= radius ) break;
661 }
662 battle->getCreature()->setTargetCreature( c );
663 }
664
665
circleAttack()666 void SpellCaster::circleAttack() {
667
668 int spellEffectSize = 2;
669 float sx, sy;
670 int radius = getRadius( spellEffectSize, &sx, &sy );
671
672 // walk around the circle
673 Creature *targets[100];
674 int targetCount = 0;
675 Creature *c = battle->getCreature()->getTargetCreature();
676 for ( int angle = 0; angle < 360; angle += 10 ) {
677 int x = toint( sx + ( static_cast<float>( radius ) * Constants::cosFromAngle( static_cast<float>( angle ) ) ) );
678 int y = toint( sy - ( static_cast<float>( radius ) * Constants::sinFromAngle( static_cast<float>( angle ) ) ) );
679 if ( x >= 0 && x < MAP_WIDTH && y >= 0 && y < MAP_DEPTH ) {
680 // Location *pos = battle->getSession()->getMap()->getLocation( x, y, 0 );
681 battle->getSession()->getMap()->startEffect( x, y, 1, Constants::EFFECT_DUST,
682 ( Constants::DAMAGE_DURATION * 4 ),
683 spellEffectSize, spellEffectSize,
684 ( GLuint )( angle * 5 ) );
685 targetCount = battle->getSession()->getMap()->getCreaturesInArea( x, y, spellEffectSize, ( RenderedCreature** )targets );
686 for ( int i = 0; i < targetCount; i++ ) {
687 if ( battle->getCreature()->canAttack( targets[ i ] ) ) {
688 battle->getCreature()->setTargetCreature( targets[ i ] );
689 causeDamage( true );
690 }
691 }
692 }
693 }
694 battle->getCreature()->setTargetCreature( c );
695 }
696
hailAttack()697 void SpellCaster::hailAttack() {
698
699 float sx, sy;
700 int spellEffectSize = 2;
701 int radius = getRadius( spellEffectSize, &sx, &sy );
702
703 // pick random locations in the circle
704 for ( int i = 0; i < radius * 2 + 2; i++ ) {
705 int x = static_cast<int>( Util::roll( sx - radius, sx + radius ) );
706 int y = static_cast<int>( Util::roll( sy - radius, sy + radius ) );
707
708 if ( x >= 0 && x < MAP_WIDTH && y >= 0 && y < MAP_DEPTH ) {
709 // Location *pos = battle->getSession()->getMap()->getLocation( x, y, 0 );
710 battle->getSession()->getMap()->startEffect( x, y, 1, Constants::EFFECT_HAIL,
711 ( Constants::DAMAGE_DURATION * 4 ),
712 1, 1,
713 ( GLuint )( i * 50 ) );
714 }
715 }
716
717 // select targets
718 Creature *targets[100];
719 int targetCount = 0;
720 Creature *c = battle->getCreature()->getTargetCreature();
721 targetCount = battle->getSession()->getMap()->getCreaturesInArea( toint( battle->getCreature()->getX() ),
722 toint( battle->getCreature()->getY() ),
723 radius,
724 ( RenderedCreature** )targets );
725 for ( int i = 0; i < targetCount; i++ ) {
726 if ( battle->getCreature()->canAttack( targets[ i ] ) ) {
727 battle->getCreature()->setTargetCreature( targets[ i ] );
728 // FIXME: Spellcaster::causeDamage() must have first parameter of type bool not GLuint
729 causeDamage( ( GLuint )( i * 50 ) );
730 }
731 }
732 battle->getCreature()->setTargetCreature( c );
733 }
734
getRadius(int spellEffectSize,float * sx,float * sy)735 int SpellCaster::getRadius( int spellEffectSize, float *sx, float *sy ) {
736
737 //cerr << "target x=" << battle->getCreature()->getTargetX() << "," << battle->getCreature()->getTargetY() << endl;
738
739 int tw = 1;
740 int td = 1;
741 Location *pos = battle->getSession()->getMap()->getLocation( battle->getCreature()->getTargetX(),
742 battle->getCreature()->getTargetY(),
743 0 );
744 if ( pos && pos->shape ) {
745 tw = pos->shape->getWidth();
746 td = pos->shape->getDepth();
747 }
748
749 int selectedRadius = static_cast<int>( Constants::distance( battle->getCreature()->getX(), battle->getCreature()->getY(),
750 battle->getCreature()->getShape()->getWidth(),
751 battle->getCreature()->getShape()->getDepth(),
752 battle->getCreature()->getTargetX(), battle->getCreature()->getTargetY(),
753 tw, td ) );
754 selectedRadius += toint( battle->getCreature()->getShape()->getWidth() / 2.0f + tw / 2.0f + spellEffectSize );
755
756 // cap the selected radius
757 int radius = level * 2;
758 if ( radius > 15 ) radius = 15;
759 if ( radius < 2 ) radius = 2;
760
761 //cerr << "selected radius=" << selectedRadius << " max radius=" << radius << endl;
762 if ( selectedRadius < radius ) radius = selectedRadius;
763 //cerr << "radius=" << radius << endl;
764
765 *sx = battle->getCreature()->getX() + ( battle->getCreature()->getShape()->getWidth() / 2.0f );
766 *sy = battle->getCreature()->getY() - ( battle->getCreature()->getShape()->getDepth() / 2.0f );
767
768 // show radius effect
769 battle->getSession()->getMap()->startEffect( toint( *sx ), toint( *sy ), 1,
770 Constants::EFFECT_RING, ( Constants::DAMAGE_DURATION * 4 ),
771 radius, radius );
772
773 return radius;
774 }
775
776
777