/** * Repeaters are multiple hazards that spawn in a straight line */ bool PowerManager::repeater(int power_index, StatBlock *src_stats, Point target) { // pay costs if (powers[power_index].requires_mp>0) src_stats->mp-=powers[power_index].requires_mp; used_item = powers[power_index].requires_item; //initialize variables Hazard *haz[10]; FPoint location_iterator; FPoint speed; int delay_iterator; int map_speed = 64; // calculate speed float dx = (float)(target.x - src_stats->pos.x); float dy = (float)(target.y - src_stats->pos.y); float theta = atan(dy/dx); speed.x = (float)map_speed * cos(theta); speed.y = (float)map_speed * sin(theta); if (dx > 0.0 && speed.x < 0.0 || dx < 0.0 && speed.x > 0.0) speed.x *= -1.0; if (dy > 0.0 && speed.y < 0.0 || dy < 0.0 && speed.y > 0.0) speed.y *= -1.0; location_iterator.x = (float)src_stats->pos.x; location_iterator.y = (float)src_stats->pos.y; delay_iterator = 0; playSound(power_index, src_stats); for (int i=0; i<powers[power_index].repeater_num; i++) { location_iterator.x += speed.x; location_iterator.y += speed.y; // only travels until it hits a wall if (collider->is_wall((int)location_iterator.x, (int)location_iterator.y)) { break; // no more hazards } haz[i] = new Hazard(); initHazard(power_index, src_stats, target, haz[i]); haz[i]->pos.x = location_iterator.x; haz[i]->pos.y = location_iterator.y; haz[i]->delay_frames = delay_iterator; delay_iterator += powers[power_index].delay; haz[i]->frame = powers[power_index].start_frame; // start at bottom frame hazards.push(haz[i]); } return true; }
/** * The activated power creates a group of missile hazards (e.g. arrow, thrown knife, firebolt). * Each individual missile is a single animated hazard that travels from the caster position to the * mouse target position. * * @param power_index The activated power ID * @param src_stats The StatBlock of the power activator * @param target The mouse cursor position in map coordinates * return boolean true if successful */ bool PowerManager::missile(int power_index, StatBlock *src_stats, Point target) { float pi = 3.1415926535898; Point src; if (powers[power_index].starting_pos == STARTING_POS_TARGET) { src.x = target.x; src.y = target.y; } else { src.x = src_stats->pos.x; src.y = src_stats->pos.y; } Hazard *haz; // calculate polar coordinates angle float theta = calcTheta(src.x, src.y, target.x, target.y); //generate hazards for (int i=0; i < powers[power_index].missile_num; i++) { haz = new Hazard(); //calculate individual missile angle float offset_angle = ((1.0 - powers[power_index].missile_num)/2 + i) * (powers[power_index].missile_angle * pi / 180.0); float variance = 0; if (powers[power_index].angle_variance != 0) variance = pow(-1.0f, (rand() % 2) - 1) * (rand() % powers[power_index].angle_variance) * pi / 180.0; //random between 0 and angle_variance away float alpha = theta + offset_angle + variance; while (alpha >= pi+pi) alpha -= pi+pi; while (alpha < 0.0) alpha += pi+pi; initHazard(power_index, src_stats, target, haz); //calculate the missile velocity int speed_var = 0; if (powers[power_index].speed_variance != 0) speed_var = (int)(pow(-1.0f, (rand() % 2) - 1) * (rand() % powers[power_index].speed_variance + 1) - 1); haz->speed.x = (haz->base_speed + speed_var) * cos(alpha); haz->speed.y = (haz->base_speed + speed_var) * sin(alpha); //calculate direction based on trajectory, not actual target (UNITS_PER_TILE reduces round off error) if (powers[power_index].directional) haz->direction = calcDirection(src.x, src.y, src.x + UNITS_PER_TILE * haz->speed.x, src.y + UNITS_PER_TILE * haz->speed.y); hazards.push(haz); } // if all else succeeded, pay costs if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp; if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item; playSound(power_index, src_stats); return true; }
/** * Repeaters are multiple hazards that spawn in a straight line */ bool PowerManager::repeater(int power_index, StatBlock *src_stats, Point target) { // pay costs up front if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp; if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item; //initialize variables Hazard *haz[10]; FPoint location_iterator; FPoint speed; int delay_iterator; int map_speed = 64; // calculate polar coordinates angle float theta = calcTheta(src_stats->pos.x, src_stats->pos.y, target.x, target.y); speed.x = (float)map_speed * cos(theta); speed.y = (float)map_speed * sin(theta); location_iterator.x = (float)src_stats->pos.x; location_iterator.y = (float)src_stats->pos.y; delay_iterator = 0; playSound(power_index, src_stats); for (int i=0; i<powers[power_index].repeater_num; i++) { location_iterator.x += speed.x; location_iterator.y += speed.y; // only travels until it hits a wall if (collider->is_wall((int)location_iterator.x, (int)location_iterator.y)) { break; // no more hazards } haz[i] = new Hazard(); initHazard(power_index, src_stats, target, haz[i]); haz[i]->pos.x = location_iterator.x; haz[i]->pos.y = location_iterator.y; haz[i]->delay_frames = delay_iterator; delay_iterator += powers[power_index].delay; haz[i]->frame = powers[power_index].start_frame; // start at bottom frame hazards.push(haz[i]); } return true; }
/** * The activated power creates a group of missile hazards (e.g. arrow, thrown knife, firebolt). * Each individual missile is a single animated hazard that travels from the caster position to the * mouse target position. * * @param power_index The activated power ID * @param src_stats The StatBlock of the power activator * @param target The mouse cursor position in map coordinates * return boolean true if successful */ bool PowerManager::missile(int power_index, StatBlock *src_stats, Point target) { float pi = 3.1415926535898; Hazard *haz[powers[power_index].missile_num]; // calculate base angle float dx = (float)target.x - (float)src_stats->pos.x; float dy = (float)target.y - (float)src_stats->pos.y; float theta = atan(dy/dx); if (dx > 0) theta += pi; //theta corrector //generate hazards for (int i=0; i < powers[power_index].missile_num; i++) { haz[i] = new Hazard(); Point rot_target; //calculate individual missile angle float offset_angle = ((1.0 - powers[power_index].missile_num)/2 + i) * (powers[power_index].missile_angle * pi / 180.0); float variance = 0; if (powers[power_index].angle_variance != 0) variance = pow(-1, (rand() % 2) - 1) * (rand() % powers[power_index].angle_variance) * pi / 180.0; //random between 0 and angle_variance away float alpha = theta + offset_angle + variance; while (alpha >= 2 * pi) alpha -= 2 * pi; while (alpha < 0) alpha += 2 * pi; //calculate animation direction (the UNITS_PER_TILE just reduces round-off error) rot_target.x = src_stats->pos.x - UNITS_PER_TILE * cos(alpha); rot_target.y = src_stats->pos.y - UNITS_PER_TILE * sin(alpha); initHazard(power_index, src_stats, rot_target, haz[i]); //calculate the missile velocity int speed_var = 0; if (powers[power_index].speed_variance != 0) speed_var = pow(-1, (rand() % 2) - 1) * (rand() % powers[power_index].speed_variance + 1) - 1; haz[i]->speed.x = (haz[0]->base_speed + speed_var) * -cos(alpha); haz[i]->speed.y = (haz[0]->base_speed + speed_var) * -sin(alpha); hazards.push(haz[i]); } // pay costs if (powers[power_index].requires_mp>0) src_stats->mp-=powers[power_index].requires_mp; used_item = powers[power_index].requires_item; playSound(power_index, src_stats); return true; }
/** * The activated power creates a static effect (not a moving hazard) * * @param power_index The activated power ID * @param src_stats The StatBlock of the power activator * @param target The mouse cursor position in map coordinates * return boolean true if successful */ bool PowerManager::effect(int power_index, StatBlock *src_stats, Point target) { if (powers[power_index].use_hazard) { Hazard *haz = new Hazard(); initHazard(power_index, src_stats, target, haz); // Hazard memory is now the responsibility of HazardManager hazards.push(haz); } buff(power_index, src_stats, target); // If there's a sound effect, play it here playSound(power_index, src_stats); // if all else succeeded, pay costs if (src_stats->hero && powers[power_index].requires_mp > 0) src_stats->mp -= powers[power_index].requires_mp; if (src_stats->hero && powers[power_index].requires_item != -1) used_item = powers[power_index].requires_item; return true; }
/** * Apply basic power info to a new hazard. * * This can be called several times to combine powers. * Typically done when a base power can be modified by equipment * (e.g. ammo type affects the traits of powers that shoot) * * @param power_index The activated power ID * @param src_stats The StatBlock of the power activator * @param target Aim position in map coordinates * @param haz A newly-initialized hazard */ void PowerManager::initHazard(int power_index, StatBlock *src_stats, Point target, Hazard *haz) { //the hazard holds the statblock of its source haz->src_stats = src_stats; // Hazard attributes based on power source haz->crit_chance = src_stats->crit; haz->accuracy = src_stats->accuracy; // Hazard damage depends on equipped weapons and the power's optional damage_multiplier if (powers[power_index].base_damage == BASE_DAMAGE_MELEE) { haz->dmg_min = src_stats->dmg_melee_min; haz->dmg_max = src_stats->dmg_melee_max; } else if (powers[power_index].base_damage == BASE_DAMAGE_RANGED) { haz->dmg_min = src_stats->dmg_ranged_min; haz->dmg_max = src_stats->dmg_ranged_max; } else if (powers[power_index].base_damage == BASE_DAMAGE_MENT) { haz->dmg_min = src_stats->dmg_ment_min; haz->dmg_max = src_stats->dmg_ment_max; } //apply the multiplier haz->dmg_min = ceil(haz->dmg_min * powers[power_index].damage_multiplier / 100.0); haz->dmg_max = ceil(haz->dmg_max * powers[power_index].damage_multiplier / 100.0); // Only apply stats from powers that are not defaults // If we do this, we can init with multiple power layers // (e.g. base spell plus weapon type) if (powers[power_index].gfx_index != -1) { haz->sprites = gfx[powers[power_index].gfx_index]; } if (powers[power_index].rendered) { haz->rendered = powers[power_index].rendered; } if (powers[power_index].lifespan != 0) { haz->lifespan = powers[power_index].lifespan; } if (powers[power_index].frame_loop != 1) { haz->frame_loop = powers[power_index].frame_loop; } if (powers[power_index].frame_duration != 1) { haz->frame_duration = powers[power_index].frame_duration; } if (powers[power_index].frame_size.x != 0) { haz->frame_size.x = powers[power_index].frame_size.x; } if (powers[power_index].frame_size.y != 0) { haz->frame_size.y = powers[power_index].frame_size.y; } if (powers[power_index].frame_offset.x != 0) { haz->frame_offset.x = powers[power_index].frame_offset.x; } if (powers[power_index].frame_offset.y != 0) { haz->frame_offset.y = powers[power_index].frame_offset.y; } if (powers[power_index].directional) { haz->direction = calcDirection(src_stats->pos.x, src_stats->pos.y, target.x, target.y); } else if (powers[power_index].visual_random != 0) { haz->visual_option = rand() % powers[power_index].visual_random; } else if (powers[power_index].visual_option != 0) { haz->visual_option = powers[power_index].visual_option; } haz->floor = powers[power_index].floor; if (powers[power_index].speed > 0) { haz->base_speed = powers[power_index].speed; } if (powers[power_index].complete_animation) { haz->complete_animation = true; } // combat traits if (powers[power_index].no_attack) { haz->active = false; } if (powers[power_index].multitarget) { haz->multitarget = true; } if (powers[power_index].active_frame != -1) { haz->active_frame = powers[power_index].active_frame; } if (powers[power_index].radius != 0) { haz->radius = powers[power_index].radius; } if (powers[power_index].trait_armor_penetration) { haz->trait_armor_penetration = true; } haz->trait_crits_impaired = powers[power_index].trait_crits_impaired; if (powers[power_index].trait_elemental) { haz->trait_elemental = powers[power_index].trait_elemental; } // status effect durations // durations stack when combining powers (e.g. base power and weapon/ammo type) haz->bleed_duration += powers[power_index].bleed_duration; haz->stun_duration += powers[power_index].stun_duration; haz->slow_duration += powers[power_index].slow_duration; haz->immobilize_duration += powers[power_index].immobilize_duration; // steal effects haz->hp_steal += powers[power_index].hp_steal; haz->mp_steal += powers[power_index].mp_steal; // hazard starting position if (powers[power_index].starting_pos == STARTING_POS_SOURCE) { haz->pos.x = (float)src_stats->pos.x; haz->pos.y = (float)src_stats->pos.y; } else if (powers[power_index].starting_pos == STARTING_POS_TARGET) { haz->pos.x = (float)target.x; haz->pos.y = (float)target.y; } else if (powers[power_index].starting_pos == STARTING_POS_MELEE) { haz->pos = calcVector(src_stats->pos, src_stats->direction, src_stats->melee_range); } // pre/post power effects if (powers[power_index].post_power != -1) { haz->post_power = powers[power_index].post_power; } if (powers[power_index].wall_power != -1) { haz->wall_power = powers[power_index].wall_power; } // if equipment has special powers, apply it here (if it hasn't already been applied) if (!haz->equipment_modified && powers[power_index].allow_power_mod) { if (powers[power_index].base_damage == BASE_DAMAGE_MELEE && src_stats->melee_weapon_power != -1) { haz->equipment_modified = true; initHazard(src_stats->melee_weapon_power, src_stats, target, haz); } else if (powers[power_index].base_damage == BASE_DAMAGE_MENT && src_stats->mental_weapon_power != -1) { haz->equipment_modified = true; initHazard(src_stats->mental_weapon_power, src_stats, target, haz); } else if (powers[power_index].base_damage == BASE_DAMAGE_RANGED && src_stats->ranged_weapon_power != -1) { haz->equipment_modified = true; initHazard(src_stats->ranged_weapon_power, src_stats, target, haz); } } }