/** * Whenever a hazard collides with an entity, this function resolves the effect * Called by HazardManager * * Returns false on miss */ bool Entity::takeHit(Hazard &h) { //check if this enemy should be affected by this hazard based on the category if(!powers->powers[h.power_index].target_categories.empty() && !stats.hero) { //the power has a target category requirement, so if it doesnt match, dont continue bool match_found = false; for (unsigned int i=0; i<stats.categories.size(); i++) { if(std::find(powers->powers[h.power_index].target_categories.begin(), powers->powers[h.power_index].target_categories.end(), stats.categories[i]) != powers->powers[h.power_index].target_categories.end()) { match_found = true; } } if(!match_found) return false; } //if the target is already dead, they cannot be hit if ((stats.cur_state == ENEMY_DEAD || stats.cur_state == ENEMY_CRITDEAD) && !stats.hero) return false; if(stats.cur_state == AVATAR_DEAD && stats.hero) return false; //if the target is an enemy and they are not already in combat, activate a beacon to draw other enemies into battle if (!stats.in_combat && !stats.hero && !stats.hero_ally) { stats.join_combat = true; stats.in_combat = true; powers->activate(stats.power_index[BEACON], &stats, stats.pos); //emit beacon } // exit if it was a beacon (to prevent stats.targeted from being set) if (powers->powers[h.power_index].beacon) return false; // prepare the combat text CombatText *combat_text = comb; // if it's a miss, do nothing int accuracy = h.accuracy; if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_MULTIPLY) accuracy = accuracy * powers->powers[h.power_index].mod_accuracy_value / 100; else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ADD) accuracy += powers->powers[h.power_index].mod_accuracy_value; else if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE) accuracy = powers->powers[h.power_index].mod_accuracy_value; int avoidance = 0; if(!powers->powers[h.power_index].trait_avoidance_ignore) { avoidance = stats.get(STAT_AVOIDANCE); } int true_avoidance = 100 - (accuracy + 25 - avoidance); //if we are using an absolute accuracy, offset the constant 25 added to the accuracy if(powers->powers[h.power_index].mod_accuracy_mode == STAT_MODIFIER_MODE_ABSOLUTE) true_avoidance += 25; clampFloor(true_avoidance, MIN_AVOIDANCE); clampCeil(true_avoidance, MAX_AVOIDANCE); if (h.missile && percentChance(stats.get(STAT_REFLECT))) { // reflect the missile 180 degrees h.setAngle(h.angle+M_PI); // change hazard source to match the reflector's type // maybe we should change the source stats pointer to the reflector's StatBlock if (h.source_type == SOURCE_TYPE_HERO || h.source_type == SOURCE_TYPE_ALLY) h.source_type = SOURCE_TYPE_ENEMY; else if (h.source_type == SOURCE_TYPE_ENEMY) h.source_type = stats.hero ? SOURCE_TYPE_HERO : SOURCE_TYPE_ALLY; // reset the hazard ticks h.lifespan = h.base_lifespan; if (activeAnimation->getName() == "block") { play_sfx_block = true; } return false; } if (percentChance(true_avoidance)) { combat_text->addMessage(msg->get("miss"), stats.pos, COMBAT_MESSAGE_MISS); return false; } // calculate base damage int dmg = randBetween(h.dmg_min, h.dmg_max); if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_MULTIPLY) dmg = dmg * powers->powers[h.power_index].mod_damage_value_min / 100; else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ADD) dmg += powers->powers[h.power_index].mod_damage_value_min; else if(powers->powers[h.power_index].mod_damage_mode == STAT_MODIFIER_MODE_ABSOLUTE) dmg = randBetween(powers->powers[h.power_index].mod_damage_value_min, powers->powers[h.power_index].mod_damage_value_max); // apply elemental resistance if (h.trait_elemental >= 0 && unsigned(h.trait_elemental) < stats.vulnerable.size()) { unsigned i = h.trait_elemental; int vulnerable = stats.vulnerable[i]; clampFloor(vulnerable,MIN_RESIST); if (stats.vulnerable[i] < 100) clampCeil(vulnerable,MAX_RESIST); dmg = (dmg * vulnerable) / 100; } if (!h.trait_armor_penetration) { // armor penetration ignores all absorption // substract absorption from armor int absorption = randBetween(stats.get(STAT_ABS_MIN), stats.get(STAT_ABS_MAX)); if (absorption > 0 && dmg > 0) { int abs = absorption; if ((abs*100)/dmg < MIN_BLOCK) absorption = (dmg * MIN_BLOCK) /100; if ((abs*100)/dmg > MAX_BLOCK) absorption = (dmg * MAX_BLOCK) /100; if ((abs*100)/dmg < MIN_ABSORB && !stats.effects.triggered_block) absorption = (dmg * MIN_ABSORB) /100; if ((abs*100)/dmg > MAX_ABSORB && !stats.effects.triggered_block) absorption = (dmg * MAX_ABSORB) /100; // Sometimes, the absorb limits cause absorbtion to drop to 1 // This could be confusing to a player that has something with an absorb of 1 equipped // So we round absorption up in this case if (absorption == 0) absorption = 1; } dmg = dmg - absorption; if (dmg <= 0) { dmg = 0; if (h.trait_elemental < 0) { if (stats.effects.triggered_block && MAX_BLOCK < 100) dmg = 1; else if (!stats.effects.triggered_block && MAX_ABSORB < 100) dmg = 1; } else { if (MAX_RESIST < 100) dmg = 1; } if (activeAnimation->getName() == "block") { play_sfx_block = true; resetActiveAnimation(); } } } // check for crits int true_crit_chance = h.crit_chance; if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_MULTIPLY) true_crit_chance = true_crit_chance * powers->powers[h.power_index].mod_crit_value / 100; else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ADD) true_crit_chance += powers->powers[h.power_index].mod_crit_value; else if(powers->powers[h.power_index].mod_crit_mode == STAT_MODIFIER_MODE_ABSOLUTE) true_crit_chance = powers->powers[h.power_index].mod_crit_value; if (stats.effects.stun || stats.effects.speed < 100) true_crit_chance += h.trait_crits_impaired; bool crit = percentChance(true_crit_chance); if (crit) { dmg = dmg + h.dmg_max; if(!stats.hero) mapr->shaky_cam_ticks = MAX_FRAMES_PER_SEC/2; } if(stats.hero) combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_TAKEDMG); else { if(crit) combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_CRIT); else combat_text->addMessage(dmg, stats.pos, COMBAT_MESSAGE_GIVEDMG); } // temporarily save the current HP for calculating HP/MP steal on final blow int prev_hp = stats.hp; // apply damage stats.takeDamage(dmg); // after effects if (dmg > 0) { // damage always breaks stun stats.effects.removeEffectType(EFFECT_STUN); if (stats.hp > 0) { powers->effect(&stats, h.src_stats, h.power_index,h.source_type); } if (!stats.effects.immunity) { if (h.hp_steal != 0) { int steal_amt = (std::min(dmg, prev_hp) * h.hp_steal) / 100; if (steal_amt == 0) steal_amt = 1; combat_text->addMessage(msg->get("+%d HP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF); h.src_stats->hp = std::min(h.src_stats->hp + steal_amt, h.src_stats->get(STAT_HP_MAX)); } if (h.mp_steal != 0) { int steal_amt = (std::min(dmg, prev_hp) * h.mp_steal) / 100; if (steal_amt == 0) steal_amt = 1; combat_text->addMessage(msg->get("+%d MP",steal_amt), h.src_stats->pos, COMBAT_MESSAGE_BUFF); h.src_stats->mp = std::min(h.src_stats->mp + steal_amt, h.src_stats->get(STAT_MP_MAX)); } } } // post effect power if (h.post_power > 0 && dmg > 0) { powers->activate(h.post_power, h.src_stats, stats.pos); } // loot if (dmg > 0 && !h.loot.empty()) { for (unsigned i=0; i<h.loot.size(); i++) { powers->loot.push_back(h.loot[i]); powers->loot.back().x = (int)stats.pos.x; powers->loot.back().y = (int)stats.pos.y; } } // interrupted to new state if (dmg > 0) { bool chance_poise = percentChance(stats.get(STAT_POISE)); if(stats.hp <= 0) { stats.effects.triggered_death = true; if(stats.hero) stats.cur_state = AVATAR_DEAD; else { doRewards(h.source_type); if (crit) stats.cur_state = ENEMY_CRITDEAD; else stats.cur_state = ENEMY_DEAD; mapr->collider.unblock(stats.pos.x,stats.pos.y); } } // don't go through a hit animation if stunned else if (!stats.effects.stun && !chance_poise) { play_sfx_hit = true; if(!chance_poise && stats.cooldown_hit_ticks == 0) { if(stats.hero) stats.cur_state = AVATAR_HIT; else stats.cur_state = ENEMY_HIT; stats.cooldown_hit_ticks = stats.cooldown_hit; if (stats.untransform_on_hit) stats.transform_duration = 0; } // roll to see if the enemy's ON_HIT power is casted if (percentChance(stats.power_chance[ON_HIT])) { powers->activate(stats.power_index[ON_HIT], &stats, stats.pos); } } // just play the hit sound else play_sfx_hit = true; } return true; }
void Avatar::transform() { // calling a transform power locks the actionbar, so we unlock it here inpt->unlockActionBar(); delete charmed_stats; charmed_stats = NULL; Enemy_Level el = enemyg->getRandomEnemy(stats.transform_type, 0, 0); if (el.type != "") { charmed_stats = new StatBlock(); charmed_stats->load(el.type); } else { logError("Avatar: Could not transform into creature type '%s'", stats.transform_type.c_str()); stats.transform_type = ""; return; } transform_triggered = true; stats.transformed = true; setPowers = true; // temporary save hero stats delete hero_stats; hero_stats = new StatBlock(); *hero_stats = stats; // do not allow two copies of the summons list hero_stats->summons.clear(); // replace some hero stats stats.speed = charmed_stats->speed; stats.flying = charmed_stats->flying; stats.intangible = charmed_stats->intangible; stats.humanoid = charmed_stats->humanoid; stats.animations = charmed_stats->animations; stats.powers_list = charmed_stats->powers_list; stats.powers_passive = charmed_stats->powers_passive; stats.effects.clearEffects(); anim->decreaseCount("animations/hero.txt"); anim->increaseCount(charmed_stats->animations); animationSet = anim->getAnimationSet(charmed_stats->animations); delete activeAnimation; activeAnimation = animationSet->getAnimation(); stats.cur_state = AVATAR_STANCE; // base stats for (unsigned int i=0; i<STAT_COUNT; ++i) { clampFloor(stats.starting[i], charmed_stats->starting[i]); } // resistances for (unsigned int i=0; i<stats.vulnerable.size(); i++) { clampCeil(stats.vulnerable[i], charmed_stats->vulnerable[i]); } loadSounds(charmed_stats); loadStepFX("NULL"); stats.applyEffects(); transform_pos = stats.pos; transform_map = mapr->getFilename(); }
GameStateTitle::GameStateTitle() : GameState() { exit_game = false; load_game = false; logo.setGraphics(render_device->loadGraphicSurface("images/menus/logo.png")); // display logo centered if (!logo.graphicsIsNull()) { logo.setDestX(VIEW_W_HALF - (logo.getGraphicsWidth()/2)); logo.setDestY(VIEW_H_HALF - (logo.getGraphicsHeight()/2)); } // set up buttons button_play = new WidgetButton("images/menus/buttons/button_default.png"); button_exit = new WidgetButton("images/menus/buttons/button_default.png"); button_cfg = new WidgetButton("images/menus/buttons/button_default.png"); button_credits = new WidgetButton("images/menus/buttons/button_default.png"); button_play->label = msg->get("Play Game"); button_play->pos.x = VIEW_W_HALF - button_play->pos.w/2; button_play->pos.y = VIEW_H - (button_exit->pos.h*4); if (!ENABLE_PLAYGAME) { button_play->enabled = false; button_play->tooltip = msg->get("Enable a core mod to continue"); } button_play->refresh(); button_cfg->label = msg->get("Configuration"); button_cfg->pos.x = VIEW_W_HALF - button_cfg->pos.w/2; button_cfg->pos.y = VIEW_H - (button_exit->pos.h*3); button_cfg->refresh(); button_credits->label = msg->get("Credits"); button_credits->pos.x = VIEW_W_HALF - button_credits->pos.w/2; button_credits->pos.y = VIEW_H - (button_exit->pos.h*2); button_credits->refresh(); button_exit->label = msg->get("Exit Game"); button_exit->pos.x = VIEW_W_HALF - button_exit->pos.w/2; button_exit->pos.y = VIEW_H - button_exit->pos.h; button_exit->refresh(); // set up labels label_version = new WidgetLabel(); label_version->set(VIEW_W, 0, JUSTIFY_RIGHT, VALIGN_TOP, RELEASE_VERSION, font->getColor("menu_normal")); // Setup tab order tablist.add(button_play); tablist.add(button_cfg); tablist.add(button_credits); tablist.add(button_exit); // Warning text box warning_box = NULL; if (GAME_FOLDER == "default") { std::string warning_text = msg->get("Warning: A game wasn't specified, falling back to the 'default' game. Did you forget the --game flag? (e.g. --game=flare-game). See --help for more details."); Point warning_size = font->calc_size(warning_text, VIEW_W/2); int warning_box_h = warning_size.y; clampCeil(warning_box_h, VIEW_H/2); warning_box = new WidgetScrollBox(VIEW_W/2, warning_box_h); warning_box->resize(warning_size.y); font->setFont("font_normal"); font->renderShadowed(warning_text, 0, 0, JUSTIFY_LEFT, warning_box->contents.getGraphics(), VIEW_W/2, FONT_WHITE); } }