/*---------------------------------------------------------------*/ static MATRIX calcTasUVectorFromAngles(tasReflection r){ double theta, om; theta = calcTheta(r.qe.ki,r.qe.kf,r.angles.sample_two_theta); om = r.angles.a3 - theta; return uFromAngles(om,r.angles.sgu, r.angles.sgl); }
unsigned char calcDirection(float x0, float y0, float x1, float y1) { float theta = calcTheta(x0, y0, x1, y1); float val = theta / (static_cast<float>(M_PI)/4); int dir = static_cast<int>(((val < 0) ? ceil(val-0.5) : floor(val+0.5)) + 4); dir = (dir + 1) % 8; if (dir >= 0 && dir < 8) return static_cast<unsigned char>(dir); else return 0; }
/** * 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 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 polor 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 polor coordinates angle float theta = calcTheta(src_stats->pos.x, src_stats->pos.y, target.x, target.y); //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 >= pi+pi) alpha -= pi+pi; while (alpha < 0.0) alpha += pi+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; }
/*-------------------------------------------------------------------------------*/ int calcTasQAngles(MATRIX UB, MATRIX planeNormal, int ss, tasQEPosition qe, ptasAngles angles){ MATRIX R, QC; double om, q, theta, cos2t; int errorCode = 1; R = buildRMatrix(UB, planeNormal, qe, &errorCode); if(R == NULL){ return errorCode; } angles->sgl = Asind(-R[2][0]); if(ABS(angles->sgl -90.) < .5){ mat_free(R); return BADUBORQ; } /* Now, this is slightly different then in the publication by M. Lumsden. The reason is that the atan2 helps to determine the sign of om whereas the sin, cos formula given by M. Lumsden yield ambiguous signs especially for om. sgu = atan(R[2][1],R[2][2]) where: R[2][1] = cos(sgl)sin(sgu) R[2][2] = cos(sgu)cos(sgl) om = atan(R[1][0],R[0][0]) where: R[1][0] = sin(om)cos(sgl) R[0][0] = cos(om)cos(sgl) The definitions of th R components are taken from M. Lumsden R-matrix definition. */ om = Atan2d(R[1][0],R[0][0]); angles->sgu = Atan2d(R[2][1],R[2][2]); QC = tasReflectionToQC(qe,UB); if(QC == NULL){ mat_free(R); return UBNOMEMORY; } q = vectorLength(QC); q = 2.*PI*vectorLength(QC); cos2t = (qe.ki*qe.ki + qe.kf*qe.kf - q*q)/(2. * ABS(qe.ki) * ABS(qe.kf)); if(ABS(cos2t) > 1.){ mat_free(R); killVector(QC); return TRIANGLENOTCLOSED; } angles->sample_two_theta = ss*Acosd(cos2t); theta = calcTheta(qe.ki, qe.kf,angles->sample_two_theta); angles->a3 = om + theta; /* put a3 into -180, 180 properly. We cal always turn by 180 because the scattering geometry is symmetric in this respect. It is like looking at the scattering plane from the other side */ angles->a3 -= 180.; if(angles->a3 < -180.){ angles->a3 += 360.; } killVector(QC); mat_free(R); return 1; }
/** * Process per-frame actions */ void StatBlock::logic() { if (hp <= 0 && !effects.triggered_death && !effects.revive) alive = false; else alive = true; // handle party buffs if (enemym && powers) { while (!party_buffs.empty()) { int power_index = party_buffs.front(); party_buffs.pop(); Power *buff_power = &powers->powers[power_index]; for (size_t i=0; i < enemym->enemies.size(); ++i) { if(enemym->enemies[i]->stats.hp > 0 && ((enemym->enemies[i]->stats.hero_ally && hero) || (enemym->enemies[i]->stats.enemy_ally && enemym->enemies[i]->stats.summoner == this)) && (buff_power->buff_party_power_id == 0 || buff_power->buff_party_power_id == enemym->enemies[i]->stats.summoned_power_index) ) { powers->effect(&enemym->enemies[i]->stats, this, power_index, (hero ? SOURCE_TYPE_HERO : SOURCE_TYPE_ENEMY)); } } } } // handle effect timers effects.logic(); // apply bonuses from items/effects to base stats applyEffects(); if (hero && effects.refresh_stats) { refresh_stats = true; effects.refresh_stats = false; } // preserve ratio on maxmp and maxhp changes float ratio; if (prev_maxhp != get(STAT_HP_MAX)) { ratio = static_cast<float>(pres_hp) / static_cast<float>(prev_maxhp); hp = static_cast<int>(ratio * static_cast<float>(get(STAT_HP_MAX))); } if (prev_maxmp != get(STAT_MP_MAX)) { ratio = static_cast<float>(pres_mp) / static_cast<float>(prev_maxmp); mp = static_cast<int>(ratio * static_cast<float>(get(STAT_MP_MAX))); } // handle cooldowns if (cooldown_ticks > 0) cooldown_ticks--; // global cooldown for (size_t i=0; i<powers_ai.size(); ++i) { // NPC/enemy powerslot cooldown if (powers_ai[i].ticks > 0) powers_ai[i].ticks--; } // HP regen if (get(STAT_HP_REGEN) > 0 && hp < get(STAT_HP_MAX) && hp > 0) { hp_ticker++; if (hp_ticker >= (60 * MAX_FRAMES_PER_SEC)/get(STAT_HP_REGEN)) { hp++; hp_ticker = 0; } } // MP regen if (get(STAT_MP_REGEN) > 0 && mp < get(STAT_MP_MAX) && hp > 0) { mp_ticker++; if (mp_ticker >= (60 * MAX_FRAMES_PER_SEC)/get(STAT_MP_REGEN)) { mp++; mp_ticker = 0; } } // handle buff/debuff durations if (transform_duration > 0) transform_duration--; // apply bleed if (effects.damage > 0 && hp > 0) { takeDamage(effects.damage); comb->addInt(effects.damage, pos, COMBAT_MESSAGE_TAKEDMG); } if (effects.damage_percent > 0 && hp > 0) { int damage = (get(STAT_HP_MAX)*effects.damage_percent)/100; takeDamage(damage); comb->addInt(damage, pos, COMBAT_MESSAGE_TAKEDMG); } if(effects.death_sentence) hp = 0; if(cooldown_hit_ticks > 0) cooldown_hit_ticks--; if (effects.stun) { // stun stops charge attacks state_ticks = 0; charge_speed = 0; } else if (state_ticks > 0) { state_ticks--; } // apply healing over time if (effects.hpot > 0) { comb->addString(msg->get("+%d HP",effects.hpot), pos, COMBAT_MESSAGE_BUFF); hp += effects.hpot; if (hp > get(STAT_HP_MAX)) hp = get(STAT_HP_MAX); } if (effects.hpot_percent > 0) { int hpot = (get(STAT_HP_MAX)*effects.hpot_percent)/100; comb->addString(msg->get("+%d HP",hpot), pos, COMBAT_MESSAGE_BUFF); hp += hpot; if (hp > get(STAT_HP_MAX)) hp = get(STAT_HP_MAX); } if (effects.mpot > 0) { comb->addString(msg->get("+%d MP",effects.mpot), pos, COMBAT_MESSAGE_BUFF); mp += effects.mpot; if (mp > get(STAT_MP_MAX)) mp = get(STAT_MP_MAX); } if (effects.mpot_percent > 0) { int mpot = (get(STAT_MP_MAX)*effects.mpot_percent)/100; comb->addString(msg->get("+%d MP",mpot), pos, COMBAT_MESSAGE_BUFF); mp += mpot; if (mp > get(STAT_MP_MAX)) mp = get(STAT_MP_MAX); } // set movement type // some creatures may shift between movement types if (intangible) movement_type = MOVEMENT_INTANGIBLE; else if (flying) movement_type = MOVEMENT_FLYING; else movement_type = MOVEMENT_NORMAL; if (hp == 0) removeSummons(); if (effects.knockback_speed != 0) { float theta = calcTheta(knockback_srcpos.x, knockback_srcpos.y, knockback_destpos.x, knockback_destpos.y); knockback_speed.x = effects.knockback_speed * cosf(theta); knockback_speed.y = effects.knockback_speed * sinf(theta); mapr->collider.unblock(pos.x, pos.y); mapr->collider.move(pos.x, pos.y, knockback_speed.x, knockback_speed.y, movement_type, hero); mapr->collider.block(pos.x, pos.y, hero_ally); } else if (charge_speed != 0.0f) { float tmp_speed = charge_speed * speedMultiplyer[direction]; float dx = tmp_speed * static_cast<float>(directionDeltaX[direction]); float dy = tmp_speed * static_cast<float>(directionDeltaY[direction]); mapr->collider.unblock(pos.x, pos.y); mapr->collider.move(pos.x, pos.y, dx, dy, movement_type, hero); mapr->collider.block(pos.x, pos.y, hero_ally); } }
/** * Process per-frame actions */ void StatBlock::logic() { if (hp <= 0 && !effects.triggered_death && !effects.revive) alive = false; else alive = true; // handle effect timers effects.logic(); // apply bonuses from items/effects to base stats applyEffects(); // preserve ratio on maxmp and maxhp changes float ratio; if (prev_maxhp != get(STAT_HP_MAX)) { ratio = static_cast<float>(pres_hp) / static_cast<float>(prev_maxhp); hp = static_cast<int>(ratio * static_cast<float>(get(STAT_HP_MAX))); } if (prev_maxmp != get(STAT_MP_MAX)) { ratio = static_cast<float>(pres_mp) / static_cast<float>(prev_maxmp); mp = static_cast<int>(ratio * static_cast<float>(get(STAT_MP_MAX))); } // handle cooldowns if (cooldown_ticks > 0) cooldown_ticks--; // global cooldown for (size_t i=0; i<powers_ai.size(); ++i) { // NPC/enemy powerslot cooldown if (powers_ai[i].ticks > 0) powers_ai[i].ticks--; } // HP regen if (get(STAT_HP_REGEN) > 0 && hp < get(STAT_HP_MAX) && hp > 0) { hp_ticker++; if (hp_ticker >= (60 * MAX_FRAMES_PER_SEC)/get(STAT_HP_REGEN)) { hp++; hp_ticker = 0; } } // MP regen if (get(STAT_MP_REGEN) > 0 && mp < get(STAT_MP_MAX) && hp > 0) { mp_ticker++; if (mp_ticker >= (60 * MAX_FRAMES_PER_SEC)/get(STAT_MP_REGEN)) { mp++; mp_ticker = 0; } } // handle buff/debuff durations if (transform_duration > 0) transform_duration--; // apply bleed if (effects.damage > 0 && hp > 0) { takeDamage(effects.damage); comb->addMessage(effects.damage, pos, COMBAT_MESSAGE_TAKEDMG); } if (effects.damage_percent > 0 && hp > 0) { int damage = (get(STAT_HP_MAX)*effects.damage_percent)/100; takeDamage(damage); comb->addMessage(damage, pos, COMBAT_MESSAGE_TAKEDMG); } if(effects.death_sentence) hp = 0; if(cooldown_hit_ticks > 0) cooldown_hit_ticks--; // apply healing over time if (effects.hpot > 0) { comb->addMessage(msg->get("+%d HP",effects.hpot), pos, COMBAT_MESSAGE_BUFF); hp += effects.hpot; if (hp > get(STAT_HP_MAX)) hp = get(STAT_HP_MAX); } if (effects.hpot_percent > 0) { int hpot = (get(STAT_HP_MAX)*effects.hpot_percent)/100; comb->addMessage(msg->get("+%d HP",hpot), pos, COMBAT_MESSAGE_BUFF); hp += hpot; if (hp > get(STAT_HP_MAX)) hp = get(STAT_HP_MAX); } if (effects.mpot > 0) { comb->addMessage(msg->get("+%d MP",effects.mpot), pos, COMBAT_MESSAGE_BUFF); mp += effects.mpot; if (mp > get(STAT_MP_MAX)) mp = get(STAT_MP_MAX); } if (effects.mpot_percent > 0) { int mpot = (get(STAT_MP_MAX)*effects.mpot_percent)/100; comb->addMessage(msg->get("+%d MP",mpot), pos, COMBAT_MESSAGE_BUFF); mp += mpot; if (mp > get(STAT_MP_MAX)) mp = get(STAT_MP_MAX); } // set movement type // some creatures may shift between movement types if (intangible) movement_type = MOVEMENT_INTANGIBLE; else if (flying) movement_type = MOVEMENT_FLYING; else movement_type = MOVEMENT_NORMAL; if (hp == 0) removeSummons(); if (effects.knockback_speed != 0) { float theta = calcTheta(knockback_srcpos.x, knockback_srcpos.y, knockback_destpos.x, knockback_destpos.y); knockback_speed.x = effects.knockback_speed * static_cast<float>(cos(theta)); knockback_speed.y = effects.knockback_speed * static_cast<float>(sin(theta)); } if (effects.knockback_speed != 0) { mapr->collider.unblock(pos.x, pos.y); mapr->collider.move(pos.x, pos.y, knockback_speed.x, knockback_speed.y, movement_type, hero); mapr->collider.block(pos.x, pos.y, hero_ally); } }