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