PageRenderTime 101ms CodeModel.GetById 15ms app.highlight 79ms RepoModel.GetById 1ms app.codeStats 1ms

/src/Avatar.cpp

http://github.com/clintbellanger/flare
C++ | 708 lines | 480 code | 125 blank | 103 comment | 217 complexity | e189af7a96010404e101275fcaa49e44 MD5 | raw file
  1/*
  2Copyright 2011 Clint Bellanger
  3
  4This file is part of FLARE.
  5
  6FLARE is free software: you can redistribute it and/or modify it under the terms
  7of the GNU General Public License as published by the Free Software Foundation,
  8either version 3 of the License, or (at your option) any later version.
  9
 10FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
 11WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 12PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 13
 14You should have received a copy of the GNU General Public License along with
 15FLARE.  If not, see http://www.gnu.org/licenses/
 16*/
 17
 18/**
 19 * class Avatar
 20 *
 21 * Contains logic and rendering routines for the player avatar.
 22 */
 23
 24#include "Avatar.h"
 25#include "SharedResources.h"
 26
 27#include <sstream>
 28
 29using namespace std;
 30
 31
 32Avatar::Avatar(PowerManager *_powers, MapIso *_map) : Entity(_map), powers(_powers) {
 33	
 34	init();
 35	
 36	// default hero animation data
 37	stats.cooldown = 4;
 38
 39	// load the hero's animations from hero definition file
 40	loadAnimations("animations/hero.txt");
 41}
 42
 43void Avatar::init() {
 44
 45	// name, base, look are set by GameStateNew so don't reset it here
 46
 47	// other init
 48	sprites = 0;
 49	stats.cur_state = AVATAR_STANCE;
 50	stats.pos.x = map->spawn.x;
 51	stats.pos.y = map->spawn.y;
 52	stats.direction = map->spawn_dir;
 53	current_power = -1;
 54	newLevelNotification = false;
 55		
 56	lockSwing = false;
 57	lockCast = false;
 58	lockShoot = false;
 59	
 60	stats.hero = true;
 61	stats.level = 1;
 62	stats.xp = 0;
 63	stats.physical_character = 1;
 64	stats.mental_character = 1;
 65	stats.offense_character = 1;
 66	stats.defense_character = 1;
 67	stats.physical_additional = 0;
 68	stats.mental_additional = 0;
 69	stats.offense_additional = 0;
 70	stats.defense_additional = 0;
 71	stats.speed = 14;
 72	stats.dspeed = 10;
 73	stats.recalc();
 74	
 75	log_msg = "";
 76
 77	stats.cooldown_ticks = 0;
 78	
 79	haz = NULL;
 80
 81	img_main = "";
 82	img_armor = "";
 83	img_off = "";
 84
 85	for (int i = 0; i < POWER_COUNT; i++) {
 86		stats.hero_cooldown[i] = 0;
 87	}
 88	
 89	for (int i=0; i<4; i++) {
 90		sound_steps[i] = NULL;
 91	}
 92}
 93
 94void Avatar::loadGraphics(const string& _img_main, string _img_armor, const string& _img_off) {
 95	SDL_Surface *gfx_main = NULL;
 96	SDL_Surface *gfx_off = NULL;
 97	SDL_Surface *gfx_head = NULL;
 98	SDL_Rect src;
 99	SDL_Rect dest;
100	
101	// Default appearance
102	if (_img_armor == "") _img_armor = "clothes";
103	
104	// Check if we really need to change the graphics
105	if (_img_main != img_main || _img_armor != img_armor || _img_off != img_off) {
106		img_main = _img_main;
107		img_armor = _img_armor;
108		img_off = _img_off;
109	
110		// composite the hero graphic
111		if (sprites) SDL_FreeSurface(sprites);
112		sprites = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_armor + ".png").c_str());
113		if (img_main != "") gfx_main = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_main + ".png").c_str());
114		if (img_off != "") gfx_off = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + img_off + ".png").c_str());
115		gfx_head = IMG_Load(mods->locate("images/avatar/" + stats.base + "/" + stats.head + ".png").c_str());
116
117		SDL_SetColorKey( sprites, SDL_SRCCOLORKEY, SDL_MapRGB(sprites->format, 255, 0, 255) ); 
118		if (gfx_main) SDL_SetColorKey( gfx_main, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_main->format, 255, 0, 255) ); 
119		if (gfx_off) SDL_SetColorKey( gfx_off, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_off->format, 255, 0, 255) ); 
120		if (gfx_head) SDL_SetColorKey( gfx_head, SDL_SRCCOLORKEY, SDL_MapRGB(gfx_head->format, 255, 0, 255) ); 
121		
122		// assuming the hero is right-handed, we know the layer z-order
123		// copy the furthest hand first
124		src.w = dest.w = 4096;
125		src.h = dest.h = 256;
126		src.x = dest.x = 0;
127		src.y = dest.y = 0;
128		if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 0,1 main hand
129		src.y = dest.y = 768;
130		if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 6,7 main hand
131		src.h = dest.h = 512;
132		src.y = dest.y = 256;
133		if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 2-5 off hand
134		
135		// copy the head in the middle
136		src.h = dest.h = 1024;
137		src.y = dest.y = 0;
138		if (gfx_head) SDL_BlitSurface(gfx_head, &src, sprites, &dest); // head
139		
140		// copy the closest hand last
141		src.w = dest.w = 4096;
142		src.h = dest.h = 256;
143		src.x = dest.x = 0;
144		src.y = dest.y = 0;
145		if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 0,1 off hand
146		src.y = dest.y = 768;
147		if (gfx_off) SDL_BlitSurface(gfx_off, &src, sprites, &dest); // row 6,7 off hand
148		src.h = dest.h = 512;
149		src.y = dest.y = 256;
150		if (gfx_main) SDL_BlitSurface(gfx_main, &src, sprites, &dest); // row 2-5 main hand
151		
152		if (gfx_main) SDL_FreeSurface(gfx_main);
153		if (gfx_off) SDL_FreeSurface(gfx_off);
154		if (gfx_head) SDL_FreeSurface(gfx_head);
155		
156		// optimize
157		SDL_Surface *cleanup = sprites;
158		sprites = SDL_DisplayFormatAlpha(sprites);
159		SDL_FreeSurface(cleanup);
160	}
161}
162
163void Avatar::loadSounds() {
164	sound_melee = Mix_LoadWAV(mods->locate("soundfx/melee_attack.ogg").c_str());
165	sound_hit = Mix_LoadWAV(mods->locate("soundfx/" + stats.base + "_hit.ogg").c_str());
166	sound_die = Mix_LoadWAV(mods->locate("soundfx/" + stats.base + "_die.ogg").c_str());
167	sound_block = Mix_LoadWAV(mods->locate("soundfx/powers/block.ogg").c_str());
168	level_up = Mix_LoadWAV(mods->locate("soundfx/level_up.ogg").c_str());
169				
170	if (!sound_melee || !sound_hit || !sound_die || !level_up) {
171		printf("Mix_LoadWAV: %s\n", Mix_GetError());
172	}
173}
174
175/**
176 * Walking/running steps sound depends on worn armor
177 */
178void Avatar::loadStepFX(const string& stepname) {
179	
180	// TODO: put default step sound in engine config file
181	string filename = "cloth";
182	if (stepname != "") {
183		filename = stepname;
184	}
185
186	// clear previous sounds
187	for (int i=0; i<4; i++) {
188		if (sound_steps[i] != NULL) {
189			Mix_FreeChunk(sound_steps[i]);
190			sound_steps[i] = NULL;
191		}
192	}
193	
194	// load new sounds
195	sound_steps[0] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "1.ogg").c_str());
196	sound_steps[1] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "2.ogg").c_str());
197	sound_steps[2] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "3.ogg").c_str());
198	sound_steps[3] = Mix_LoadWAV(mods->locate("soundfx/steps/step_" + filename + "4.ogg").c_str());
199	
200}
201
202
203bool Avatar::pressing_move() {
204	if(MOUSE_MOVE) {
205		return inp->pressing[MAIN1];
206	} else {
207		return inp->pressing[UP] || inp->pressing[DOWN] || inp->pressing[LEFT] || inp->pressing[RIGHT];
208	}	
209}
210
211void Avatar::set_direction() {
212	// handle direction changes
213	if(MOUSE_MOVE) {
214		Point target = screen_to_map(inp->mouse.x,  inp->mouse.y, stats.pos.x, stats.pos.y);
215		// if no line of movement to target, use pathfinder
216		if( !map->collider.line_of_movement(stats.pos.x, stats.pos.y, target.x, target.y) ) {
217			vector<Point> path;
218			// if a path is returned, target first waypoint
219			if ( map->collider.compute_path(stats.pos,target,path,1000) ) {
220				target = path.back();
221			}
222		}
223		stats.direction = face(target.x, target.y);
224	} else {
225		if(inp->pressing[UP] && inp->pressing[LEFT]) stats.direction = 1;
226		else if(inp->pressing[UP] && inp->pressing[RIGHT]) stats.direction = 3;
227		else if(inp->pressing[DOWN] && inp->pressing[RIGHT]) stats.direction = 5;
228		else if(inp->pressing[DOWN] && inp->pressing[LEFT]) stats.direction = 7;
229		else if(inp->pressing[LEFT]) stats.direction = 0;
230		else if(inp->pressing[UP]) stats.direction = 2;
231		else if(inp->pressing[RIGHT]) stats.direction = 4;
232		else if(inp->pressing[DOWN]) stats.direction = 6;
233	}
234}
235
236/**
237 * logic()
238 * Handle a single frame.  This includes:
239 * - move the avatar based on buttons pressed
240 * - calculate the next frame of animation
241 * - calculate camera position based on avatar position
242 *
243 * @param power_index The actionbar power activated.  -1 means no power.
244 */
245void Avatar::logic(int actionbar_power, bool restrictPowerUse) {
246
247	Point target;
248	int stepfx;
249	stats.logic();
250	if (stats.forced_move_duration > 0) {
251		move();
252		// calc new cam position from player position
253		// cam is focused at player position
254		map->cam.x = stats.pos.x;
255		map->cam.y = stats.pos.y;
256		map->hero_tile.x = stats.pos.x / 32;
257		map->hero_tile.y = stats.pos.y / 32;
258		return;
259	}
260	if (stats.stun_duration > 0) return;
261	bool allowed_to_move;
262	bool allowed_to_use_power;
263	
264	// check level up
265	int max_spendable_stat_points = 16;
266	if (stats.xp >= stats.xp_table[stats.level] && stats.level < MAX_CHARACTER_LEVEL) {
267		stats.level++;
268		stringstream ss;
269		ss << msg->get("Congratulations, you have reached level %d!", stats.level);
270		if (stats.level < max_spendable_stat_points) {
271			ss << " " << msg->get("You may increase one attribute through the Character Menu.");
272			newLevelNotification = true;
273		}
274		log_msg = ss.str();
275		stats.recalc();
276		Mix_PlayChannel(-1, level_up, 0);
277	}
278
279	// check for bleeding spurt
280	if (stats.bleed_duration % 30 == 1) {
281		powers->activate(POWER_SPARK_BLOOD, &stats, stats.pos);
282	}
283	// check for bleeding to death
284	if (stats.hp == 0 && !(stats.cur_state == AVATAR_DEAD)) {
285		stats.cur_state = AVATAR_DEAD;
286	}
287	
288	// assist mouse movement
289	if (!inp->pressing[MAIN1]) drag_walking = false;
290	
291	// handle animation
292	activeAnimation->advanceFrame();
293			
294	switch(stats.cur_state) {
295		case AVATAR_STANCE:
296
297			setAnimation("stance");
298		
299			// allowed to move or use powers?
300			if (MOUSE_MOVE) {
301				allowed_to_move = restrictPowerUse && (!inp->lock[MAIN1] || drag_walking);
302				allowed_to_use_power = !allowed_to_move;
303			}
304			else {
305				allowed_to_move = true;
306				allowed_to_use_power = true;
307			}
308
309			// handle transitions to RUN
310			if (allowed_to_move)
311				set_direction();
312			
313			if (pressing_move() && allowed_to_move) {
314				if (MOUSE_MOVE && inp->pressing[MAIN1]) {
315					inp->lock[MAIN1] = true;
316					drag_walking = true;
317				}
318				
319				if (move()) { // no collision
320					stats.cur_state = AVATAR_RUN;
321				}
322
323			}
324			// handle power usage
325			if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) {				
326				target = screen_to_map(inp->mouse.x,  inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y);
327			
328				// check requirements
329				if (powers->powers[actionbar_power].requires_mp > stats.mp)
330					break;
331				if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical)
332					break;
333				if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental)
334					break;
335				if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense)
336					break;
337				if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y))
338					break;
339				if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y))
340					break;
341				if (stats.hero_cooldown[actionbar_power] > 0)
342					break;
343
344				stats.hero_cooldown[actionbar_power] = powers->powers[actionbar_power].cooldown; //set the cooldown timer
345				current_power = actionbar_power;
346				act_target.x = target.x;
347				act_target.y = target.y;
348			
349				// is this a power that requires changing direction?
350				if (powers->powers[current_power].face) {
351					stats.direction = face(target.x, target.y);
352				}
353			
354				// handle melee powers
355				if (powers->powers[current_power].new_state == POWSTATE_SWING) {
356					stats.cur_state = AVATAR_MELEE;
357					break;
358				}
359				// handle ranged powers
360				if (powers->powers[current_power].new_state == POWSTATE_SHOOT) {
361					stats.cur_state = AVATAR_SHOOT;
362					break;
363				}
364				// handle ment powers
365				if (powers->powers[current_power].new_state == POWSTATE_CAST) {
366					stats.cur_state = AVATAR_CAST;
367					break;
368				}
369				if (powers->powers[current_power].new_state == POWSTATE_BLOCK) {
370					stats.cur_state = AVATAR_BLOCK;
371					stats.blocking = true;
372					break;
373				}
374			}
375			
376			break;
377			
378		case AVATAR_RUN:
379
380			setAnimation("run");
381		
382			stepfx = rand() % 4;
383			
384			if (activeAnimation->getCurFrame() == 1 || activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) {
385				Mix_PlayChannel(-1, sound_steps[stepfx], 0);
386			}
387
388			// allowed to move or use powers?
389			if (MOUSE_MOVE) {
390				allowed_to_use_power = !(restrictPowerUse && !inp->lock[MAIN1]);
391			}
392			else {
393				allowed_to_use_power = true;
394			}
395			
396			// handle direction changes
397			set_direction();
398			
399			// handle transition to STANCE
400			if (!pressing_move()) {
401				stats.cur_state = AVATAR_STANCE;
402				break;
403			} 
404			else if (!move()) { // collide with wall
405				stats.cur_state = AVATAR_STANCE;
406				break;
407			}
408						
409			// handle power usage
410			if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) {
411
412				target = screen_to_map(inp->mouse.x,  inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y);
413			
414				// check requirements
415				if (powers->powers[actionbar_power].requires_mp > stats.mp)
416					break;
417				if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical)
418					break;
419				if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental)
420					break;
421				if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense)
422					break;
423				if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y))
424					break;
425				if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y))
426					break;
427				if (stats.hero_cooldown[actionbar_power] > 0)
428					break;
429
430				stats.hero_cooldown[actionbar_power] = powers->powers[actionbar_power].cooldown; //set the cooldown timer
431				current_power = actionbar_power;
432				act_target.x = target.x;
433				act_target.y = target.y;
434			
435				// is this a power that requires changing direction?
436				if (powers->powers[current_power].face) {
437					stats.direction = face(target.x, target.y);
438				}
439			
440				// handle melee powers
441				if (powers->powers[current_power].new_state == POWSTATE_SWING) {
442					stats.cur_state = AVATAR_MELEE;
443					break;
444				}
445				// handle ranged powers
446				if (powers->powers[current_power].new_state == POWSTATE_SHOOT) {
447					stats.cur_state = AVATAR_SHOOT;
448					break;
449				}
450				// handle ment powers
451				if (powers->powers[current_power].new_state == POWSTATE_CAST) {
452					stats.cur_state = AVATAR_CAST;
453					break;
454				}
455				if (powers->powers[current_power].new_state == POWSTATE_BLOCK) {
456					stats.cur_state = AVATAR_BLOCK;
457					stats.blocking = true;
458					break;
459				}				
460			}
461							
462			break;
463			
464		case AVATAR_MELEE:
465
466			setAnimation("melee");
467
468			if (activeAnimation->getCurFrame() == 1) {
469				Mix_PlayChannel(-1, sound_melee, 0);
470			}
471			
472			// do power
473			if (activeAnimation->getCurFrame()  == activeAnimation->getMaxFrame()/2) {
474				powers->activate(current_power, &stats, act_target);
475			}
476			
477			if (activeAnimation->getTimesPlayed() >= 1) {
478				stats.cur_state = AVATAR_STANCE;
479				if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown;
480			}
481			break;
482
483		case AVATAR_CAST:
484
485			setAnimation("ment");
486
487			// do power
488			if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) {
489				powers->activate(current_power, &stats, act_target);
490			}
491
492			if (activeAnimation->getTimesPlayed() >= 1) {
493				stats.cur_state = AVATAR_STANCE;
494				if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown;
495			}
496			break;
497
498			
499		case AVATAR_SHOOT:
500		
501			setAnimation("ranged");
502
503			// do power
504			if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) {
505				powers->activate(current_power, &stats, act_target);
506			}
507
508			if (activeAnimation->getTimesPlayed() >= 1) {
509				stats.cur_state = AVATAR_STANCE;
510				if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown;
511			}
512			break;
513
514		case AVATAR_BLOCK:
515		
516			setAnimation("block");
517
518			if (powers->powers[actionbar_power].new_state != POWSTATE_BLOCK) {
519				stats.cur_state = AVATAR_STANCE;
520				stats.blocking = false;
521			}
522			break;
523			
524		case AVATAR_HIT:
525
526			setAnimation("hit");
527						 
528			if (activeAnimation->getTimesPlayed() >= 1) {
529				stats.cur_state = AVATAR_STANCE;
530			}
531			
532			break;
533			
534		case AVATAR_DEAD:
535
536			setAnimation("die");
537				
538			if (activeAnimation->getCurFrame() == 1 && activeAnimation->getTimesPlayed() < 1) {
539				Mix_PlayChannel(-1, sound_die, 0);
540				log_msg = msg->get("You are defeated.  You lose half your gold.  Press Enter to continue.");
541			}
542
543			if (activeAnimation->getTimesPlayed() >= 1) {
544				stats.corpse = true;
545			}
546			
547			// allow respawn with Accept
548			if (inp->pressing[ACCEPT]) {
549				stats.hp = stats.maxhp;
550				stats.mp = stats.maxmp;
551				stats.alive = true;
552				stats.corpse = false;
553				stats.cur_state = AVATAR_STANCE;
554				
555				// remove temporary effects
556				stats.clearEffects();
557				
558				// set teleportation variables.  GameEngine acts on these.
559				map->teleportation = true;
560				map->teleport_mapname = map->respawn_map;
561				map->teleport_destination.x = map->respawn_point.x;
562				map->teleport_destination.y = map->respawn_point.y;
563			}
564			
565			break;
566		
567		default:
568			break;
569	}
570	
571	// calc new cam position from player position
572	// cam is focused at player position
573	map->cam.x = stats.pos.x;
574	map->cam.y = stats.pos.y;
575	map->hero_tile.x = stats.pos.x / 32;
576	map->hero_tile.y = stats.pos.y / 32;
577	
578	// check for map events
579	map->checkEvents(stats.pos);
580
581	// decrement all cooldowns
582	for (int i = 0; i < POWER_COUNT; i++){
583		stats.hero_cooldown[i] -= 1000 / FRAMES_PER_SEC;
584		if (stats.hero_cooldown[i] < 0) stats.hero_cooldown[i] = 0;
585	}
586}
587
588/**
589 * Called by HazardManager
590 * Return false on a miss
591 */
592bool Avatar::takeHit(Hazard h) {
593
594	if (stats.cur_state != AVATAR_DEAD) {
595		// check miss
596		int avoidance = stats.avoidance;
597		if (stats.blocking) avoidance *= 2;
598		if (rand() % 100 > (h.accuracy - avoidance + 25)) return false; 
599	
600		int dmg;
601		if (h.dmg_min == h.dmg_max) dmg = h.dmg_min;
602		else dmg = h.dmg_min + (rand() % (h.dmg_max - h.dmg_min + 1));
603	
604		// apply elemental resistance
605		// TODO: make this generic
606		if (h.trait_elemental == ELEMENT_FIRE) {
607			dmg = (dmg * stats.attunement_fire) / 100;
608		}
609		if (h.trait_elemental == ELEMENT_WATER) {
610			dmg = (dmg * stats.attunement_ice) / 100;			
611		}
612	
613		// apply absorption
614		int absorption;
615		if (!h.trait_armor_penetration) { // armor penetration ignores all absorption
616			if (stats.absorb_min == stats.absorb_max) absorption = stats.absorb_min;
617			else absorption = stats.absorb_min + (rand() % (stats.absorb_max - stats.absorb_min + 1));
618			
619			if (stats.blocking) absorption += absorption + stats.absorb_max; // blocking doubles your absorb amount
620			
621			dmg = dmg - absorption;
622			if (dmg < 1 && !stats.blocking) dmg = 1; // when blocking, dmg can be reduced to 0
623			if (dmg <= 0) {
624				dmg = 0;
625				Mix_PlayChannel(-1, sound_block, 0);
626				activeAnimation->reset(); // shield stutter
627			}
628		}
629	
630		
631		int prev_hp = stats.hp;
632		stats.takeDamage(dmg);
633		
634		// after effects
635		if (stats.hp > 0 && stats.immunity_duration == 0 && dmg > 0) {
636			if (h.stun_duration > stats.stun_duration) stats.stun_duration = h.stun_duration;
637			if (h.slow_duration > stats.slow_duration) stats.slow_duration = h.slow_duration;
638			if (h.bleed_duration > stats.bleed_duration) stats.bleed_duration = h.bleed_duration;
639			if (h.immobilize_duration > stats.immobilize_duration) stats.immobilize_duration = h.immobilize_duration;
640			if (h.forced_move_duration > stats.forced_move_duration) stats.forced_move_duration = h.forced_move_duration;
641			if (h.forced_move_speed != 0) {
642				float theta = powers->calcTheta(h.src_stats->pos.x, h.src_stats->pos.y, stats.pos.x, stats.pos.y);
643				stats.forced_speed.x = ceil((float)h.forced_move_speed * cos(theta));
644				stats.forced_speed.y = ceil((float)h.forced_move_speed * sin(theta));
645			}
646			if (h.hp_steal != 0) {
647				h.src_stats->hp += (int)ceil((float)dmg * (float)h.hp_steal / 100.0);
648				if (h.src_stats->hp > h.src_stats->maxhp) h.src_stats->hp = h.src_stats->maxhp;
649			}
650			// if (h.mp_steal != 0) { //enemies don't have MP
651		}
652		
653		// post effect power
654		if (h.post_power >= 0 && dmg > 0) {
655			powers->activate(h.post_power, h.src_stats, stats.pos);
656		}
657		
658		// Power-specific: Vengeance gains stacks when blocking
659		if (stats.blocking && stats.physdef >= 9) {
660			if (stats.vengeance_stacks < 3)
661				stats.vengeance_stacks++;
662		}
663		
664		
665		if (stats.hp <= 0) {
666			stats.cur_state = AVATAR_DEAD;
667			
668			// raise the death penalty flag.  Another module will read this and reset.
669			stats.death_penalty = true;
670		}
671		else if (prev_hp > stats.hp) { // only interrupt if damage was taken
672			Mix_PlayChannel(-1, sound_hit, 0);
673			stats.cur_state = AVATAR_HIT;
674		}
675		
676		return true;
677	}
678	return false;
679}
680
681/**
682 * getRender()
683 * Map objects need to be drawn in Z order, so we allow a parent object (GameEngine)
684 * to collect all mobile sprites each frame.
685 */
686Renderable Avatar::getRender() {
687	Renderable r = activeAnimation->getCurrentFrame(stats.direction);
688	r.sprite = sprites;
689	r.map_pos.x = stats.pos.x;
690	r.map_pos.y = stats.pos.y;
691	return r;
692}
693
694Avatar::~Avatar() {
695
696	SDL_FreeSurface(sprites);
697	Mix_FreeChunk(sound_melee);
698	Mix_FreeChunk(sound_hit);
699	Mix_FreeChunk(sound_die);
700	Mix_FreeChunk(sound_block);
701	Mix_FreeChunk(sound_steps[0]);
702	Mix_FreeChunk(sound_steps[1]);
703	Mix_FreeChunk(sound_steps[2]);
704	Mix_FreeChunk(sound_steps[3]);
705	Mix_FreeChunk(level_up);
706			
707	delete haz;
708}