point monster::scent_move(game *g) { plans.clear(); std::vector<point> smoves; int maxsmell = 2; // Squares with smell 0 are not eligable targets if (has_flag(MF_KEENNOSE)) { maxsmell = 1; } int minsmell = 9999; point pbuff, next(-1, -1); unsigned int smell; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { smell = g->scent(posx + x, posy + y); int mon = g->mon_at(posx + x, posy + y); if ((mon == -1 || g->z[mon].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g, posx + x, posy + y) || (posx + x == g->u.posx && posx + y == g->u.posy) || (g->m.has_flag(bashable, posx + x, posy + y) && has_flag(MF_BASHES)))) { if ((!is_fleeing(g->u) && smell > maxsmell) || ( is_fleeing(g->u) && smell < minsmell) ) { smoves.clear(); pbuff.x = posx + x; pbuff.y = posy + y; smoves.push_back(pbuff); maxsmell = smell; minsmell = smell; } else if ((!is_fleeing(g->u) && smell == maxsmell) || ( is_fleeing(g->u) && smell == minsmell) ) { pbuff.x = posx + x; pbuff.y = posy + y; smoves.push_back(pbuff); } } } } if (smoves.size() > 0) { int nextsq = rng(0, smoves.size() - 1); next = smoves[nextsq]; } return next; }
void monster::print_info(game *g, WINDOW* w) { // First line of w is the border; the next two are terrain info, and after that // is a blank line. w is 13 characters tall, and we can't use the last one // because it's a border as well; so we have lines 4 through 11. // w is also 48 characters wide - 2 characters for border = 46 characters for us mvwprintz(w, 6, 1, type->color, type->name.c_str()); if (friendly != 0) { wprintz(w, c_white, " "); wprintz(w, h_white, "Friendly!"); } std::string damage_info; nc_color col; if (hp == type->hp) { damage_info = "It is uninjured"; col = c_green; } else if (hp >= type->hp * .8) { damage_info = "It is lightly injured"; col = c_ltgreen; } else if (hp >= type->hp * .6) { damage_info = "It is moderately injured"; col = c_yellow; } else if (hp >= type->hp * .3) { damage_info = "It is heavily injured"; col = c_yellow; } else if (hp >= type->hp * .1) { damage_info = "It is severly injured"; col = c_ltred; } else { damage_info = "it is nearly dead"; col = c_red; } mvwprintz(w, 7, 1, col, damage_info.c_str()); if (is_fleeing(g->u)) wprintz(w, c_white, ", and it is fleeing."); else wprintz(w, col, "."); std::string tmp = type->description; std::string out; size_t pos; int line = 8; do { pos = tmp.find_first_of('\n'); out = tmp.substr(0, pos); mvwprintz(w, line, 1, c_white, out.c_str()); tmp = tmp.substr(pos + 1); line++; } while (pos != std::string::npos && line < 12); }
point monster::scent_move() { std::vector<point> smoves; int maxsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if (has_flag(MF_KEENNOSE)) { maxsmell = 1; smell_threshold = 400; } int minsmell = 9999; point pbuff, next(-1, -1); unsigned int smell; const bool fleeing = is_fleeing(g->u); if( !fleeing && g->scent( posx(), posy() ) > smell_threshold ) { return next; } for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { const int nx = posx() + x; const int ny = posy() + y; smell = g->scent(nx, ny); int mon = g->mon_at(nx, ny); if ((mon == -1 || g->zombie(mon).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(nx, ny) || (nx == g->u.posx && ny == g->u.posy) || (g->m.has_flag("BASHABLE", nx, ny) && has_flag(MF_BASHES)))) { if ((!fleeing && smell > maxsmell ) || ( fleeing && smell < minsmell ) ) { smoves.clear(); pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); maxsmell = smell; minsmell = smell; } else if ((!fleeing && smell == maxsmell ) || ( fleeing && smell == minsmell ) ) { pbuff.x = nx; pbuff.y = ny; smoves.push_back(pbuff); } } } } if (smoves.size() > 0) { int nextsq = rng(0, smoves.size() - 1); next = smoves[nextsq]; } return next; }
tripoint monster::scent_move() { // @todo Remove when scentmap is 3D if( abs( posz() - g->get_levz() ) > 1 ) { return { -1, -1, INT_MIN }; } std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent.get( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent.get( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = bash_skill() > 0; for( const auto &dest : g->m.points_in_radius( pos(), 1, 1 ) ) { int smell = g->scent.get( dest ); if( g->m.valid_move( pos(), dest, can_bash, true ) && ( can_move_to( dest ) || ( dest == g->u.pos() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) > 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
tripoint monster::scent_move() { std::vector<tripoint> smoves; int bestsmell = 10; // Squares with smell 0 are not eligible targets. int smell_threshold = 200; // Squares at or above this level are ineligible. if( has_flag( MF_KEENNOSE ) ) { bestsmell = 1; smell_threshold = 400; } const bool fleeing = is_fleeing( g->u ); if( fleeing ) { bestsmell = g->scent( pos() ); } tripoint next( -1, -1, posz() ); if( ( !fleeing && g->scent( pos() ) > smell_threshold ) || ( fleeing && bestsmell == 0 ) ) { return next; } const bool can_bash = has_flag( MF_BASHES ) || has_flag( MF_BORES ); for( const auto &dest : g->m.points_in_radius( pos(), 1 ) ) { int smell = g->scent( dest ); int mon = g->mon_at( dest ); if( ( mon == -1 || g->zombie( mon ).friendly != 0 || has_flag( MF_ATTACKMON ) ) && ( can_move_to( dest ) || ( dest == g->u.pos3() ) || ( can_bash && g->m.bash_rating( bash_estimate(), dest ) >= 0 ) ) ) { if( ( !fleeing && smell > bestsmell ) || ( fleeing && smell < bestsmell ) ) { smoves.clear(); smoves.push_back( dest ); bestsmell = smell; } else if( ( !fleeing && smell == bestsmell ) || ( fleeing && smell == bestsmell ) ) { smoves.push_back( dest ); } } } return random_entry( smoves, next ); }
void monster::plan(const std::vector<int> &friendlies) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc = 0; int stc = 0; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0, numz = g->num_zombies(); i < numz; i++) { monster *tmp = &(g->zombie(i)); if (tmp->friendly == 0) { int d = rl_dist(posx(), posy(), tmp->posx(), tmp->posy()); if (d < dist && g->m.sees(posx(), posy(), tmp->posx(), tmp->posy(), sightrange, tc)) { closest = i; dist = d; stc = tc; } } } if (has_effect("docile")) { closest = -1; } if (closest >= 0) { set_dest(g->zombie(closest).posx(), g->zombie(closest).posy(), stc); } else if (friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if (friendly < 0 && sees_player( tc ) ) { if (rl_dist(posx(), posy(), g->u.posx, g->u.posy) > 2) { set_dest(g->u.posx, g->u.posy, tc); } else { plans.clear(); } } return; } // If we can see, and we can see a character, move toward them or flee. if (can_see() && sees_player( tc ) ) { dist = rl_dist(posx(), posy(), g->u.posx, g->u.posy); if (is_fleeing(g->u)) { // Wander away. fleeing = true; set_dest(posx() * 2 - g->u.posx, posy() * 2 - g->u.posy, tc); } else { // Chase the player. closest = -2; stc = tc; } } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx(), posy(), me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx(), posy(), me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; set_dest(posx() * 2 - me->posx, posy() * 2 - me->posy, tc); \ } else { closest = i; stc = tc; } dist = medist; } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; if (can_see()) { for (int f = 0, numf = friendlies.size(); f < numf; f++) { const int i = friendlies[f]; monster *mon = &(g->zombie(i)); int mondist = rl_dist(posx(), posy(), mon->posx(), mon->posy()); if (mondist < dist && g->m.sees(posx(), posy(), mon->posx(), mon->posy(), sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx() * 2 - mon->posx(); wandy = posy() * 2 - mon->posy(); wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (closest == -2) { if (one_in(2)) {//random for the diversity of the trajectory ++stc; } else { --stc; } set_dest(g->u.posx, g->u.posy, stc); } else if (closest <= -3) set_dest(g->zombie(-3 - closest).posx(), g->zombie(-3 - closest).posy(), stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
void monster::plan(game *g) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc, stc; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0; i < g->z.size(); i++) { monster *tmp = &(g->z[i]); if (tmp->friendly == 0 && rl_dist(posx, posy, tmp->posx, tmp->posy) < dist && g->m.sees(posx, posy, tmp->posx, tmp->posy, sightrange, tc)) { closest = i; dist = rl_dist(posx, posy, tmp->posx, tmp->posy); stc = tc; } } if (has_effect(ME_DOCILE)) closest = -1; if (closest >= 0) set_dest(g->z[closest].posx, g->z[closest].posy, stc); else if (friendly > 0 && one_in(3)) // Grow restless with no targets friendly--; else if (friendly < 0 && g->sees_u(posx, posy, tc)) { if (rl_dist(posx, posy, g->u.posx, g->u.posy) > 2) set_dest(g->u.posx, g->u.posy, tc); else plans.clear(); } return; } if (is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { fleeing = true; wandx = posx * 2 - g->u.posx; wandy = posy * 2 - g->u.posy; wandf = 40; dist = rl_dist(posx, posy, g->u.posx, g->u.posy); } // If we can see, and we can see a character, start moving towards them if (!is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc)) { dist = rl_dist(posx, posy, g->u.posx, g->u.posy); closest = -2; stc = tc; } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx, posy, me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; wandx = posx * 2 - me->posx; wandy = posy * 2 - me->posy; wandf = 40; dist = medist; } else if (can_see() && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc)) { dist = rl_dist(posx, posy, me->posx, me->posy); closest = i; stc = tc; } } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; for (int i = 0; i < g->z.size(); i++) { monster *mon = &(g->z[i]); int mondist = rl_dist(posx, posy, mon->posx, mon->posy); if (mon->friendly != 0 && mondist < dist && can_see() && g->m.sees(posx, posy, mon->posx, mon->posy, sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx * 2 - mon->posx; wandy = posy * 2 - mon->posy; wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (!fleeing) { if (closest == -2) set_dest(g->u.posx, g->u.posy, stc); else if (closest <= -3) set_dest(g->z[-3 - closest].posx, g->z[-3 - closest].posy, stc); else if (closest >= 0) set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } }
// 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::process_effects() { for( auto effect_it = effects.begin(); effect_it != effects.end(); ++effect_it ) { std::string id = effect_it->second.get_id(); if (id == "nasty_poisoned") { mod_speed_bonus( -rng(3, 5) ); apply_damage( nullptr, bp_torso, rng( 3, 6 ) ); } if (id == "poisoned") { mod_speed_bonus( -rng(0, 3) ); apply_damage( nullptr, bp_torso, rng( 1, 3 ) ); // MATERIALS-TODO: use fire resistance } else if (id == "onfire") { if (made_of("flesh") || made_of("iflesh")) apply_damage( nullptr, bp_torso, rng( 3, 8 ) ); if (made_of("veggy")) apply_damage( nullptr, bp_torso, rng( 10, 20 ) ); if (made_of("paper") || made_of("powder") || made_of("wood") || made_of("cotton") || made_of("wool")) apply_damage( nullptr, bp_torso, rng( 15, 40 ) ); } } //If this monster has the ability to heal in combat, do it now. if( has_flag( MF_REGENERATES_50 ) ) { if( hp < type->hp ) { if( one_in( 2 ) && g->u.sees( this ) ) { add_msg( m_warning, _( "The %s is visibly regenerating!" ), name().c_str() ); } hp += 50; if( hp > type->hp ) { hp = type->hp; } } } if( has_flag( MF_REGENERATES_10 ) ) { if( hp < type->hp ) { if( one_in( 2 ) && g->u.sees( this ) ) { add_msg( m_warning, _( "The %s seems a little healthier." ), name().c_str() ); } hp += 10; if( hp > type->hp ) { hp = type->hp; } } } //Monster will regen morale and aggression if it is on max HP //It regens more morale and aggression if is currently fleeing. if( has_flag( MF_REGENMORALE ) && hp >= type->hp ) { if( is_fleeing( g->u ) ) { morale = type->morale; anger = type->agro; } if( morale <= type->morale ) { morale += 1; } if( anger <= type->agro ) { anger += 1; } if( morale < 0 ) { morale += 5; } if( anger < 0 ) { anger += 5; } } // If this critter dies in sunlight, check & assess damage. if( has_flag( MF_SUNDEATH ) && g->is_in_sunlight( posx(), posy() ) ) { if( g->u.sees( this ) ) { add_msg( m_good, _( "The %s burns horribly in the sunlight!" ), name().c_str() ); } hp -= 100; if( hp < 0 ) { hp = 0; } } Creature::process_effects(); }
// 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--; //Hallucinations have a chance of disappearing each turn if (is_hallucination() && one_in(25)) { die(g); return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } //If this monster has the ability to heal in combat, do it now. if (has_flag(MF_REGENERATES_50)) { hp += 50; if(hp > type->hp){ hp = type->hp; } } if (has_flag(MF_REGENERATES_10)) { hp += 10; if(hp > type->hp){ hp = type->hp; } } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (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) { 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->zombie(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. plans.clear(); 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 plans.clear(); point tmp = wander_next(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 // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(g, x, y); if(!did_something) { moves -= 100; // If we don't do this, we'll get infinite loops. } } 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); }
// General movement. // Currently, priority goes: // 1) Special Attack // 2) Sight-based tracking // 3) Scent-based tracking // 4) Sound-based tracking void monster::move() { // 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--; } //Hallucinations have a chance of disappearing each turn if (is_hallucination() && one_in(25)) { die(); return; } // First, use the special attack, if we can! if (sp_timeout > 0) { sp_timeout--; } //If this monster has the ability to heal in combat, do it now. if (has_flag(MF_REGENERATES_50)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s is visibly regenerating!"), name().c_str()); } hp += 50; if(hp > type->hp) { hp = type->hp; } } } if (has_flag(MF_REGENERATES_10)) { if (hp < type->hp) { if (one_in(2)) { g->add_msg(_("The %s seems a little healthier."), name().c_str()); } hp += 10; if(hp > type->hp) { hp = type->hp; } } } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if (has_flag(MF_ABSORBS)) { if(!g->m.i_at(posx(), posy()).empty()) { g->add_msg(_("The %s flows around the objects on the floor and they are quickly dissolved!"), name().c_str()); std::vector<item> items_absorbed = g->m.i_at(posx(), posy()); for( size_t i = 0; i < items_absorbed.size(); ++i ) { hp += items_absorbed.at(i).volume(); //Yeah this means it can get more HP than normal. } g->m.i_clear(posx(), posy()); } } //Monster will regen morale and agression if it is on max HP //It regens more morale and agression if is currently fleeing. if(has_flag(MF_REGENMORALE) && hp >= type->hp){ if(is_fleeing(g->u)){ morale = type->morale; anger = type->agro; } if(morale <= type->morale) morale += 1; if(anger <= type->agro) anger += 1; if(morale < 0) morale += 5; if(anger < 0) anger += 5; } // If this critter dies in sunlight, check & assess damage. if (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { g->add_msg(_("The %s burns horribly in the sunlight!"), name().c_str()); hp -= 100; if(hp < 0) { hp = 0 ; } } if (sp_timeout == 0 && (friendly == 0 || has_flag(MF_FRIENDLY_SPECIAL))) { mattack ma; if(!is_hallucination()) { (ma.*type->sp_attack)(this); } } if (moves < 0) { return; } if (has_flag(MF_IMMOBILE)) { moves = 0; return; } if (has_effect("stunned")) { stumble(false); moves = 0; return; } if (has_effect("downed")) { moves = 0; return; } if (has_effect("bouldering")) { moves -= 20; if (moves < 0) { return; } } if (friendly != 0) { if (friendly > 0) { friendly--; } friendly_move(); return; } bool moved = false; point next; int mondex = (!plans.empty() ? 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.empty()) { 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(false); return; } if (!plans.empty() && (mondex == -1 || g->zombie(mondex).friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(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. plans.clear(); point tmp = scent_move(); if (tmp.x != -1) { next = tmp; moved = true; } } if (wandf > 0 && !moved) { // No LOS, no scent, so as a fall-back follow sound plans.clear(); point tmp = wander_next(); 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 // Note: The below works because C++ in A() || B() won't call B() if A() is true int& x = next.x; int& y = next.y; // Define alias for x and y bool did_something = attack_at(x, y) || bash_at(x, y) || move_to(x, y); if(!did_something) { moves -= 100; // If we don't do this, we'll get infinite loops. } } 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.empty())) || !moved) { stumble(moved); } }
void monster::plan(const mfactions &factions) { // Bots are more intelligent than most living stuff bool electronic = has_flag( MF_ELECTRONIC ); Creature *target = nullptr; // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33 float dist = !electronic ? 1000 : 8.6f; int bresenham_slope = 0; int selected_slope = 0; bool fleeing = false; bool docile = has_flag( MF_VERMIN ) || ( friendly != 0 && has_effect( "docile" ) ); bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end(); int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0; int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0; bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale; bool swarms = has_flag( MF_SWARMS ); auto mood = attitude(); // If we can see the player, move toward them or flee. if( friendly == 0 && sees( g->u, bresenham_slope ) ) { dist = rate_target( g->u, bresenham_slope, dist, electronic ); fleeing = fleeing || is_fleeing( g->u ); target = &g->u; selected_slope = bresenham_slope; if( dist <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } else if( friendly != 0 && !docile ) { // Target unfriendly monsters, only if we aren't interacting with the player. for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) { monster &tmp = g->zombie( i ); if( tmp.friendly == 0 ) { float rating = rate_target( tmp, bresenham_slope, dist, electronic ); if( rating < dist ) { target = &tmp; dist = rating; selected_slope = bresenham_slope; } } } } if( !docile ) { for( size_t i = 0; i < g->active_npc.size(); i++ ) { npc *me = g->active_npc[i]; float rating = rate_target( *me, bresenham_slope, dist, electronic ); bool fleeing_from = is_fleeing( *me ); // Switch targets if closer and hostile or scarier than current target if( ( rating < dist && fleeing ) || ( rating < dist && attitude( me ) == MATT_ATTACK ) || ( !fleeing && fleeing_from ) ) { target = me; dist = rating; selected_slope = bresenham_slope; } fleeing = fleeing || fleeing_from; if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } fleeing = fleeing || ( mood == MATT_FLEE ); if( friendly == 0 && !docile ) { for( const auto &fac : factions ) { auto faction_att = faction->attitude( fac.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } for( int i : fac.second ) { // mon indices monster &mon = g->zombie( i ); float rating = rate_target( mon, bresenham_slope, dist, electronic ); if( rating < dist ) { target = &mon; dist = rating; selected_slope = bresenham_slope; } if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } } // Friendly monsters here // Avoid for hordes of same-faction stuff or it could get expensive const monfaction *actual_faction = friendly == 0 ? faction : GetMFact( "player" ); auto const &myfaction_iter = factions.find( actual_faction ); if( myfaction_iter == factions.end() ) { DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction " << ( friendly == 0 ? faction->name : "player" ) << " which wasn't loaded in game::monmove"; swarms = false; group_morale = false; } swarms = swarms && target == nullptr; // Only swarm if we have no target if( group_morale || swarms ) { auto const &myfaction = myfaction_iter->second; for( int i : myfaction ) { monster &mon = g->zombie( i ); float rating = rate_target( mon, bresenham_slope, dist, electronic ); if( group_morale && rating <= 10 ) { morale += 10 - rating; } if( swarms ) { if( rating < 5 ) { // Too crowded here wandx = posx() * rng( 1, 3 ) - mon.posx(); wandy = posy() * rng( 1, 3 ) - mon.posy(); wandf = 2; target = nullptr; // Swarm to the furthest ally you can see } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) { target = &mon; dist = rating; selected_slope = bresenham_slope; } } } } if( target != nullptr ) { if( one_in( 2 ) ) { // Random for the diversity of the trajectory ++selected_slope; } else { --selected_slope; } point dest = target->pos(); auto att_to_target = attitude_to( *target ); if( att_to_target == Attitude::A_HOSTILE && !fleeing ) { set_dest( dest.x, dest.y, selected_slope ); } else if( fleeing ) { set_dest( posx() * 2 - dest.x, posy() * 2 - dest.y, selected_slope ); } if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) { int hp_per = target->hp_percentage(); if( hp_per <= 70 ) { anger += 10 - int( hp_per / 10 ); } } } else if( friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if( friendly < 0 && sees( g->u, bresenham_slope ) ) { if( rl_dist( pos(), g->u.pos() ) > 2 ) { set_dest(g->u.posx(), g->u.posy(), bresenham_slope); } else { plans.clear(); } } // If we're not adjacent to the start of our plan path, don't act on it. // This is to catch when we had pre-existing invalid plans and // made it through the function without changing them. if( !plans.empty() && square_dist(pos().x, pos().y, plans.front().x, plans.front().y ) > 1 ) { plans.clear(); } }
void monster::plan( const mfactions &factions ) { // Bots are more intelligent than most living stuff bool smart_planning = has_flag( MF_PRIORITIZE_TARGETS ); Creature *target = nullptr; // 8.6f is rating for tank drone 60 tiles away, moose 16 or boomer 33 float dist = !smart_planning ? 1000 : 8.6f; bool fleeing = false; bool docile = friendly != 0 && has_effect( effect_docile ); bool angers_hostile_weak = type->anger.find( MTRIG_HOSTILE_WEAK ) != type->anger.end(); int angers_hostile_near = ( type->anger.find( MTRIG_HOSTILE_CLOSE ) != type->anger.end() ) ? 5 : 0; int fears_hostile_near = ( type->fear.find( MTRIG_HOSTILE_CLOSE ) != type->fear.end() ) ? 5 : 0; bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale; bool swarms = has_flag( MF_SWARMS ); auto mood = attitude(); // If we can see the player, move toward them or flee. if( friendly == 0 && sees( g->u ) ) { dist = rate_target( g->u, dist, smart_planning ); fleeing = fleeing || is_fleeing( g->u ); target = &g->u; if( dist <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } else if( friendly != 0 && !docile ) { // Target unfriendly monsters, only if we aren't interacting with the player. for( int i = 0, numz = g->num_zombies(); i < numz; i++ ) { monster &tmp = g->zombie( i ); if( tmp.friendly == 0 ) { float rating = rate_target( tmp, dist, smart_planning ); if( rating < dist ) { target = &tmp; dist = rating; } } } } if( docile ) { if( friendly != 0 && target != nullptr ) { set_dest( target->pos() ); } return; } for( size_t i = 0; i < g->active_npc.size(); i++ ) { npc &who = *g->active_npc[i]; auto faction_att = faction.obj().attitude( who.get_monster_faction() ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } float rating = rate_target( who, dist, smart_planning ); bool fleeing_from = is_fleeing( who ); // Switch targets if closer and hostile or scarier than current target if( ( rating < dist && fleeing ) || ( rating < dist && attitude( &who ) == MATT_ATTACK ) || ( !fleeing && fleeing_from ) ) { target = &who; dist = rating; } fleeing = fleeing || fleeing_from; if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } fleeing = fleeing || ( mood == MATT_FLEE ); if( friendly == 0 ) { for( const auto &fac : factions ) { auto faction_att = faction.obj().attitude( fac.first ); if( faction_att == MFA_NEUTRAL || faction_att == MFA_FRIENDLY ) { continue; } for( int i : fac.second ) { // mon indices monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, smart_planning ); if( rating < dist ) { target = &mon; dist = rating; } if( rating <= 5 ) { anger += angers_hostile_near; morale -= fears_hostile_near; } } } } // Friendly monsters here // Avoid for hordes of same-faction stuff or it could get expensive const auto actual_faction = friendly == 0 ? faction : mfaction_str_id( "player" ); auto const &myfaction_iter = factions.find( actual_faction ); if( myfaction_iter == factions.end() ) { DebugLog( D_ERROR, D_GAME ) << disp_name() << " tried to find faction " << actual_faction.id().str() << " which wasn't loaded in game::monmove"; swarms = false; group_morale = false; } swarms = swarms && target == nullptr; // Only swarm if we have no target if( group_morale || swarms ) { for( const int i : myfaction_iter->second ) { monster &mon = g->zombie( i ); float rating = rate_target( mon, dist, smart_planning ); if( group_morale && rating <= 10 ) { morale += 10 - rating; } if( swarms ) { if( rating < 5 ) { // Too crowded here wander_pos.x = posx() * rng( 1, 3 ) - mon.posx(); wander_pos.y = posy() * rng( 1, 3 ) - mon.posy(); wandf = 2; target = nullptr; // Swarm to the furthest ally you can see } else if( rating < INT_MAX && rating > dist && wandf <= 0 ) { target = &mon; dist = rating; } } } } if( target != nullptr ) { tripoint dest = target->pos(); auto att_to_target = attitude_to( *target ); if( att_to_target == Attitude::A_HOSTILE && !fleeing ) { set_dest( dest ); } else if( fleeing ) { set_dest( tripoint( posx() * 2 - dest.x, posy() * 2 - dest.y, posz() ) ); } if( angers_hostile_weak && att_to_target != Attitude::A_FRIENDLY ) { int hp_per = target->hp_percentage(); if( hp_per <= 70 ) { anger += 10 - int( hp_per / 10 ); } } } else if( friendly > 0 && one_in( 3 ) ) { // Grow restless with no targets friendly--; } else if( friendly < 0 && sees( g->u ) ) { if( rl_dist( pos(), g->u.pos() ) > 2 ) { set_dest( g->u.pos() ); } else { unset_dest(); } } }
void monster::process_effects() { // Monster only effects int mod = 1; for( auto &elem : effects ) { for( auto &_effect_it : elem.second ) { auto &it = _effect_it.second; // Monsters don't get trait-based reduction, but they do get effect based reduction bool reduced = has_effect(it.get_resist_effect()); mod_speed_bonus(it.get_mod("SPEED", reduced)); int val = it.get_mod("HURT", reduced); if (val > 0) { if(it.activated(calendar::turn, "HURT", val, reduced, mod)) { apply_damage(nullptr, bp_torso, val); } } std::string id = _effect_it.second.get_id(); // MATERIALS-TODO: use fire resistance if (id == "onfire") { if (made_of("flesh") || made_of("iflesh")) apply_damage( nullptr, bp_torso, rng( 3, 8 ) ); if (made_of("veggy")) apply_damage( nullptr, bp_torso, rng( 10, 20 ) ); if (made_of("paper") || made_of("powder") || made_of("wood") || made_of("cotton") || made_of("wool")) apply_damage( nullptr, bp_torso, rng( 15, 40 ) ); } } } //If this monster has the ability to heal in combat, do it now. if( has_flag( MF_REGENERATES_50 ) ) { if( hp < type->hp ) { if( one_in( 2 ) && g->u.sees( *this ) ) { add_msg( m_warning, _( "The %s is visibly regenerating!" ), name().c_str() ); } hp += 50; if( hp > type->hp ) { hp = type->hp; } } } if( has_flag( MF_REGENERATES_10 ) ) { if( hp < type->hp ) { if( one_in( 2 ) && g->u.sees( *this ) ) { add_msg( m_warning, _( "The %s seems a little healthier." ), name().c_str() ); } hp += 10; if( hp > type->hp ) { hp = type->hp; } } } //Monster will regen morale and aggression if it is on max HP //It regens more morale and aggression if is currently fleeing. if( has_flag( MF_REGENMORALE ) && hp >= type->hp ) { if( is_fleeing( g->u ) ) { morale = type->morale; anger = type->agro; } if( morale <= type->morale ) { morale += 1; } if( anger <= type->agro ) { anger += 1; } if( morale < 0 ) { morale += 5; } if( anger < 0 ) { anger += 5; } } // If this critter dies in sunlight, check & assess damage. if( has_flag( MF_SUNDEATH ) && g->is_in_sunlight( posx(), posy() ) ) { if( g->u.sees( *this ) ) { add_msg( m_good, _( "The %s burns horribly in the sunlight!" ), name().c_str() ); } apply_damage( nullptr, bp_torso, 100 ); if( hp < 0 ) { hp = 0; } } Creature::process_effects(); }
void monster::plan(game *g) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc, stc; if (friendly != 0) { // Target monsters, not the player! for (int i = 0; i < g->z.size(); i++) { monster *tmp = &(g->z[i]); if (tmp->friendly == 0 && rl_dist(posx, posy, tmp->posx, tmp->posy) < dist && g->m.sees(posx, posy, tmp->posx, tmp->posy, sightrange, tc)) { closest = i; dist = rl_dist(posx, posy, tmp->posx, tmp->posy); stc = tc; } } if (closest >= 0) set_dest(g->z[closest].posx, g->z[closest].posy, stc); else if (friendly > 0 && one_in(3)) // Grow restless with no targets friendly--; else if (friendly < 0 && g->sees_u(posx, posy, tc)) { if (rl_dist(posx, posy, g->u.posx, g->u.posy) > 2) set_dest(g->u.posx, g->u.posy, tc); else plans.clear(); } return; } if (is_fleeing(g->u) && can_see() && g->sees_u(posx, posy, tc) && (!g->u.has_trait(PF_ANIMALEMPATH) || !has_flag(MF_ANIMAL))) { wandx = posx * 2 - g->u.posx; wandy = posy * 2 - g->u.posy; wandf = 40; } // If we can see, and we can see a character, start moving towards them if (!is_fleeing(g->u) && can_see()) { if (g->sees_u(posx, posy, tc)) { dist = rl_dist(posx, posy, g->u.posx, g->u.posy); closest = -2; stc = tc; } for (int i = 0; i < g->active_npc.size(); i++) { npc *me = &(g->active_npc[i]); if (rl_dist(posx, posy, me->posx, me->posy) < dist && g->m.sees(posx, posy, me->posx, me->posy, sightrange, tc)) { dist = rl_dist(posx, posy, me->posx, me->posy); closest = i; stc = tc; } } for (int i = 0; i < g->z.size(); i++) { monster *mon = &(g->z[i]); if (mon->friendly != 0 && rl_dist(posx, posy, mon->posx, mon->posy) < dist && g->m.sees(posx, posy, mon->posx, mon->posy, sightrange, tc)) { dist = rl_dist(posx, posy, mon->posx, mon->posy); closest = -3 - i; stc = tc; } } if (closest == -2) set_dest(g->u.posx, g->u.posy, stc); else if (closest <= -3) set_dest(g->z[-3 - closest].posx, g->z[-3 - closest].posy, stc); else if (closest >= 0) set_dest(g->active_npc[closest].posx, g->active_npc[closest].posy, stc); } }
void monster::plan(const std::vector<int> &friendlies) { int sightrange = g->light_level(); int closest = -1; int dist = 1000; int tc = 0; int stc = 0; bool fleeing = false; if (friendly != 0) { // Target monsters, not the player! for (int i = 0, numz = g->num_zombies(); i < numz; i++) { monster *tmp = &(g->zombie(i)); if (tmp->friendly == 0) { int d = rl_dist(posx(), posy(), tmp->posx(), tmp->posy()); if (d < dist && g->m.sees(posx(), posy(), tmp->posx(), tmp->posy(), sightrange, tc)) { closest = i; dist = d; stc = tc; } } } if (has_effect("docile")) { closest = -1; } if (closest >= 0) { set_dest(g->zombie(closest).posx(), g->zombie(closest).posy(), stc); } else if (friendly > 0 && one_in(3)) { // Grow restless with no targets friendly--; } else if (friendly < 0 && sees_player( tc ) ) { if (rl_dist(posx(), posy(), g->u.posx, g->u.posy) > 2) { set_dest(g->u.posx, g->u.posy, tc); } else { plans.clear(); } } return; } // If we can see, and we can see a character, move toward them or flee. if (can_see() && sees_player( tc ) ) { dist = rl_dist(posx(), posy(), g->u.posx, g->u.posy); if (is_fleeing(g->u)) { // Wander away. fleeing = true; set_dest(posx() * 2 - g->u.posx, posy() * 2 - g->u.posy, tc); } else { // Chase the player. closest = -2; stc = tc; } } for (size_t i = 0; i < g->active_npc.size(); i++) { npc *me = (g->active_npc[i]); int medist = rl_dist(posx(), posy(), me->posx, me->posy); if ((medist < dist || (!fleeing && is_fleeing(*me))) && (can_see() && g->m.sees(posx(), posy(), me->posx, me->posy, sightrange, tc))) { if (is_fleeing(*me)) { fleeing = true; set_dest(posx() * 2 - me->posx, posy() * 2 - me->posy, tc);\ } else { closest = i; stc = tc; } dist = medist; } } if (!fleeing) { fleeing = attitude() == MATT_FLEE; if (can_see()) { for( auto &friendlie : friendlies ) { const int i = friendlie; monster *mon = &(g->zombie(i)); int mondist = rl_dist(posx(), posy(), mon->posx(), mon->posy()); if (mondist < dist && g->m.sees(posx(), posy(), mon->posx(), mon->posy(), sightrange, tc)) { dist = mondist; if (fleeing) { wandx = posx() * 2 - mon->posx(); wandy = posy() * 2 - mon->posy(); wandf = 40; } else { closest = -3 - i; stc = tc; } } } } if (closest == -2) { if (one_in(2)) {//random for the diversity of the trajectory ++stc; } else { --stc; } set_dest(g->u.posx, g->u.posy, stc); } else if (closest <= -3) { set_dest(g->zombie(-3 - closest).posx(), g->zombie(-3 - closest).posy(), stc); } else if (closest >= 0) { set_dest(g->active_npc[closest]->posx, g->active_npc[closest]->posy, stc); } } // If we're not adjacent to the start of our plan path, don't act on it. // This is to catch when we had pre-existing invalid plans and // made it through the function without changing them. if( !plans.empty() && square_dist(pos().x, pos().y, plans.front().x, plans.front().y ) > 1 ) { plans.clear(); } }