int monster::attack_at(int x, int y) { int mondex = g->mon_at(x, y); int npcdex = g->npc_at(x, y); if(x == g->u.posx && y == g->u.posy) { hit_player(g, g->u); return 1; } if(mondex != -1) { // Currently, there are only pro-player and anti-player groups, // this makes it easy for us. monster& mon = g->zombie(mondex); // Don't attack yourself. if(&mon == this) { return 0; } // Special case: Target is hallucination if(mon.is_hallucination()) { g->kill_mon(mondex); // We haven't actually attacked anything, i.e. we can still do things. // Hallucinations(obviously) shouldn't affect the way real monsters act. return 0; } // With no melee dice, we can't attack, but we had to process until here // because hallucinations require no melee dice to destroy. if(type->melee_dice <= 0) { return 0; } bool is_enemy = mon.friendly != friendly; is_enemy = is_enemy || has_flag(MF_ATTACKMON); // I guess the flag means all monsters are enemies? if(is_enemy) { hit_monster(g, mondex); return 1; } } else if(npcdex != -1 && type->melee_dice > 0) { // For now we're always attacking NPCs that are getting into our // way. This is consistent with how it worked previously, but // later on not hitting allied NPCs would be cool. hit_player(g, *g->active_npc[npcdex]); return 1; } // Nothing to attack. return 0; }
void monster::friendly_move(game *g) { point next; bool moved = false; moves -= 100; if (plans.size() > 0 && (plans[0].x != g->u.posx || plans[0].y != g->u.posy) && (can_move_to(g->m, plans[0].x, plans[0].y) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ next = plans[0]; plans.erase(plans.begin()); moved = true; } else stumble(g, moved); if (moved) { int mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); if (mondex != -1 && g->z[mondex].friendly == 0 && type->melee_dice > 0) hit_monster(g, mondex); else if (npcdex != -1 && type->melee_dice > 0) hit_player(g, g->active_npc[g->npc_at(next.x, next.y)]); else if (mondex == -1 && npcdex == -1 && can_move_to(g->m, next.x, next.y)) move_to(g, next.x, next.y); else if ((!can_move_to(g->m, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } } }
/* Function: friendly_move The per-turn movement and action calculation of any friendly monsters. */ void monster::friendly_move(game *g) { point next; bool moved = false; //If we sucessfully calculated a plan in the generic monster movement function, begin executing it. if (plans.size() > 0 && (plans[0].x != g->u.posx || plans[0].y != g->u.posy) && (can_move_to(g, plans[0].x, plans[0].y) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ next = plans[0]; plans.erase(plans.begin()); moved = true; } else { //Otherwise just stumble around randomly until we formulate a plan. moves -= 100; stumble(g, moved); } if (moved) { //We have a plan. int mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); //If there is an unfriendly mosnter in the target square we want to move into, hit them if we have a melee attack. if (mondex != -1 && g->z[mondex].friendly == 0 && type->melee_dice > 0){ hit_monster(g, mondex); } //If there is an npc (any npc?) we hit them assuming we have a melee attack. else if (npcdex != -1 && type->melee_dice > 0){ hit_player(g, *g->active_npc[g->npc_at(next.x, next.y)]); } //If no one is there and its a walkable square, walk there. else if (mondex == -1 && npcdex == -1 && can_move_to(g, next.x, next.y)){ move_to(g, next.x, next.y); } //If there is a bashable object in our way, bash it down. else if ((!can_move_to(g, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); moves -= 100; } //If there is a destroyable object in our way, destroy it. else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } //If all else fails in our plan (an issue with pathfinding maybe) stumble around instead. else { stumble(g, moved); moves -= 100; } } }
/** * This function handles those runes which detonate but do not cast * spells. Typically, poisoned or diseased runes. * @param op Rune. * @param victim Victim of the rune. */ static void rune_attack(object *op, object *victim) { int dam = op->stats.dam; op->stats.dam = (sint16) ((float) dam * (LEVEL_DAMAGE(op->level) * 0.925f)); if (victim) { tag_t tag = victim->count; hit_player(victim, op->stats.dam, op, AT_INTERNAL); if (was_destroyed(victim, tag)) { op->stats.dam = dam; return; } /* If there's a disease in the needle, put it in the player */ if (op->randomitems != NULL) { create_treasure(op->randomitems, op, 0, op->level ? op->level : victim->map->difficulty, T_STYLE_UNSET, ART_CHANCE_UNSET, 0, NULL); } if (op->inv && op->inv->type == DISEASE) { object *disease = op->inv; infect_object(victim, disease, 1); remove_ob(disease); check_walk_off(disease, NULL, MOVE_APPLY_VANISHED); } } else { hit_map(op, 0, 0); } op->stats.dam = dam; }
void monster::hit_player(game *g, player &p, bool can_grab) { moves -= 100; if (type->melee_dice == 0) // We don't attack, so just return { return; } add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect(ME_RUN, 4); } bool is_npc = p.is_npc(); bool u_see = (!is_npc || g->u_see(p.posx, p.posy)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; technique_id tech = p.pick_defensive_technique(g, this, NULL); p.perform_defensive_technique(tech, g, this, NULL, bphit, side, dam, cut, stab); //110*e^(-.3*[melee skill of monster]) = % chance to miss. *100 to track .01%'s //Returns ~80% at 1, drops quickly to 33% at 4, then slowly to 5% at 10 and 1% at 16 if (rng(0, 10000) < 11000 * exp(-.3 * type->melee_skill)) { g->add_msg("The %s misses.", name().c_str()); } else { //Reduce player's ability to dodge by monster's ability to hit int dodge_ii = p.dodge(g) - rng(0, type->melee_skill); if (dodge_ii < 0) { dodge_ii = 0; } // 100/(1+99*e^(-.6*[dodge() return modified by monster's skill])) = % chance to dodge // *100 to track .01%'s // 1% minimum, scales slowly to 16% at 5, then rapidly to 80% at 10, // then returns less with each additional point, reaching 99% at 16 if (rng(0, 10000) < 10000/(1 + 99 * exp(-.6 * dodge_ii))) { g->add_msg("%s dodge the %s.", You.c_str(), name().c_str()); p.practice(g->turn, "dodge", type->melee_skill * 2); //Better monster = more skill gained } //Successful hit with damage else if (dam > 0) { p.practice(g->turn, "dodge", type->melee_skill); if (u_see && tech != TEC_BLOCK) { g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); } // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) { g->add_msg("You stop reloading."); } else if (g->u.activity.type == ACT_READ) { g->add_msg("You stop reading."); } else if (g->u.activity.type == ACT_CRAFT || g->u.activity.type == ACT_LONGCRAFT) { g->add_msg("You stop crafting."); g->u.activity.type = ACT_NULL; } } if (p.has_active_bionic("bio_ods")) { if (u_see) { g->add_msg("%s offensive defense system shocks it!", Your.c_str()); } if (hurt(rng(10, 40))) die(g); } if (p.encumb(bphit) == 0 &&(p.has_trait(PF_SPINES) || p.has_trait(PF_QUILLS))) { int spine = rng(1, (p.has_trait(PF_QUILLS) ? 20 : 8)); g->add_msg("%s %s puncture it!", Your.c_str(), (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); if (hurt(spine)) die(g); } if (dam + cut <= 0) { return; // Defensive technique canceled damage. } //Hurt the player dam = p.hit(g, bphit, side, dam, cut); //Monster effects if (dam > 0 && has_flag(MF_VENOM)) { if (!is_npc) { g->add_msg("You're poisoned!"); } p.add_disease("poison", 30); } else if (dam > 0 && has_flag(MF_BADVENOM)) { if (!is_npc) { g->add_msg("You feel poison flood your body, wracking you with pain..."); } p.add_disease("badpoison", 40); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { if (!is_npc) { g->add_msg("You're Bleeding!"); } p.add_disease("bleed", 60); } //Same as monster's chance to not miss if (can_grab && has_flag(MF_GRABS) && (rng(0, 10000) > 11000 * exp(-.3 * type->melee_skill))) { if (!is_npc) { g->add_msg("The %s grabs you!", name().c_str()); } if (p.weapon.has_technique(TEC_BREAK, &p) && dice(p.dex_cur + p.skillLevel("melee"), 12) > dice(type->melee_dice, 10)) { if (!is_npc) { g->add_msg("You break the grab!"); } } else hit_player(g, p, false); //We grabed, so hit them again } //Counter-attack? if (tech == TEC_COUNTER && !is_npc) { g->add_msg("Counter-attack!"); // A counterattack is a free action to avoid stunlocking the player. int player_moves = p.moves; hurt( p.hit_mon(g, this) ); p.moves = player_moves; } } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) { g->active_npc.erase(g->active_npc.begin() + index); } plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; for (int i = 0; i < type->anger.size(); i++) { if (type->anger[i] == MTRIG_FRIEND_ATTACKED) { anger_adjust += 15; } } for (int i = 0; i < type->placate.size(); i++) { if (type->placate[i] == MTRIG_FRIEND_ATTACKED) { anger_adjust -= 15; } } for (int i = 0; i < type->fear.size(); i++) { if (type->fear[i] == MTRIG_FRIEND_ATTACKED) { morale_adjust -= 15; } } if (anger_adjust != 0 && morale_adjust != 0) { for (int i = 0; i < g->z.size(); i++) { g->z[i].morale += morale_adjust; g->z[i].anger += anger_adjust; } } }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move(game *g) { // We decrement wandf no matter what. We'll save our wander_to plans until // after we finish out set_dest plans, UNLESS they time out first. if (wandf > 0) wandf--; // First, use the special attack, if we can! if (sp_timeout > 0) sp_timeout--; if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; (ma.*type->sp_attack)(g, this); } if (moves < 0) return; if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect(ME_STUNNED)) { stumble(g, false); moves = 0; return; } if (has_effect(ME_DOWNED)) { moves = 0; return; } if (has_effect(ME_BOULDERING)){ moves -= 20; if (moves < 0) moves = 0; return; } if (friendly != 0) { if (friendly > 0) friendly--; friendly_move(g); return; } bool moved = false; point next; int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1); monster_attitude current_attitude = attitude(); if (friendly == 0) current_attitude = attitude(&(g->u)); // If our plans end in a player, set our attitude to consider that player if (plans.size() > 0) { if (plans.back().x == g->u.posx && plans.back().y == g->u.posy) current_attitude = attitude(&(g->u)); else { for (int i = 0; i < g->active_npc.size(); i++) { if (plans.back().x == g->active_npc[i]->posx && plans.back().y == g->active_npc[i]->posy) current_attitude = attitude((g->active_npc[i])); } } } if (current_attitude == MATT_IGNORE || (current_attitude == MATT_FOLLOW && plans.size() <= MONSTER_FOLLOW_DIST)) { moves -= 100; stumble(g, false); return; } if (plans.size() > 0 && !is_fleeing(g->u) && (mondex == -1 || g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g, plans[0].x, plans[0].y) || (plans[0].x == g->u.posx && plans[0].y == g->u.posy) || (g->m.has_flag(bashable, plans[0].x, plans[0].y) && has_flag(MF_BASHES)))){ // CONCRETE PLANS - Most likely based on sight next = plans[0]; moved = true; } else if (has_flag(MF_SMELLS)) { // No sight... or our plans are invalid (e.g. moving through a transparent, but // solid, square of terrain). Fall back to smell if we have it. point tmp = scent_move(g); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound point tmp = sound_move(g); if (tmp.x != posx || tmp.y != posy) { next = tmp; moved = true; } } // Finished logic section. By this point, we should have chosen a square to // move to (moved = true). if (moved) { // Actual effects of moving to the square we've chosen mondex = g->mon_at(next.x, next.y); int npcdex = g->npc_at(next.x, next.y); if (next.x == g->u.posx && next.y == g->u.posy && type->melee_dice > 0) hit_player(g, g->u); else if (mondex != -1 && g->z[mondex].type->species == species_hallu) { g->kill_mon(mondex); moves -= 100; } else if (mondex != -1 && type->melee_dice > 0 && this != &(g->z[mondex]) && (g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON))) hit_monster(g, mondex); else if (npcdex != -1 && type->melee_dice > 0) hit_player(g, *g->active_npc[npcdex]); else if ((!can_move_to(g, next.x, next.y) || one_in(3)) && g->m.has_flag(bashable, next.x, next.y) && has_flag(MF_BASHES)) { std::string bashsound = "NOBASH"; // If we hear "NOBASH" it's time to debug! int bashskill = int(type->melee_dice * type->melee_sides); g->m.bash(next.x, next.y, bashskill, bashsound); g->sound(next.x, next.y, 18, bashsound); moves -= 100; } else if (g->m.move_cost(next.x, next.y) == 0 && has_flag(MF_DESTROYS)) { g->m.destroy(g, next.x, next.y, true); moves -= 250; } else if (can_move_to(g, next.x, next.y) && g->is_empty(next.x, next.y)) move_to(g, next.x, next.y); else moves -= 100; } else moves -= 100; // If we're close to our target, we get focused and don't stumble if ((has_flag(MF_STUMBLES) && (plans.size() > 3 || plans.size() == 0)) || !moved) stumble(g, moved); }
void monster::hit_player(game *g, player &p, bool can_grab) { moves -= 100; if (type->melee_dice == 0) // We don't attack, so just return { return; } add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect(ME_RUN, 4); } bool is_npc = p.is_npc(); bool u_see = (!is_npc || g->u_see(p.posx, p.posy)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; int side = random_side(bphit); //110*e^(-.3*[melee skill of monster]) = % chance to miss. *100 to track .01%'s //Returns ~80% at 1, drops quickly to 33% at 4, then slowly to 5% at 10 and 1% at 16 if (rng(0, 10000) < 11000 * exp(-.3 * type->melee_skill)) { if (u_see) { g->add_msg(_("The %s misses."), name().c_str()); } } else { if (!g->u.uncanny_dodge()) { //Reduce player's ability to dodge by monster's ability to hit int dodge_ii = p.dodge(g) - rng(0, type->melee_skill); if (dodge_ii < 0) { dodge_ii = 0; } // 100/(1+99*e^(-.6*[dodge() return modified by monster's skill])) = % chance to dodge // *100 to track .01%'s // 1% minimum, scales slowly to 16% at 5, then rapidly to 80% at 10, // then returns less with each additional point, reaching 99% at 16 if (rng(0, 10000) < 10000/(1 + 99 * exp(-.6 * dodge_ii))) { if (is_npc) { if(u_see) { g->add_msg(_("%1$s dodges the %2$s."), p.name.c_str(), name().c_str()); } } else { g->add_msg(_("You dodge the %s."), name().c_str()); } p.practice(g->turn, "dodge", type->melee_skill * 2); //Better monster = more skill gained } //Successful hit with damage else if (dam > 0) { p.practice(g->turn, "dodge", type->melee_skill); if(!p.block_hit(g, this, NULL, bphit, side, dam, cut, stab) && u_see) { if (is_npc) { if( u_see ) { g->add_msg(_("The %1$s hits %2$s's %3$s."), name().c_str(), p.name.c_str(), body_part_name(bphit, side).c_str()); } } else { g->add_msg(_("The %1$s hits your %2$s."), name().c_str(), body_part_name(bphit, side).c_str()); } } // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) { g->add_msg(_("You stop reloading.")); } else if (g->u.activity.type == ACT_READ) { g->add_msg(_("You stop reading.")); } else if (g->u.activity.type == ACT_CRAFT || g->u.activity.type == ACT_LONGCRAFT) { g->add_msg(_("You stop crafting.")); g->u.activity.type = ACT_NULL; } } if (p.has_active_bionic("bio_ods")) { if (!is_npc) { g->add_msg(_("Your offensive defense system shocks it!"), p.name.c_str()); } else if (u_see) { g->add_msg(_("%s's offensive defense system shocks it!"), p.name.c_str()); } hurt(rng(10, 40)); } if (p.encumb(bphit) == 0 &&(p.has_trait("SPINES") || p.has_trait("QUILLS"))) { int spine = rng(1, (p.has_trait("QUILLS") ? 20 : 8)); if (is_npc) { if( u_see ) { g->add_msg(_("%1$s's %2$s puncture it!"), p.name.c_str(), (g->u.has_trait("QUILLS") ? _("quills") : _("spines"))); } } else { g->add_msg(_("Your %s puncture it!"), (g->u.has_trait("QUILLS") ? _("quills") : _("spines"))); } hurt(spine); } if (dam + cut <= 0) { return; // Defensive technique canceled damage. } //Hallucinations don't actually hurt the player, but do produce the message if(is_hallucination()) { //~14% chance of vanishing after hitting the player if(one_in(7)) { die(g); return; } } else { //Hurt the player dam = p.hit(g, bphit, side, dam, cut); //Monster effects if (dam > 0 && has_flag(MF_VENOM)) { g->add_msg_if_player(&p, _("You're poisoned!")); p.add_disease("poison", 30); } else if (dam > 0 && has_flag(MF_BADVENOM)) { g->add_msg_if_player(&p, _("You feel poison flood your body, wracking you with pain...")); p.add_disease("badpoison", 40); } else if (dam > 0 && has_flag(MF_PARALYZE)) { g->add_msg_if_player(&p, _("You feel poison enter your body!")); p.add_disease("paralyzepoison", 100, false, 1, 20, 100); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { g->add_msg_if_player(&p, _("You're Bleeding!")); p.add_disease("bleed", 60, false, 1, 3, 120, 1, bphit, side, true); } //Same as monster's chance to not miss if (can_grab && has_flag(MF_GRABS) && (rng(0, 10000) > 11000 * exp(-.3 * type->melee_skill))) { g->add_msg(_("The %s grabs you!"), name().c_str()); if (p.has_grab_break_tec() && dice(p.dex_cur + p.skillLevel("melee"), 12) > dice(type->melee_dice, 10)) { g->add_msg_if_player(&p, _("You break the grab!")); } else { hit_player(g, p, false); //We grabed, so hit them again } } } // TODO: readd with counter mechanic } } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) { g->active_npc.erase(g->active_npc.begin() + index); } plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_ATTACKED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_ATTACKED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (int i = 0; i < g->num_zombies(); i++) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } }
/** * If we are here, the arch (spell) we check was able to move to this * place. wall() has failed, including reflection checking. * * Look for a target. * @param op The spell object. */ void check_fired_arch(object *op) { tag_t op_tag = op->count, tmp_tag; object *tmp, *hitter, *head; int dam; /* we return here if we have NOTHING blocking here */ if (!blocked(op, op->map, op->x, op->y, op->terrain_flag)) { return; } if (op->other_arch) { explode_object(op); return; } if (op->stats.sp == SP_PROBE && op->type == BULLET) { probe(op); remove_ob(op); check_walk_off(op, NULL, MOVE_APPLY_VANISHED); return; } hitter = get_owner(op); if (!hitter) { hitter = op; } else if (hitter->head) { hitter = hitter->head; } for (tmp = get_map_ob(op->map, op->x, op->y); tmp != NULL; tmp = tmp->above) { head = tmp->head; if (!head) { head = tmp; } if (!IS_LIVE(tmp)) { continue; } /* Let friends fire through friends */ if (is_friend_of(hitter, head) || head == hitter) { continue; } tmp_tag = tmp->count; dam = hit_player(tmp, op->stats.dam, op, AT_INTERNAL); if (was_destroyed(op, op_tag) || !was_destroyed(tmp, tmp_tag) || (op->stats.dam -= dam) < 0) { if (!QUERY_FLAG(op, FLAG_REMOVED)) { remove_ob(op); check_walk_off(op, NULL, MOVE_APPLY_VANISHED); return; } } } }
/** * 'victim' moves onto 'trap' (trap has FLAG_WALK_ON or FLAG_FLY_ON set) or * 'victim' leaves 'trap' (trap has FLAG_WALK_OFF or FLAG_FLY_OFF) set. * * I added the flags parameter to give the single events more information * about whats going on: * * Most important is the "MOVE_APPLY_VANISHED" flag. * If set, a object has left a tile but "vanished" and not moved (perhaps * it exploded or something). This means that some events are not * triggered like trapdoors or teleporter traps for example which have a * "FLY/MOVE_OFF" set. This will avoid that they touch invalid objects. * @param trap Object victim moved on. * @param victim The object that moved on trap. * @param originator Player, monster or other object that caused 'victim' * to move onto 'trap'. Will receive messages caused by this action. May * be NULL, however, some types of traps require an originator to * function. * @param flags Flags. */ void move_apply(object *trap, object *victim, object *originator, int flags) { static int recursion_depth = 0; /* move_apply() is the most likely candidate for causing unwanted and * possibly unlimited recursion. */ /* The following was changed because it was causing perfeclty correct * maps to fail. 1) it's not an error to recurse: * rune detonates, summoning monster. monster lands on nearby rune. * nearby rune detonates. This sort of recursion is expected and * proper. This code was causing needless crashes. */ if (recursion_depth >= 500) { LOG(llevDebug, "WARNING: move_apply(): aborting recursion [trap arch %s, name %s; victim arch %s, name %s]\n", trap->arch->name, trap->name, victim->arch->name, victim->name); return; } if (trap->head) { trap = trap->head; } /* Trigger the TRIGGER event */ if (trigger_event(EVENT_TRIGGER, victim, trap, originator, NULL, 0, 0, 0, SCRIPT_FIX_NOTHING)) { return; } recursion_depth++; switch (trap->type) { /* these objects can trigger other objects connected to them. * We need to check them at map loading time and other special * events to be sure to have a 100% working map state. */ case BUTTON: case PEDESTAL: update_button(trap); break; case TRIGGER_BUTTON: case TRIGGER_PEDESTAL: case TRIGGER_ALTAR: check_trigger(trap, victim); break; case CHECK_INV: check_inv(victim, trap); break; /* these objects trigger to but they are "instant". * We don't need to check them when loading. */ case ALTAR: /* sacrifice victim on trap */ apply_altar(trap, victim, originator); break; case CONVERTER: if (!(flags & MOVE_APPLY_VANISHED)) { convert_item(victim, trap); } break; case PLAYERMOVER: break; /* should be walk_on/fly_on only */ case SPINNER: if (victim->direction) { if ((victim->direction = victim->direction + trap->direction) > 8) { victim->direction = (victim->direction % 8) + 1; } update_turn_face(victim); } break; case DIRECTOR: if (victim->direction) { victim->direction = trap->direction; update_turn_face(victim); } break; /* no need to hit anything */ case MMISSILE: if (IS_LIVE(victim) && !(flags&MOVE_APPLY_VANISHED)) { tag_t trap_tag = trap->count; hit_player(victim, trap->stats.dam, trap, AT_MAGIC); if (!was_destroyed(trap, trap_tag)) { remove_ob(trap); } check_walk_off(trap, NULL, MOVE_APPLY_VANISHED); } break; case THROWN_OBJ: if (trap->inv == NULL || (flags & MOVE_APPLY_VANISHED)) { break; } /* fallthrough */ case ARROW: /* bad bug: monster throw a object, make a step forwards, step on object , * trigger this here and get hit by own missile - and will be own enemy. * Victim then is his own enemy and will start to kill herself (this is * removed) but we have not synced victim and his missile. To avoid senseless * action, we avoid hits here */ if ((IS_LIVE(victim) && trap->speed) && trap->owner != victim) { hit_with_arrow(trap, victim); } break; case CONE: case LIGHTNING: break; case BULLET: if ((QUERY_FLAG(victim, FLAG_NO_PASS) || IS_LIVE(victim)) && !(flags & MOVE_APPLY_VANISHED)) { check_fired_arch(trap); } break; case TRAPDOOR: { int max, sound_was_played; object *ab; if ((flags & MOVE_APPLY_VANISHED)) { break; } if (!trap->value) { sint32 tot; for (ab = trap->above, tot = 0; ab != NULL; ab = ab->above) { if (!QUERY_FLAG(ab, FLAG_FLYING)) { tot += (ab->nrof ? ab->nrof : 1) * ab->weight + ab->carrying; } } if (!(trap->value = (tot > trap->weight) ? 1 : 0)) { break; } SET_ANIMATION(trap, (NUM_ANIMATIONS(trap) / NUM_FACINGS(trap)) * trap->direction + trap->value); update_object(trap, UP_OBJ_FACE); } for (ab = trap->above, max = 100, sound_was_played = 0; --max && ab && !QUERY_FLAG(ab, FLAG_FLYING); ab = ab->above) { if (!sound_was_played) { play_sound_map(trap->map, trap->x, trap->y, SOUND_FALL_HOLE, SOUND_NORMAL); sound_was_played = 1; } if (ab->type == PLAYER) { new_draw_info(NDI_UNIQUE, ab, "You fall into a trapdoor!"); } transfer_ob(ab, EXIT_X(trap), EXIT_Y(trap), trap->last_sp, ab, trap); } break; } case PIT: /* Pit not open? */ if ((flags & MOVE_APPLY_VANISHED) || trap->stats.wc > 0) { break; } play_sound_map(victim->map, victim->x, victim->y, SOUND_FALL_HOLE, SOUND_NORMAL); if (victim->type == PLAYER) { new_draw_info(NDI_UNIQUE, victim, "You fall through the hole!\n"); } transfer_ob(victim->head ? victim->head : victim, EXIT_X(trap), EXIT_Y(trap), trap->last_sp, victim, trap); break; case EXIT: /* If no map path specified, we assume it is the map path of the exit. */ if (!EXIT_PATH(trap)) { FREE_AND_ADD_REF_HASH(EXIT_PATH(trap), trap->map->path); } if (!(flags & MOVE_APPLY_VANISHED) && victim->type == PLAYER && EXIT_PATH(trap) && EXIT_Y(trap) != -1 && EXIT_X(trap) != -1) { /* Basically, don't show exits leading to random maps the players output. */ if (trap->msg && strncmp(EXIT_PATH(trap), "/!", 2) && strncmp(EXIT_PATH(trap), "/random/", 8)) { new_draw_info(NDI_NAVY, victim, trap->msg); } enter_exit(victim, trap); } break; case SHOP_MAT: if (!(flags & MOVE_APPLY_VANISHED)) { apply_shop_mat(trap, victim); } break; /* Drop a certain amount of gold, and have one item identified */ case IDENTIFY_ALTAR: if (!(flags & MOVE_APPLY_VANISHED)) { apply_identify_altar(victim, trap, originator); } break; case SIGN: /* Only player should be able read signs */ if (victim->type == PLAYER) { apply_sign(victim, trap); } break; case CONTAINER: if (victim->type == PLAYER) { (void) esrv_apply_container(victim, trap); } break; case RUNE: if (!(flags & MOVE_APPLY_VANISHED) && trap->level && IS_LIVE(victim)) { spring_trap(trap, victim); } break; #if 0 /* we don't have this atm. */ case DEEP_SWAMP: if (!(flags & MOVE_APPLY_VANISHED)) { walk_on_deep_swamp(trap, victim); } break; #endif default: LOG(llevDebug, "name %s, arch %s, type %d with fly/walk on/off not handled in move_apply()\n", trap->name, trap->arch->name, trap->type); break; } recursion_depth--; }
void player::hit_player(game *g, player &p, bool allow_grab) { bool is_u = (this == &(g->u)); // Affects how we'll display messages if (is_u && p.is_npc()) { npc* npcPtr = dynamic_cast<npc*>(&p); npcPtr->make_angry(); } std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); std::string verb = "hit"; // Divide their dodge roll by 2 if this is a grab int target_dodge = (allow_grab ? p.dodge_roll(g) : p.dodge_roll(g) / 2); int hit_value = hit_roll() - target_dodge; bool missed = (hit_roll() <= 0); int move_cost = attack_speed(*this, missed); if (missed) { int stumble_pen = stumble(*this); if (is_u) { // Only display messages if this is the player if (weapon.has_technique(TEC_FEINT, this)) g->add_msg("You feint."); else if (stumble_pen >= 60) g->add_msg("You miss and stumble with the momentum."); else if (stumble_pen >= 10) g->add_msg("You swing wildly and miss."); else g->add_msg("You miss."); } melee_practice(*this, false, unarmed_attack(), weapon.is_bashing_weapon(), weapon.is_cutting_weapon(), (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))); move_cost += stumble_pen; if (weapon.has_technique(TEC_FEINT, this)) move_cost = rng(move_cost / 3, move_cost); moves -= move_cost; return; } moves -= move_cost; body_part bp_hit; int side = rng(0, 1); hit_value += rng(-10, 10); if (hit_value >= 30) bp_hit = bp_eyes; else if (hit_value >= 20) bp_hit = bp_head; else if (hit_value >= 10) bp_hit = bp_torso; else if (one_in(4)) bp_hit = bp_legs; else bp_hit = bp_arms; std::string target = (p.is_npc() ? p.name + "'s " : "your "); target += body_part_name(bp_hit, side); bool critical_hit = scored_crit(target_dodge); int bash_dam = roll_bash_damage(NULL, critical_hit); int cut_dam = roll_cut_damage(NULL, critical_hit); int stab_dam = roll_stab_damage(NULL, critical_hit); technique_id tech_def = p.pick_defensive_technique(g, NULL, this); p.perform_defensive_technique(tech_def, g, NULL, this, bp_hit, side, bash_dam, cut_dam, stab_dam); if (bash_dam + cut_dam + stab_dam <= 0) return; // Defensive technique canceled our attack! if (critical_hit) // Crits cancel out Toad Style's armor boost p.rem_disease(DI_ARMOR_BOOST); int pain = 0; // Boost to pain; required for perform_technique // Moves lost to getting your weapon stuck int stuck_penalty = roll_stuck_penalty(NULL, (stab_dam >= cut_dam)); if (weapon.is_style()) stuck_penalty = 0; // Pick one or more special attacks technique_id technique = pick_technique(g, NULL, &p, critical_hit, allow_grab); // Handles effects as well; not done in melee_affect_* perform_technique(technique, g, NULL, &p, bash_dam, cut_dam, stab_dam, pain); p.pain += pain; // Mutation-based attacks perform_special_attacks(g, NULL, &p, bash_dam, cut_dam, stab_dam); // Handles speed penalties to monster & us, etc melee_special_effects(g, NULL, &p, critical_hit, bash_dam, cut_dam, stab_dam); // Make a rather quiet sound, to alert any nearby monsters if (weapon.typeId() != "style_ninjutsu") // Ninjutsu is silent! g->sound(posx, posy, 8, ""); p.hit(g, bp_hit, side, bash_dam, (cut_dam > stab_dam ? cut_dam : stab_dam)); verb = melee_verb(technique, your, *this, bash_dam, cut_dam, stab_dam); int dam = bash_dam + (cut_dam > stab_dam ? cut_dam : stab_dam); hit_message(g, You.c_str(), verb.c_str(), target.c_str(), dam, critical_hit); bool bashing = (bash_dam >= 10 && !unarmed_attack()); bool cutting = (cut_dam >= 10 && cut_dam >= stab_dam); bool stabbing = (stab_dam >= 10 && stab_dam >= cut_dam); melee_practice(*this, true, unarmed_attack(), bashing, cutting, stabbing); if (dam >= 5 && has_artifact_with(AEP_SAP_LIFE)) healall( rng(dam / 10, dam / 5) ); if (allow_grab && technique == TEC_GRAB) { // Move our weapon to a temp slot, if it's not unarmed if (p.weapon.has_technique(TEC_BREAK, &p) && dice(p.dex_cur + p.skillLevel("melee"), 12) > dice(dex_cur + skillLevel("melee"), 10)) { if (is_u) g->add_msg("%s break%s the grab!", target.c_str(), (p.is_npc() ? "s" : "")); } else if (!unarmed_attack()) { item tmpweap = remove_weapon(); hit_player(g, p, false); // False means a second grab isn't allowed weapon = tmpweap; } else hit_player(g, p, false); // False means a second grab isn't allowed } if (tech_def == TEC_COUNTER) { if (!p.is_npc()) g->add_msg("Counter-attack!"); p.hit_player(g, *this); } }
void check_projectile_collisions(projectile *p) { SDL_Rect a = { p->x, p->y, p->w, p->h }; SDL_Rect b; if (p->spawned_by != get_player_item()) { b.x = get_player_x(); b.y = get_player_y(); b.w = get_player_stat("width"); b.h = get_player_stat("height"); if (!check_collision(a, b)) { hit_player(p->dmg); destroy_projectile(p); return; } } int xmin = (p->x/TILE_DIM)-2; int ymin = (p->y/TILE_DIM)-2; int xmax = (p->x/TILE_DIM)+2; int ymax = (p->y/TILE_DIM)+2; int x, y; for (x = xmin; x <= xmax; x++) { for (y = ymin; y <= ymax; y++) { if (is_solid_tile(x, y)) { b.x = x*32; b.y = y*32; b.w = b.h = TILE_DIM; if (!check_collision(a, b)) { destroy_projectile(p); return; } } } } list_node *entities = get_entities(); list_node *c; entity *e; for (c = entities->next; c->next != NULL; c = c->next) { if (((entity *) c->data) != NULL && ((entity *) c->data)->item != p->spawned_by) { e = (entity *) c->data; b.x = e->x; b.y = e->y; b.w = e->w; b.h = e->h; if (e->collideable && !check_collision(a, b)) { hit_entity(e, p->dmg); destroy_projectile(p); return; } } } }
void monster::hit_player(game *g, player &p, bool can_grab) { if (type->melee_dice == 0) // We don't attack, so just return return; add_effect(ME_HIT_BY_PLAYER, 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) add_effect(ME_RUN, 4); bool is_npc = p.is_npc(); int junk; bool u_see = (!is_npc || g->u_see(p.posx, p.posy, junk)); std::string you = (is_npc ? p.name : "you"); std::string You = (is_npc ? p.name : "You"); std::string your = (is_npc ? p.name + "'s" : "your"); std::string Your = (is_npc ? p.name + "'s" : "Your"); body_part bphit; int side = rng(0, 1); int dam = hit(g, p, bphit), cut = type->melee_cut, stab = 0; technique_id tech = p.pick_defensive_technique(g, this, NULL); p.perform_defensive_technique(tech, g, this, NULL, bphit, side, dam, cut, stab); if (dam == 0 && u_see) g->add_msg("The %s misses %s.", name().c_str(), you.c_str()); else if (dam > 0) { if (u_see && tech != TEC_BLOCK) g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); // Attempt defensive moves if (!is_npc) { if (g->u.activity.type == ACT_RELOAD) g->add_msg("You stop reloading."); else if (g->u.activity.type == ACT_READ) g->add_msg("You stop reading."); else if (g->u.activity.type == ACT_CRAFT) g->add_msg("You stop crafting."); g->u.activity.type = ACT_NULL; } if (p.has_active_bionic(bio_ods)) { if (u_see) g->add_msg("%s offensive defense system shocks it!", Your.c_str()); hurt(rng(10, 40)); } if (p.encumb(bphit) == 0 && (p.has_trait(PF_SPINES) || p.has_trait(PF_QUILLS))) { int spine = rng(1, (p.has_trait(PF_QUILLS) ? 20 : 8)); g->add_msg("%s %s puncture it!", Your.c_str(), (g->u.has_trait(PF_QUILLS) ? "quills" : "spines")); hurt(spine); } if (dam + cut <= 0) return; // Defensive technique canceled damage. p.hit(g, bphit, side, dam, cut); if (has_flag(MF_VENOM)) { if (!is_npc) g->add_msg("You're poisoned!"); p.add_disease(DI_POISON, 30, g); } else if (has_flag(MF_BADVENOM)) { if (!is_npc) g->add_msg("You feel poison flood your body, wracking you with pain..."); p.add_disease(DI_BADPOISON, 40, g); } if (has_flag(MF_BLEED) && dam > 6 && cut > 0) { if (!is_npc) g->add_msg("You're Bleeding!"); p.add_disease(DI_BLEED, 30, g); } if (can_grab && has_flag(MF_GRABS) && dice(type->melee_dice, 10) > dice(p.dodge(g), 10)) { if (!is_npc) g->add_msg("The %s grabs you!", name().c_str()); if (p.weapon.has_technique(TEC_BREAK, &p) && dice(p.dex_cur + p.skillLevel("melee").level(), 12) > dice(type->melee_dice, 10)){ if (!is_npc) g->add_msg("You break the grab!"); } else hit_player(g, p, false); } if (tech == TEC_COUNTER && !is_npc) { g->add_msg("Counter-attack!"); hurt( p.hit_mon(g, this) ); } } // if dam > 0 if (is_npc) { if (p.hp_cur[hp_head] <= 0 || p.hp_cur[hp_torso] <= 0) { npc* tmp = dynamic_cast<npc*>(&p); tmp->die(g); int index = g->npc_at(p.posx, p.posy); if (index != -1 && index < g->active_npc.size()) g->active_npc.erase(g->active_npc.begin() + index); plans.clear(); } } // Adjust anger/morale of same-species monsters, if appropriate int anger_adjust = 0, morale_adjust = 0; for (unsigned int i = 0; i < type->anger.size(); i++) { if (type->anger[i] == MTRIG_FRIEND_ATTACKED) anger_adjust += 15; } for (unsigned int i = 0; i < type->placate.size(); i++) { if (type->placate[i] == MTRIG_FRIEND_ATTACKED) anger_adjust -= 15; } for (unsigned int i = 0; i < type->fear.size(); i++) { if (type->fear[i] == MTRIG_FRIEND_ATTACKED) morale_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { for (unsigned int i = 0; i < g->z.size(); i++) { g->z[i].morale += morale_adjust; g->z[i].anger += anger_adjust; } } }