bool monster::push_to( const tripoint &p, const int boost, const size_t depth ) { if( is_hallucination() ) { // Don't let hallucinations push, not even other hallucinations return false; } if( !has_flag( MF_PUSH_MON ) || depth > 2 || has_effect( effect_pushed ) ) { return false; } // TODO: Generalize this to Creature monster *const critter = g->critter_at<monster>( p ); if( critter == nullptr || critter == this || p == pos() ) { return false; } if( !can_move_to( p ) ) { return false; } if( critter->is_hallucination() ) { // Kill the hallu, but return false so that the regular move_to is uses instead critter->die( nullptr ); return false; } // Stability roll of the pushed critter const int defend = critter->stability_roll(); // Stability roll of the pushing zed const int attack = stability_roll() + boost; if( defend > attack ) { return false; } const int movecost_from = 50 * g->m.move_cost( p ); const int movecost_attacker = std::max( movecost_from, 200 - 10 * ( attack - defend ) ); const tripoint dir = p - pos(); // Mark self as pushed to simplify recursive pushing add_effect( effect_pushed, 1 ); for( size_t i = 0; i < 6; i++ ) { const int dx = rng( -1, 1 ); const int dy = rng( -1, 1 ); if( dx == 0 && dy == 0 ) { continue; } // Pushing forward is easier than pushing aside const int direction_penalty = abs( dx - dir.x ) + abs( dy - dir.y ); if( direction_penalty > 2 ) { continue; } tripoint dest( p.x + dx, p.y + dy, p.z ); // Pushing into cars/windows etc. is harder const int movecost_penalty = g->m.move_cost( dest ) - 2; if( movecost_penalty <= -2 ) { // Can't push into unpassable terrain continue; } int roll = attack - ( defend + direction_penalty + movecost_penalty ); if( roll < 0 ) { continue; } Creature *critter_recur = g->critter_at( dest ); if( critter_recur == nullptr || critter_recur->is_hallucination() ) { // Try to push recursively monster *mon_recur = dynamic_cast< monster * >( critter_recur ); if( mon_recur == nullptr ) { continue; } if( critter->push_to( dest, roll, depth + 1 ) ) { // The tile isn't necessarily free, need to check if( !g->critter_at( p ) ) { move_to( p ); } moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; } else { continue; } } critter_recur = g->critter_at( dest ); if( critter_recur != nullptr ) { if( critter_recur->is_hallucination() ) { critter_recur->die( nullptr ); } else { return false; } } critter->setpos( dest ); move_to( p ); moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; } // Try to trample over a much weaker zed (or one with worse rolls) // Don't allow trampling with boost if( boost > 0 || attack < 2 * defend ) { return false; } g->swap_critters( *critter, *this ); critter->add_effect( effect_stunned, rng( 0, 2 ) ); // Only print the message when near player or it can get spammy if( rl_dist( g->u.pos(), pos() ) < 4 && g->u.sees( *critter ) ) { add_msg( m_warning, _( "The %1$s tramples %2$s" ), name().c_str(), critter->disp_name().c_str() ); } moves -= movecost_attacker; if( movecost_from > 100 ) { critter->add_effect( effect_downed, movecost_from / 100 + 1 ); } else { critter->moves -= movecost_from; } return true; }
bool item::is_cutting_weapon() { return (type->melee_cut >= 8 && !has_flag(IF_SPEAR)); }
point monster::sound_move(game *g) { plans.clear(); point next; bool xbest = true; if (abs(wandy - posy) > abs(wandx - posx))// which is more important xbest = false; next.x = posx; next.y = posy; int x = posx, x2 = posx - 1, x3 = posx + 1; int y = posy, y2 = posy - 1, y3 = posy + 1; if (wandx < posx) { x--; x2++; } if (wandx > posx) { x++; x2++; x3 -= 2; } if (wandy < posy) { y--; y2++; } if (wandy > posy) { y++; y2++; y3 -= 2; } if (xbest) { if (can_move_to(g->m, x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y))) { next.x = x; next.y = y; } else if (can_move_to(g->m, x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(g->m, x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(g->m, x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y3))) { next.x = x; next.y = y3; } else if (can_move_to(g->m, x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x3, y))) { next.x = x3; next.y = y; } } else { if (can_move_to(g->m, x, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y))) { next.x = x; next.y = y; } else if (can_move_to(g->m, x2, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x2, y))) { next.x = x2; next.y = y; } else if (can_move_to(g->m, x, y2) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y2))) { next.x = x; next.y = y2; } else if (can_move_to(g->m, x3, y) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x3, y))) { next.x = x3; next.y = y; } else if (can_move_to(g->m, x, y3) || (x == g->u.posx && y == g->u.posy) || (has_flag(MF_BASHES) && g->m.has_flag(bashable, x, y3))) { next.x = x; next.y = y3; } } return next; }
bool map::is_destructable(int x, int y) { return (has_flag(bashable, x, y) || (move_cost(x, y) == 0 && !has_flag(transparent, x, y))); }
void map::drawsq(WINDOW* w, player &u, int x, int y, bool invert, bool show_items) { if (!inbounds(x, y)) return; // Out of bounds int k = x + SEEX - u.posx; int j = y + SEEY - u.posy; nc_color tercol; char sym = terlist[ter(x, y)].sym; bool hi = false; if (u.has_disease(DI_BOOMERED)) tercol = c_magenta; else if ((u.is_wearing(itm_goggles_nv) && u.has_active_item(itm_UPS_on)) || u.has_active_bionic(bio_night_vision)) tercol = c_ltgreen; else tercol = terlist[ter(x, y)].color; if (move_cost(x, y) == 0 && has_flag(swimmable, x, y) && !u.underwater) show_items = false; // Can only see underwater items if WE are underwater // If there's a trap here, and we have sufficient perception, draw that instead if (tr_at(x, y) != tr_null && u.per_cur - u.encumb(bp_eyes) >= (*traps)[tr_at(x, y)]->visibility) { tercol = (*traps)[tr_at(x, y)]->color; if ((*traps)[tr_at(x, y)]->sym == '%') { switch(rng(1, 5)) { case 1: sym = '*'; break; case 2: sym = '0'; break; case 3: sym = '8'; break; case 4: sym = '&'; break; case 5: sym = '+'; break; } } else sym = (*traps)[tr_at(x, y)]->sym; } // If there's a field here, draw that instead (unless its symbol is %) if (field_at(x, y).type != fd_null) { tercol = fieldlist[field_at(x, y).type].color[field_at(x, y).density - 1]; if (fieldlist[field_at(x, y).type].sym == '*') { switch (rng(1, 5)) { case 1: sym = '*'; break; case 2: sym = '0'; break; case 3: sym = '8'; break; case 4: sym = '&'; break; case 5: sym = '+'; break; } } else if (fieldlist[field_at(x, y).type].sym != '%') sym = fieldlist[field_at(x, y).type].sym; } // If there's items here, draw those instead if (show_items && i_at(x, y).size() > 0 && field_at(x, y).is_null()) { if ((terlist[ter(x, y)].sym != '.')) hi = true; else { tercol = i_at(x, y)[i_at(x, y).size() - 1].color(); if (i_at(x, y).size() > 1) invert = !invert; sym = i_at(x, y)[i_at(x, y).size() - 1].symbol(); } } if (invert) mvwputch_inv(w, j, k, tercol, sym); else if (hi) mvwputch_hi (w, j, k, tercol, sym); else mvwputch (w, j, k, tercol, sym); }
void monster::melee_attack(Creature &target, bool, matec_id) { mod_moves(-100); if (type->melee_dice == 0) { // We don't attack, so just return return; } add_effect("hit_by_player", 3); // Make us a valid target for a few turns if (has_flag(MF_HIT_AND_RUN)) { add_effect("run", 4); } bool u_see_me = g->u_see(this); body_part bp_hit; //int highest_hit = 0; int hitstat = type->melee_skill; int hitroll = dice(hitstat,10); damage_instance damage; if(!is_hallucination()) { if (type->melee_dice > 0) { damage.add_damage(DT_BASH, dice(type->melee_dice,type->melee_sides)); } if (type->melee_cut > 0) { damage.add_damage(DT_CUT, type->melee_cut); } } /* TODO: height-related bodypart selection //If the player is knocked down or the monster can fly, any body part is a valid target if(target.is_on_ground() || has_flag(MF_FLIES)){ highest_hit = 20; } else { switch (type->size) { case MS_TINY: highest_hit = 3; break; case MS_SMALL: highest_hit = 12; break; case MS_MEDIUM: highest_hit = 20; break; case MS_LARGE: highest_hit = 28; break; case MS_HUGE: highest_hit = 35; break; } if (digging()){ highest_hit -= 8; } if (highest_hit <= 1){ highest_hit = 2; } } if (highest_hit > 20){ highest_hit = 20; } int bp_rand = rng(0, highest_hit - 1); if (bp_rand <= 2){ bp_hit = bp_legs; } else if (bp_rand <= 10){ bp_hit = bp_torso; } else if (bp_rand <= 14){ bp_hit = bp_arms; } else if (bp_rand <= 16){ bp_hit = bp_mouth; } else if (bp_rand == 18){ bp_hit = bp_eyes; } else{ bp_hit = bp_head; } */ dealt_damage_instance dealt_dam; int hitspread = target.deal_melee_attack(this, hitroll); if (hitspread >= 0) { target.deal_melee_hit(this, hitspread, false, damage, dealt_dam); } bp_hit = dealt_dam.bp_hit; if (hitspread < 0) { // a miss // TODO: characters practice dodge when a hit misses 'em if (target.is_player()) { if (u_see_me) { Messages::player_messages.add_msg(_("You dodge %1$s."), disp_name().c_str()); } else { Messages::player_messages.add_msg(_("You dodge an attack from an unseen source.")); } } else { if (u_see_me) { Messages::player_messages.add_msg(_("The %1$s dodges %2$s attack."), name().c_str(), target.disp_name(true).c_str()); } } //Hallucinations always produce messages but never actually deal damage } else if (is_hallucination() || dealt_dam.total_damage() > 0) { if (target.is_player()) { if (u_see_me) { Messages::player_messages.add_msg(_("The %1$s hits your %2$s."), name().c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } else { Messages::player_messages.add_msg(_("Something hits your %s."), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } } else { if (u_see_me) { Messages::player_messages.add_msg(_("The %1$s hits %2$s %3$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str()); } } } else { if (target.is_player()) { if (u_see_me) { Messages::player_messages.add_msg(_("The %1$s hits your %2$s, but your %3$s protects you."), name().c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str(), target.skin_name().c_str()); } else { Messages::player_messages.add_msg(_("Something hits your %1$s, but your %2$s protects you."), body_part_name(bp_hit, random_side(bp_hit)).c_str(), target.skin_name().c_str()); } } else { if (u_see_me) { Messages::player_messages.add_msg(_("The %1$s hits %2$s %3$s but is stopped by %2$s %4$s."), name().c_str(), target.disp_name(true).c_str(), body_part_name(bp_hit, random_side(bp_hit)).c_str(), target.skin_name().c_str()); } } } if (is_hallucination()) { if(one_in(7)) { die(); } return; } // 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; } } }
bool monster::can_hear() { return has_flag(MF_HEARS) && !has_effect(ME_DEAF); }
/* * Enter a store, and interact with it. * * Note that we use the standard "request_command()" function * to get a command, allowing us to use "command_arg" and all * command macros and other nifty stuff, but we use the special * "shopping" argument, to force certain commands to be converted * into other commands, normally, we convert "p" (pray) and "m" * (cast magic) into "g" (get), and "s" (search) into "d" (drop). */ void do_cmd_store(void) { s32b which; s32b maintain_num; s32b i; bool recreate = FALSE; store_type *st_ptr; store_info_type *sti_ptr; cave_type *c_ptr; /* Access the player grid */ c_ptr = &cave[p_ptr->py][p_ptr->px]; /* Verify a store */ if (!cave_feat_is(c_ptr, FLAG_CONTAINS_BUILDING)) { msg_print("You see no store here."); return; } /* Extract the store code */ which = get_flag(c_ptr, FLAG_CONTAINS_BUILDING); /* Get the store */ st_ptr = flag_get_store(&town_info[p_ptr->town_num].stores, which); /* If it does not exists yet, create it */ if (!st_ptr) { call_lua("store.create_for_town", "(d,d)", "", p_ptr->town_num, which); st_ptr = flag_get_store(&town_info[p_ptr->town_num].stores, which); if (!st_ptr) quit(format("Could not create store %d town %d! BAD BAD BAD!", which, p_ptr->town_num)); } /* Hack -- Check the "locked doors" */ if (st_ptr->store_open > turn) { msg_print("The doors are locked."); return; } sti_ptr = &st_info[st_ptr->st_idx]; /* Calculate the number of store maintainances since the last visit */ if (has_flag(sti_ptr, FLAG_STORE_MAINTAIN_TURNS)) { if (get_flag(sti_ptr, FLAG_STORE_MAINTAIN_TURNS) >= 1) maintain_num = (turn - st_ptr->last_visit) / (10L * get_flag(sti_ptr, FLAG_STORE_MAINTAIN_TURNS)); else maintain_num = 0; } else maintain_num = (turn - st_ptr->last_visit) / (10L * STORE_TURNS); /* Maintain the store max. 10 times */ if (maintain_num > 10) maintain_num = 10; if (maintain_num) { /* Maintain the store */ for (i = 0; i < maintain_num; i++) call_lua("store.maintain", "(S)", "", st_ptr); /* Save the visit */ st_ptr->last_visit = turn; } /* Forget the lite */ /* forget_lite(); */ /* Forget the view */ forget_view(); call_lua("store.display", "(S)", "", st_ptr); /* Free turn XXX XXX XXX */ energy_use = 0; /* Recreate the level only when needed */ if (recreate) { /* Reinit wilderness to activate quests ... */ p_ptr->oldpx = p_ptr->px; p_ptr->oldpy = p_ptr->py; p_ptr->leaving = TRUE; } /* Hack -- Cancel automatic command */ command_new = 0; /* Hack -- Cancel "see" mode */ command_see = FALSE; /* Flush messages XXX XXX XXX */ msg_print(NULL); /* Clear the screen */ Term_clear(); /* Update everything */ p_ptr->update |= (PU_VIEW | PU_MON_LITE); p_ptr->update |= (PU_MONSTERS); /* Redraw entire screen */ flag_bool(&p_ptr->redraw, FLAG_PR_BASIC); flag_bool(&p_ptr->redraw, FLAG_PR_EXTRA); /* Redraw map */ flag_bool(&p_ptr->redraw, FLAG_PR_MAP); /* Window stuff */ flag_bool(&p_ptr->window, FLAG_PW_OVERHEAD); }
void monster::die(game *g) { if (!dead) dead = true; // Drop goodies int total_chance = 0, total_it_chance, cur_chance, selected_location, selected_item; bool animal_done = false; std::vector<items_location_and_chance> it = g->monitems[type->id]; std::vector<itype_id> mapit; if (type->item_chance != 0 && it.size() == 0) debugmsg("Type %s has item_chance %d but no items assigned!", type->name.c_str(), type->item_chance); else { for (int i = 0; i < it.size(); i++) total_chance += it[i].chance; while (rng(0, 99) < abs(type->item_chance) && !animal_done) { cur_chance = rng(1, total_chance); selected_location = -1; while (cur_chance > 0) { selected_location++; cur_chance -= it[selected_location].chance; } total_it_chance = 0; mapit = g->mapitems[it[selected_location].loc]; for (int i = 0; i < mapit.size(); i++) total_it_chance += g->itypes[mapit[i]]->rarity; cur_chance = rng(1, total_it_chance); selected_item = -1; while (cur_chance > 0) { selected_item++; cur_chance -= g->itypes[mapit[selected_item]]->rarity; } g->m.spawn_item(posx, posy, g->itypes[mapit[selected_item]], 0); if (type->item_chance < 0) animal_done = true; // Only drop ONE item. } } // Done dropping items // If we're a queen, make nearby groups of our type start to die out if (has_flag(MF_QUEEN)) { // Do it for overmap above/below too for(int z = 0; z >= -1; --z) { std::vector<mongroup*> groups = g->cur_om.monsters_at(g->levx, g->levy, z); for (int i = 0; i < groups.size(); i++) { if (MonsterGroupManager::IsMonsterInGroup(groups[i]->type, mon_id(type->id))) groups[i]->dying = true; } } } // If we're a mission monster, update the mission if (mission_id != -1) { mission_type *misstype = g->find_mission_type(mission_id); if (misstype->goal == MGOAL_FIND_MONSTER) g->fail_mission(mission_id); if (misstype->goal == MGOAL_KILL_MONSTER) g->mission_step_complete(mission_id, 1); } // Also, perform our death function mdeath md; (md.*type->dies)(g, this); // If our species fears seeing one of our own die, process that int anger_adjust = 0, morale_adjust = 0; for (int i = 0; i < type->anger.size(); i++) { if (type->anger[i] == MTRIG_FRIEND_DIED) anger_adjust += 15; } for (int i = 0; i < type->placate.size(); i++) { if (type->placate[i] == MTRIG_FRIEND_DIED) anger_adjust -= 15; } for (int i = 0; i < type->fear.size(); i++) { if (type->fear[i] == MTRIG_FRIEND_DIED) morale_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { int light = g->light_level(); for (int i = 0; i < g->z.size(); i++) { int t = 0; if (g->m.sees(g->z[i].posx, g->z[i].posy, posx, posy, light, t)) { g->z[i].morale += morale_adjust; g->z[i].anger += anger_adjust; } } } }
bool monster::can_move_to( const tripoint &p ) const { const bool can_climb = has_flag( MF_CLIMBS ) || has_flag( MF_FLIES ); if( g->m.impassable( p ) && !( can_climb && g->m.has_flag( "CLIMBABLE", p ) ) ) { return false; } if( !can_submerge() && g->m.has_flag( TFLAG_DEEP_WATER, p ) ) { return false; } if( has_flag( MF_DIGS ) && !g->m.has_flag( "DIGGABLE", p ) ) { return false; } if( has_flag( MF_AQUATIC ) && !g->m.has_flag( "SWIMMABLE", p ) ) { return false; } if( has_flag( MF_SUNDEATH ) && g->is_in_sunlight( p ) ) { return false; } // Various avoiding behaviors if( has_flag( MF_AVOID_DANGER_1 ) || has_flag( MF_AVOID_DANGER_2 ) ) { const ter_id target = g->m.ter( p ); // Don't enter lava ever if( target == t_lava ) { return false; } // Don't ever throw ourselves off cliffs if( !g->m.has_floor( p ) && !has_flag( MF_FLIES ) ) { return false; } // Don't enter open pits ever unless tiny, can fly or climb well if( !( type->size == MS_TINY || can_climb ) && ( target == t_pit || target == t_pit_spiked || target == t_pit_glass ) ) { return false; } // The following behaviors are overridden when attacking if( attitude( &( g->u ) ) != MATT_ATTACK ) { if( g->m.has_flag( "SHARP", p ) && !( type->size == MS_TINY || has_flag( MF_FLIES ) ) ) { return false; } } const field &target_field = g->m.field_at( p ); // Differently handled behaviors if( has_flag( MF_AVOID_DANGER_2 ) ) { const trap &target_trap = g->m.tr_at( p ); // Don't enter any dangerous fields if( is_dangerous_fields( target_field ) ) { return false; } // Don't step on any traps (if we can see) if( has_flag( MF_SEES ) && !target_trap.is_benign() && g->m.has_floor( p ) ) { return false; } } else if( has_flag( MF_AVOID_DANGER_1 ) ) { // Don't enter fire or electricity ever (other dangerous fields are fine though) if( target_field.findField( fd_fire ) || target_field.findField( fd_electricity ) ) { return false; } } } return true; }
bool monster::move_to( const tripoint &p, bool force, const float stagger_adjustment ) { const bool digs = digging(); const bool flies = has_flag( MF_FLIES ); const bool on_ground = !digs && !flies; const bool climbs = has_flag( MF_CLIMBS ) && g->m.has_flag( TFLAG_NO_FLOOR, p ); // Allows climbing monsters to move on terrain with movecost <= 0 Creature *critter = g->critter_at( p, is_hallucination() ); if( g->m.has_flag( "CLIMBABLE", p ) ) { if( g->m.impassable( p ) && critter == nullptr ) { if( flies ) { moves -= 100; force = true; if( g->u.sees( *this ) ) { add_msg( _( "The %1$s flies over the %2$s." ), name().c_str(), g->m.has_flag_furn( "CLIMBABLE", p ) ? g->m.furnname( p ).c_str() : g->m.tername( p ).c_str() ); } } else if( has_flag( MF_CLIMBS ) ) { moves -= 150; force = true; if( g->u.sees( *this ) ) { add_msg( _( "The %1$s climbs over the %2$s." ), name().c_str(), g->m.has_flag_furn( "CLIMBABLE", p ) ? g->m.furnname( p ).c_str() : g->m.tername( p ).c_str() ); } } } } if( critter != nullptr && !force ) { return false; } // Make sure that we can move there, unless force is true. if( !force && !can_move_to( p ) ) { return false; } if( !force ) { // This adjustment is to make it so that monster movement speed relative to the player // is consistent even if the monster stumbles, // and the same regardless of the distance measurement mode. // Note: Keep this as float here or else it will cancel valid moves const float cost = stagger_adjustment * ( float )( climbs ? calc_climb_cost( pos(), p ) : calc_movecost( pos(), p ) ); if( cost > 0.0f ) { moves -= ( int )ceil( cost ); } else { return false; } } //Check for moving into/out of water bool was_water = g->m.is_divable( pos() ); bool will_be_water = on_ground && can_submerge() && g->m.is_divable( p ); if( was_water && !will_be_water && g->u.sees( p ) ) { //Use more dramatic messages for swimming monsters add_msg( m_warning, _( "A %1$s %2$s from the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "leaps" ) : _( "emerges" ), g->m.tername( pos() ).c_str() ); } else if( !was_water && will_be_water && g->u.sees( p ) ) { add_msg( m_warning, _( "A %1$s %2$s into the %3$s!" ), name().c_str(), has_flag( MF_SWIMS ) || has_flag( MF_AQUATIC ) ? _( "dives" ) : _( "sinks" ), g->m.tername( p ).c_str() ); } setpos( p ); footsteps( p ); underwater = will_be_water; if( is_hallucination() ) { //Hallucinations don't do any of the stuff after this point return true; } // TODO: Make tanks stop taking damage from rubble, because it's just silly if( type->size != MS_TINY && on_ground ) { if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 10 ) ); } if( g->m.has_flag( "ROUGH", pos() ) && one_in( 6 ) ) { apply_damage( nullptr, bp_torso, rng( 1, 2 ) ); } } if( g->m.has_flag( "UNSTABLE", p ) && on_ground ) { add_effect( effect_bouldering, 1, num_bp, true ); } else if( has_effect( effect_bouldering ) ) { remove_effect( effect_bouldering ); } g->m.creature_on_trap( *this ); if( !will_be_water && ( has_flag( MF_DIGS ) || has_flag( MF_CAN_DIG ) ) ) { underwater = g->m.has_flag( "DIGGABLE", pos() ); } // Diggers turn the dirt into dirtmound if( digging() ) { int factor = 0; switch( type->size ) { case MS_TINY: factor = 100; break; case MS_SMALL: factor = 30; break; case MS_MEDIUM: factor = 6; break; case MS_LARGE: factor = 3; break; case MS_HUGE: factor = 1; break; } if( one_in( factor ) ) { g->m.ter_set( pos(), t_dirtmound ); } } // Acid trail monsters leave... a trail of acid if( has_flag( MF_ACIDTRAIL ) ) { g->m.add_field( pos(), fd_acid, 3, 0 ); } if( has_flag( MF_SLUDGETRAIL ) ) { for( const tripoint &sludge_p : g->m.points_in_radius( pos(), 1 ) ) { const int fstr = 3 - ( abs( sludge_p.x - posx() ) + abs( sludge_p.y - posy() ) ); if( fstr >= 2 ) { g->m.add_field( sludge_p, fd_sludge, fstr, 0 ); } } } return true; }
// 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( nullptr ); return; } //The monster can consume objects it stands on. Check if there are any. //If there are. Consume them. if( !is_hallucination() && has_flag( MF_ABSORBS ) && !g->m.has_flag( TFLAG_SEALED, pos() ) && g->m.has_items( pos() ) ) { if( g->u.sees( *this ) ) { add_msg( _( "The %s flows around the objects on the floor and they are quickly dissolved!" ), name().c_str() ); } static const auto volume_per_hp = units::from_milliliter( 250 ); for( auto &elem : g->m.i_at( pos() ) ) { hp += elem.volume() / volume_per_hp; // Yeah this means it can get more HP than normal. } g->m.i_clear( pos() ); } const bool pacified = has_effect( effect_pacified ); // First, use the special attack, if we can! // The attack may change `monster::special_attacks` (e.g. by transforming // this into another monster type). Therefore we can not iterate over it // directly and instead iterate over the map from the monster type // (properties of monster types should never change). for( const auto &sp_type : type->special_attacks ) { const std::string &special_name = sp_type.first; const auto local_iter = special_attacks.find( special_name ); if( local_iter == special_attacks.end() ) { continue; } mon_special_attack &local_attack_data = local_iter->second; if( !local_attack_data.enabled ) { continue; } // Cooldowns are decremented in monster::process_turn if( local_attack_data.cooldown == 0 && !pacified && !is_hallucination() ) { if( !sp_type.second->call( *this ) ) { continue; } // `special_attacks` might have changed at this point. Sadly `reset_special` // doesn't check the attack name, so we need to do it here. if( special_attacks.count( special_name ) == 0 ) { continue; } reset_special( special_name ); } } // The monster can sometimes hang in air due to last fall being blocked const bool can_fly = has_flag( MF_FLIES ); if( !can_fly && g->m.has_flag( TFLAG_NO_FLOOR, pos() ) ) { g->m.creature_on_trap( *this, false ); } if( moves < 0 ) { return; } // TODO: Move this to attack_at/move_to/etc. functions bool attacking = false; if( !move_effects( attacking ) ) { moves = 0; return; } if( has_flag( MF_IMMOBILE ) ) { moves = 0; return; } if( has_effect( effect_stunned ) ) { stumble(); moves = 0; return; } if( friendly > 0 ) { --friendly; } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); if( !wander() ) { if( goal == g->u.pos() ) { current_attitude = attitude( &( g->u ) ); } else { for( const npc &guy : g->all_npcs() ) { if( goal == guy.pos() ) { current_attitude = attitude( &guy ); } } } } if( current_attitude == MATT_IGNORE || ( current_attitude == MATT_FOLLOW && rl_dist( pos(), goal ) <= MONSTER_FOLLOW_DIST ) ) { moves -= 100; stumble(); return; } bool moved = false; tripoint destination; // If true, don't try to greedily avoid locally bad paths bool pathed = false; if( !wander() ) { while( !path.empty() && path.front() == pos() ) { path.erase( path.begin() ); } const auto &pf_settings = get_pathfinding_settings(); if( pf_settings.max_dist >= rl_dist( pos(), goal ) && ( path.empty() || rl_dist( pos(), path.front() ) >= 2 || path.back() != goal ) ) { // We need a new path path = g->m.route( pos(), goal, pf_settings, get_path_avoid() ); } // Try to respect old paths, even if we can't pathfind at the moment if( !path.empty() && path.back() == goal ) { destination = path.front(); moved = true; pathed = true; } else { // Straight line forward, probably because we can't pathfind (well enough) destination = goal; moved = true; } } if( !moved && 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. unset_dest(); tripoint tmp = scent_move(); if( tmp.x != -1 ) { destination = tmp; moved = true; } } if( wandf > 0 && !moved && friendly == 0 ) { // No LOS, no scent, so as a fall-back follow sound unset_dest(); if( wander_pos != pos() ) { destination = wander_pos; moved = true; } } if( !g->m.has_zlevels() ) { // Otherwise weird things happen destination.z = posz(); } tripoint next_step; const bool staggers = has_flag( MF_STUMBLES ); if( moved ) { // Implement both avoiding obstacles and staggering. moved = false; float switch_chance = 0.0; const bool can_bash = bash_skill() > 0; // This is a float and using trig_dist() because that Does the Right Thing(tm) // in both circular and roguelike distance modes. const float distance_to_target = trig_dist( pos(), destination ); for( const tripoint &candidate : squares_closer_to( pos(), destination ) ) { if( candidate.z != posz() ) { bool can_z_move = true; if( !g->m.valid_move( pos(), candidate, false, true ) ) { // Can't phase through floor can_z_move = false; } if( can_z_move && !can_fly && candidate.z > posz() && !g->m.has_floor_or_support( candidate ) ) { // Can't "jump" up a whole z-level can_z_move = false; } // Last chance - we can still do the z-level stair teleport bullshit that isn't removed yet // @todo: Remove z-level stair bullshit teleport after aligning all stairs if( !can_z_move && posx() / ( SEEX * 2 ) == candidate.x / ( SEEX * 2 ) && posy() / ( SEEY * 2 ) == candidate.y / ( SEEY * 2 ) ) { const tripoint &upper = candidate.z > posz() ? candidate : pos(); const tripoint &lower = candidate.z > posz() ? pos() : candidate; if( g->m.has_flag( TFLAG_GOES_DOWN, upper ) && g->m.has_flag( TFLAG_GOES_UP, lower ) ) { can_z_move = true; } } if( !can_z_move ) { continue; } } // A flag to allow non-stumbling critters to stumble when the most direct choice is bad. bool bad_choice = false; const Creature *target = g->critter_at( candidate, is_hallucination() ); if( target != nullptr ) { const Creature::Attitude att = attitude_to( *target ); if( att == A_HOSTILE ) { // When attacking an adjacent enemy, we're direct. moved = true; next_step = candidate; break; } else if( att == A_FRIENDLY && ( target->is_player() || target->is_npc() ) ) { continue; // Friendly firing the player or an NPC is illegal for gameplay reasons } else if( !has_flag( MF_ATTACKMON ) && !has_flag( MF_PUSH_MON ) ) { // Bail out if there's a non-hostile monster in the way and we're not pushy. continue; } // Friendly fire and pushing are always bad choices - they take a lot of time bad_choice = true; } // Bail out if we can't move there and we can't bash. if( !pathed && !can_move_to( candidate ) ) { if( !can_bash ) { continue; } const int estimate = g->m.bash_rating( bash_estimate(), candidate ); if( estimate <= 0 ) { continue; } if( estimate < 5 ) { bad_choice = true; } } const float progress = distance_to_target - trig_dist( candidate, destination ); // The x2 makes the first (and most direct) path twice as likely, // since the chance of switching is 1/1, 1/4, 1/6, 1/8 switch_chance += progress * 2; // Randomly pick one of the viable squares to move to weighted by distance. if( moved == false || x_in_y( progress, switch_chance ) ) { moved = true; next_step = candidate; // If we stumble, pick a random square, otherwise take the first one, // which is the most direct path. // Except if the direct path is bad, then check others // Or if the path is given by pathfinder if( !staggers && ( !bad_choice || pathed ) ) { break; } } } } // 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 const bool did_something = ( !pacified && attack_at( next_step ) ) || ( !pacified && bash_at( next_step ) ) || ( !pacified && push_to( next_step, 0, 0 ) ) || move_to( next_step, false, get_stagger_adjust( pos(), destination, next_step ) ); if( !did_something ) { moves -= 100; // If we don't do this, we'll get infinite loops. } } else { moves -= 100; stumble(); path.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, simpleminded animals are too dumb to follow the player. if( friendly == 0 && sees( g->u ) && !has_flag( MF_PET_WONT_FOLLOW ) ) { 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( monster &tmp : g->all_monsters() ) { 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( npc &who : g->all_npcs() ) { 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( monster *const mon_ptr : fac.second ) { monster &mon = *mon_ptr; 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( monster *const mon_ptr : myfaction_iter->second ) { monster &mon = *mon_ptr; 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::knock_back_from( const tripoint &p ) { if( p == pos() ) { return; // No effect } if( is_hallucination() ) { die( nullptr ); return; } tripoint to = pos();; if( p.x < posx() ) { to.x++; } if( p.x > posx() ) { to.x--; } if( p.y < posy() ) { to.y++; } if( p.y > posy() ) { to.y--; } bool u_see = g->u.sees( to ); // First, see if we hit another monster if( monster *const z = g->critter_at<monster>( to ) ) { apply_damage( z, bp_torso, z->type->size ); add_effect( effect_stunned, 1 ); if( type->size > 1 + z->type->size ) { z->knock_back_from( pos() ); // Chain reaction! z->apply_damage( this, bp_torso, type->size ); z->add_effect( effect_stunned, 1 ); } else if( type->size > z->type->size ) { z->apply_damage( this, bp_torso, type->size ); z->add_effect( effect_stunned, 1 ); } z->check_dead_state(); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s!" ), name().c_str(), z->name().c_str() ); } return; } if( npc *const p = g->critter_at<npc>( to ) ) { apply_damage( p, bp_torso, 3 ); add_effect( effect_stunned, 1 ); p->deal_damage( this, bp_torso, damage_instance( DT_BASH, type->size ) ); if( u_see ) { add_msg( _( "The %1$s bounces off %2$s!" ), name().c_str(), p->name.c_str() ); } p->check_dead_state(); return; } // If we're still in the function at this point, we're actually moving a tile! if( g->m.has_flag_ter( TFLAG_DEEP_WATER, to ) ) { if( g->m.has_flag( "LIQUID", to ) && can_drown() ) { die( nullptr ); if( u_see ) { add_msg( _( "The %s drowns!" ), name().c_str() ); } } else if( has_flag( MF_AQUATIC ) ) { // We swim but we're NOT in water die( nullptr ); if( u_see ) { add_msg( _( "The %s flops around and dies!" ), name().c_str() ); } } } if( g->m.impassable( to ) ) { // It's some kind of wall. apply_damage( nullptr, bp_torso, type->size ); add_effect( effect_stunned, 2 ); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s." ), name().c_str(), g->m.obstacle_name( to ).c_str() ); } } else { // It's no wall setpos( to ); } check_dead_state(); }
bool monster::digging() { return has_flag(MF_DIGS) || (has_flag(MF_CAN_DIG) && g->m.has_flag("DIGGABLE", posx(), posy())); }
void apply_magic(object_type *o_ptr, s32b lev, bool okay, bool good, bool great) { s32b i, power; object_kind *k_ptr = &k_info[o_ptr->k_idx]; /* Uses level */ flags_mbonus_level = lev; call_lua("objects_get_power_level", "(O,d,b,b,b)", "d", o_ptr, lev, okay, good, great, &power); /* Initialize books */ if (has_flag(o_ptr, FLAG_GET_BOOK_SPELLS)) { call_lua("setup_object_spells", "(O,d)", "", o_ptr, get_flag(o_ptr, FLAG_GET_BOOK_SPELLS)); } /* No need to touch normal artifacts */ if ((has_flag(k_ptr, FLAG_NORM_ART))) { /* Ahah! we tried to trick us !! */ if (k_ptr->artifact || (((has_flag(k_ptr, FLAG_SPECIAL_GENE))) && (!k_allow_special[o_ptr->k_idx]))) { object_prep(o_ptr, lookup_kind(get_flag(k_ptr, FLAG_NORM_ART), flag_get2(&k_ptr->flags, FLAG_NORM_ART))); if (wizard) msg_print("We've been tricked!"); } else { k_ptr->artifact = TRUE; if (cheat_peek || p_ptr->precognition) object_mention(o_ptr); } /* Reset to default */ flags_mbonus_level = -1; return; } /* Mega hack */ if (hack_apply_magic_power) { if (hack_apply_magic_power == -99) power = 0; else power = hack_apply_magic_power; } hack_apply_magic_power = 0; o_ptr->elevel = 1; o_ptr->exp = 0; /* Special make code */ invoke_on_make(&k_ptr->flags, o_ptr, power); /* Generic on make for all items */ invoke_on_make_all_pre(o_ptr, power); /* Hack -- analyze artifacts */ if (o_ptr->artifact_id) { artifact_type *a_ptr = &a_info[o_ptr->artifact_id]; /* Hack -- Mark the artifact as "created" */ a_ptr->cur_num = 1; /* Extract the other fields */ o_ptr->ac = a_ptr->ac; o_ptr->dd = a_ptr->dd; o_ptr->ds = a_ptr->ds; o_ptr->to_a = a_ptr->to_a; o_ptr->to_h = a_ptr->to_h; o_ptr->to_d = a_ptr->to_d; o_ptr->weight = a_ptr->weight; o_ptr->number = 1; /* Transfer flags */ flag_add(&o_ptr->flags, &a_ptr->flags); /* Mega-Hack -- increase the rating */ rating += 10; /* Mega-Hack -- increase the rating again */ if (a_ptr->cost > 50000L) rating += 10; /* Set the good item flag */ good_item_flag = TRUE; /* Cheat -- peek at the item */ if ((cheat_peek) || (p_ptr->precognition)) object_mention(o_ptr); /* Special make code */ invoke_on_make(&a_ptr->flags, o_ptr, power); invoke_on_make_all(o_ptr, power); /* Hack -- extract the "cursed" flag */ if (has_flag(o_ptr, FLAG_CURSED)) o_ptr->ident |= (IDENT_CURSED); /* Done */ /* Reset to default */ flags_mbonus_level = -1; return; } if (o_ptr->art_name) rating += 40; /* Analyze ego-items */ else { ego_item_type *e_ptr; s32b j; s16b e_idx; bool did_ego = FALSE; for (i = 0; i < MAX_EGO_PER_OBJ; i++) { e_idx = o_ptr->ego_id[i]; if (!e_idx) continue; did_ego = TRUE; e_ptr = &e_info[e_idx]; /* Hack -- extra powers */ for (j = 0; j < MAX_EGO_FLAG_GROUPS; j++) { /* Rarity check */ if (magik(e_ptr->rar[j])) { /* Copy all flags */ flag_add(&o_ptr->flags, &e_ptr->flags[j]); /* Special make code */ invoke_on_make(&e_ptr->flags[j], o_ptr, power); } } /* Hack -- acquire "cursed" flag */ if (has_flag(o_ptr, FLAG_CURSED)) o_ptr->ident |= (IDENT_CURSED); /* Hack -- obtain bonuses */ if (e_ptr->max_to_h > 0) o_ptr->to_h += randint(e_ptr->max_to_h); if (e_ptr->max_to_h < 0) o_ptr->to_h -= randint( -e_ptr->max_to_h); if (e_ptr->max_to_d > 0) o_ptr->to_d += randint(e_ptr->max_to_d); if (e_ptr->max_to_d < 0) o_ptr->to_d -= randint( -e_ptr->max_to_d); if (e_ptr->max_to_a > 0) o_ptr->to_a += randint(e_ptr->max_to_a); if (e_ptr->max_to_a < 0) o_ptr->to_a -= randint( -e_ptr->max_to_a); /* Hack -- apply rating bonus */ rating += e_ptr->rating; } /* Cheat -- describe the item */ if ((did_ego) && ((cheat_peek) || (p_ptr->precognition))) object_mention(o_ptr); } /* Generic on make for all items */ invoke_on_make_all(o_ptr, power); /* Examine real objects */ if (o_ptr->k_idx) { /* Hack -- acquire "cursed" flag */ if (has_flag(o_ptr, FLAG_CURSED)) o_ptr->ident |= (IDENT_CURSED); } /* Reset to default */ flags_mbonus_level = -1; }
bool monster::is_warm() { return has_flag(MF_WARM); }
/* * Let an object fall to the ground at or near a location. * * The initial location is assumed to be "in_bounds()". * * This function takes a parameter "chance". This is the percentage * chance that the item will "disappear" instead of drop. If the object * has been thrown, then this is the chance of disappearance on contact. * * Hack -- this function uses "chance" to determine if it should produce * some form of "description" of the drop event (under the player). * * We check several locations to see if we can find a location at which * the object can combine, stack, or be placed. Artifacts will try very * hard to be placed, including "teleporting" to a useful grid if needed. */ s16b drop_near(object_type *j_ptr, s32b chance, s32b y, s32b x) { s32b i, k, d, s; s32b bs, bn; s32b by, bx; s32b dy, dx; s32b ty, tx; s16b o_idx = 0; cave_type *c_ptr; char o_name[80]; bool flag = FALSE; bool plural = FALSE; /* Extract plural */ if (j_ptr->number != 1) plural = TRUE; /* Describe object */ object_desc(o_name, j_ptr, FALSE, 0); /* Handle normal "breakage" */ if (!(j_ptr->art_name || artifact_p(j_ptr)) && (rand_int(100) < chance)) { /* Message */ msg_format("The %s disappear%s.", o_name, (plural ? "" : "s")); /* Debug */ if (wizard) msg_print("(breakage)"); delete_object(j_ptr); /* Failure */ return (0); } /* Score */ bs = -1; /* Picker */ bn = 0; /* Default */ by = y; bx = x; /* Scan local grids */ for (dy = -3; dy <= 3; dy++) { /* Scan local grids */ for (dx = -3; dx <= 3; dx++) { bool comb = FALSE; /* Calculate actual distance */ d = (dy * dy) + (dx * dx); /* Ignore distant grids */ if (d > 10) continue; /* Location */ ty = y + dy; tx = x + dx; /* Skip illegal grids */ if (!in_bounds(ty, tx)) continue; /* Require line of sight */ if (!los(y, x, ty, tx)) continue; /* Obtain grid */ c_ptr = &cave[ty][tx]; /* Require floor space (or shallow terrain) -KMW- */ if (!has_flag(&f_info[c_ptr->feat], FLAG_FLOOR)) continue; /* No traps */ if (flag_used(&c_ptr->activations)) continue; /* No objects */ k = 0; /* Scan objects in that grid */ for_inventory_slot(&c_ptr->inventory, o_ptr); { /* Check for possible combination */ if (object_similar(o_ptr, j_ptr)) comb = TRUE; /* Count objects */ k++; } end_inventory_slot(); /* Add new object */ if (!comb) k++; /* Paranoia */ if (k >= inventory_limit_inven(&c_ptr->inventory)) continue; /* Calculate score */ s = 1000 - (d + k * 5); /* Skip bad values */ if (s < bs) continue; /* New best value */ if (s > bs) bn = 0; /* Apply the randomizer to equivalent values */ if ((++bn >= 2) && (rand_int(bn) != 0)) continue; /* Keep score */ bs = s; /* Track it */ by = ty; bx = tx; /* Okay */ flag = TRUE; } } /* Handle lack of space */ if (!flag && !(artifact_p(j_ptr) || j_ptr->art_name)) { /* Message */ msg_format("The %s disappear%s.", o_name, (plural ? "" : "s")); /* Debug */ if (wizard) msg_print("(no floor space)"); delete_object(j_ptr); /* Failure */ return (0); } /* Find a grid */ for (i = 0; !flag; i++) { /* Bounce around */ if (i < 1000) { ty = rand_spread(by, 1); tx = rand_spread(bx, 1); } /* Random locations */ else { ty = rand_int(cur_hgt); tx = rand_int(cur_wid); } /* Grid */ c_ptr = &cave[ty][tx]; /* Require floor space */ if (!has_flag(&f_info[c_ptr->feat], FLAG_FLOOR) || has_flag(&f_info[c_ptr->feat], FLAG_NO_WALK)) continue; /* Bounce to that location */ by = ty; bx = tx; /* Require floor space */ if (!cave_clean_bold(by, bx)) continue; /* Okay */ flag = TRUE; } j_ptr->iy = by; j_ptr->ix = bx; j_ptr->held_m_idx = 0; /* Grid */ c_ptr = &cave[by][bx]; /* Carry */ o_idx = inven_carry_inven(&c_ptr->inventory, j_ptr, FALSE); /* * j_ptr might have been merged into an existing object and then * deleted, so re-get the object. */ j_ptr = get_object(item_slot_to_item(o_idx)); /* Note the spot */ note_spot(by, bx); /* Draw the spot */ lite_spot(by, bx); /* Mega-Hack -- no message if "dropped" by player */ /* Message when an object falls under the player */ if (chance && (by == p_ptr->py) && (bx == p_ptr->px)) { msg_print("You feel something roll beneath your feet."); /* Sound */ sound(SOUND_DROP); } process_hooks(HOOK_DROPPED_NEAR, "(O,b,d,d,d,d)", j_ptr, chance, y, x, by, bx); /* XXX XXX XXX */ /* Result */ return (o_idx); }
bool monster::can_see() { return has_flag(MF_SEES) && !has_effect(ME_BLIND); }
void monster::die() { if (!dead) dead = true; if (!no_extra_death_drops) { drop_items_on_death(); } if (type->difficulty >= 30 && get_killer() != NULL && get_killer()->is_player()) { g->u.add_memorial_log( pgettext("memorial_male", "Killed a %s."), pgettext("memorial_female", "Killed a %s."), name().c_str()); } // If we're a queen, make nearby groups of our type start to die out if (has_flag(MF_QUEEN)) { // Do it for overmap above/below too for(int z = 0; z >= -1; --z) { for (int x = -MAPSIZE/2; x <= MAPSIZE/2; x++) { for (int y = -MAPSIZE/2; y <= MAPSIZE/2; y++) { std::vector<mongroup*> groups = g->cur_om->monsters_at(g->levx+x, g->levy+y, z); for (int i = 0; i < groups.size(); i++) { if (MonsterGroupManager::IsMonsterInGroup (groups[i]->type, (type->id))) groups[i]->dying = true; } } } } } // If we're a mission monster, update the mission if (mission_id != -1) { mission_type *misstype = g->find_mission_type(mission_id); if (misstype->goal == MGOAL_FIND_MONSTER) g->fail_mission(mission_id); if (misstype->goal == MGOAL_KILL_MONSTER) g->mission_step_complete(mission_id, 1); } // Also, perform our death function mdeath md; if(is_hallucination()) { //Hallucinations always just disappear md.disappear(this); return; } else { //Not a hallucination, go process the death effects. std::vector<void (mdeath::*)(monster *)> deathfunctions = type->dies; void (mdeath::*func)(monster *); for (int i = 0; i < deathfunctions.size(); i++) { func = deathfunctions.at(i); (md.*func)(this); } } // If our species fears seeing one of our own die, process that int anger_adjust = 0, morale_adjust = 0; if (type->has_anger_trigger(MTRIG_FRIEND_DIED)){ anger_adjust += 15; } if (type->has_fear_trigger(MTRIG_FRIEND_DIED)){ morale_adjust -= 15; } if (type->has_placate_trigger(MTRIG_FRIEND_DIED)){ anger_adjust -= 15; } if (anger_adjust != 0 && morale_adjust != 0) { int light = g->light_level(); for (int i = 0; i < g->num_zombies(); i++) { int t = 0; if (g->m.sees(g->zombie(i).posx(), g->zombie(i).posy(), _posx, _posy, light, t)) { g->zombie(i).morale += morale_adjust; g->zombie(i).anger += anger_adjust; } } } }
void monster::die(game *g) { // Drop goodies int total_chance = 0, total_it_chance, cur_chance, selected_location, selected_item; bool animal_done = false; std::vector<items_location_and_chance> it = g->monitems[type->id]; std::vector<itype_id> mapit; if (type->item_chance != 0 && it.size() == 0) debugmsg("Type %s has item_chance %d but no items assigned!", type->name.c_str(), type->item_chance); else { for (int i = 0; i < it.size(); i++) total_chance += it[i].chance; while (rng(0, 99) < abs(type->item_chance) && !animal_done) { cur_chance = rng(1, total_chance); selected_location = -1; while (cur_chance > 0) { selected_location++; cur_chance -= it[selected_location].chance; } total_it_chance = 0; mapit = g->mapitems[it[selected_location].loc]; for (int i = 0; i < mapit.size(); i++) total_it_chance += g->itypes[mapit[i]]->rarity; cur_chance = rng(1, total_it_chance); selected_item = -1; while (cur_chance > 0) { selected_item++; cur_chance -= g->itypes[mapit[selected_item]]->rarity; } g->m.add_item(posx, posy, g->itypes[mapit[selected_item]], 0); if (type->item_chance < 0) animal_done = true; // Only drop ONE item. } } // Done dropping items // If we're a queen, make nearby groups of our type start to die out if (has_flag(MF_QUEEN)) { std::vector<mongroup*> groups = g->cur_om.monsters_at(g->levx, g->levy); for (int i = 0; i < groups.size(); i++) { moncat_id moncat_type = groups[i]->type; bool match = false; for (int j = 0; !match && j < g->moncats[moncat_type].size(); j++) { if (g->moncats[moncat_type][j] == type->id) match = true; } if (match) groups[i]->dying = true; } // Do it for overmap above/below too overmap tmp; if (g->cur_om.posz == 0) tmp = overmap(g, g->cur_om.posx, g->cur_om.posy, -1); else tmp = overmap(g, g->cur_om.posx, g->cur_om.posy, 0); groups = tmp.monsters_at(g->levx, g->levy); for (int i = 0; i < groups.size(); i++) { moncat_id moncat_type = groups[i]->type; bool match = false; for (int j = 0; !match && j < g->moncats[moncat_type].size(); j++) { if (g->moncats[moncat_type][j] == type->id) match = true; } if (match) groups[i]->dying = true; } } // If we're a mission monster, update the mission if (mission_id != -1) { mission_type *misstype = g->find_mission_type(mission_id); if (misstype->goal == MGOAL_FIND_MONSTER) g->fail_mission(mission_id); if (misstype->goal == MGOAL_KILL_MONSTER) g->mission_step_complete(mission_id, 1); } // Also, perform our death function mdeath md; (md.*type->dies)(g, this); }
bool monster::can_see() { return has_flag(MF_SEES) && !has_effect("blind"); }
void map::shoot(game *g, int x, int y, int &dam, bool hit_items, unsigned flags) { if (flags & mfb(WF_AMMO_FLAME) && has_flag(flammable, x, y)) add_field(g, x, y, fd_fire, 2); switch (ter(x, y)) { case t_door_c: case t_door_locked: dam -= rng(15, 30); if (dam > 0) ter(x, y) = t_door_b; break; case t_door_b: if (hit_items || one_in(8)) { // 1 in 8 chance of hitting the door dam -= rng(10, 30); if (dam > 0) ter(x, y) = t_door_frame; } else dam -= rng(0, 1); break; case t_door_boarded: dam -= rng(15, 35); if (dam > 0) ter(x, y) = t_door_b; break; case t_window: dam -= rng(0, 5); if (dam > 0) ter(x, y) = t_window_frame; break; case t_window_boarded: dam -= rng(10, 30); if (dam > 0) ter(x, y) = t_window_frame; break; case t_wall_glass_h: case t_wall_glass_v: dam -= rng(0, 8); if (dam > 0) ter(x, y) = t_floor; break; case t_paper: dam -= rng(4, 16); if (dam > 0) ter(x, y) = t_dirt; if (flags & mfb(WF_AMMO_INCENDIARY)) add_field(g, x, y, fd_fire, 1); break; case t_gas_pump: if (hit_items || one_in(3)) { if (dam > 15) { if (flags & mfb(WF_AMMO_INCENDIARY) || flags & mfb(WF_AMMO_FLAME)) g->explosion(x, y, 40, 0, true); else { for (int i = x - 2; i <= x + 2; i++) { for (int j = y - 2; j <= y + 2; j++) { if (move_cost(i, j) > 0 && one_in(3)) add_item(i, j, g->itypes[itm_gasoline], 0); } } } ter(x, y) = t_gas_pump_smashed; } dam -= 60; } break; case t_vat: if (dam >= 10) { g->sound(x, y, 15, "ke-rash!"); ter(x, y) = t_floor; } else dam = 0; break; default: if (move_cost(x, y) == 0 && !trans(x, y)) dam = 0; // TODO: Bullets can go through some walls? else dam -= (rng(0, 1) * rng(0, 1) * rng(0, 1)); } // Now, destroy items on that tile. if ((move_cost(x, y) == 2 && !hit_items) || !inbounds(x, y)) return; // Items on floor-type spaces won't be shot up. bool destroyed; for (int i = 0; i < i_at(x, y).size(); i++) { destroyed = false; switch (i_at(x, y)[i].type->m1) { case GLASS: case PAPER: if (dam > rng(2, 8) && one_in(i_at(x, y)[i].volume())) destroyed = true; break; case PLASTIC: if (dam > rng(2, 10) && one_in(i_at(x, y)[i].volume() * 3)) destroyed = true; break; case VEGGY: case FLESH: if (dam > rng(10, 40)) destroyed = true; break; case COTTON: case WOOL: i_at(x, y)[i].damage++; if (i_at(x, y)[i].damage >= 5) destroyed = true; break; } if (destroyed) { for (int j = 0; j < i_at(x, y)[i].contents.size(); j++) i_at(x, y).push_back(i_at(x, y)[i].contents[j]); i_rem(x, y, i); i--; } } }
bool monster::can_hear() { return has_flag(MF_HEARS) && !has_effect("deaf"); }
std::string item::info(bool showtext) { std::stringstream dump; dump << " Volume: " << volume() << " Weight: " << weight() << "\n" << " Bash: " << int(type->melee_dam) << (has_flag(IF_SPEAR) ? " Pierce: " : " Cut: ") << int(type->melee_cut) << " To-hit bonus: " << (type->m_to_hit > 0 ? "+" : "" ) << int(type->m_to_hit) << "\n" << " Moves per attack: " << attack_time() << "\n"; if (is_food()) { it_comest* food = dynamic_cast<it_comest*>(type); dump << " Nutrition: " << int(food->nutr) << "\n Quench: " << int(food->quench) << "\n Enjoyability: " << int(food->fun); } else if (is_food_container()) { it_comest* food = dynamic_cast<it_comest*>(contents[0].type); dump << " Nutrition: " << int(food->nutr) << "\n Quench: " << int(food->quench) << "\n Enjoyability: " << int(food->fun); } else if (is_ammo()) { it_ammo* ammo = dynamic_cast<it_ammo*>(type); dump << " Type: " << ammo_name(ammo->type) << "\n Damage: " << int(ammo->damage) << "\n Armor-pierce: " << int(ammo->pierce) << "\n Range: " << int(ammo->range) << "\n Accuracy: " << int(100 - ammo->accuracy) << "\n Recoil: " << int(ammo->recoil); } else if (is_gun()) { it_gun* gun = dynamic_cast<it_gun*>(type); int ammo_dam = 0, ammo_recoil = 0; bool has_ammo = (curammo != NULL && charges > 0); if (has_ammo) { ammo_dam = curammo->damage; ammo_recoil = curammo->recoil; } dump << " Skill used: " << skill_name(gun->skill_used) << "\n Ammunition: " << clip_size() << " rounds of " << ammo_name(ammo_type()); dump << "\n Damage: "; if (has_ammo) dump << ammo_dam; dump << (gun_damage(false) >= 0 ? "+" : "" ) << gun_damage(false); if (has_ammo) dump << " = " << gun_damage(); dump << "\n Accuracy: " << int(100 - accuracy()); dump << "\n Recoil: "; if (has_ammo) dump << ammo_recoil; dump << (recoil(false) >= 0 ? "+" : "" ) << recoil(false); if (has_ammo) dump << " = " << recoil(); dump << "\n Reload time: " << int(gun->reload_time); if (has_flag(IF_RELOAD_ONE)) dump << " per round"; if (burst_size() == 0) dump << "\n Semi-automatic."; else dump << "\n Burst size: " << burst_size(); if (contents.size() > 0) dump << "\n"; for (int i = 0; i < contents.size(); i++) dump << "\n+" << contents[i].tname(); } else if (is_gunmod()) { it_gunmod* mod = dynamic_cast<it_gunmod*>(type); if (mod->accuracy != 0) dump << " Accuracy: " << (mod->accuracy > 0 ? "+" : "") << int(mod->accuracy); if (mod->damage != 0) dump << "\n Damage: " << (mod->damage > 0 ? "+" : "") << int(mod->damage); if (mod->clip != 0) dump << "\n Clip: " << (mod->clip > 0 ? "+" : "") << int(mod->damage) << "%"; if (mod->recoil != 0) dump << "\n Recoil: " << int(mod->recoil); if (mod->burst != 0) dump << "\n Burst: " << (mod->clip > 0 ? "+" : "") << int(mod->clip); if (mod->newtype != AT_NULL) dump << "\n " << ammo_name(mod->newtype); dump << "\n Used on: "; if (mod->used_on_pistol) dump << "Pistols. "; if (mod->used_on_shotgun) dump << "Shotguns. "; if (mod->used_on_smg) dump << "SMGs. "; if (mod->used_on_rifle) dump << "Rifles."; } else if (is_armor()) { it_armor* armor = dynamic_cast<it_armor*>(type); dump << " Covers: "; if (armor->covers & mfb(bp_head)) dump << "The head. "; if (armor->covers & mfb(bp_eyes)) dump << "The eyes. "; if (armor->covers & mfb(bp_mouth)) dump << "The mouth. "; if (armor->covers & mfb(bp_torso)) dump << "The torso. "; if (armor->covers & mfb(bp_hands)) dump << "The hands. "; if (armor->covers & mfb(bp_legs)) dump << "The legs. "; if (armor->covers & mfb(bp_feet)) dump << "The feet. "; dump << "\n Encumberment: " << int(armor->encumber) << "\n Bashing protection: " << int(armor->dmg_resist) << "\n Cut protection: " << int(armor->cut_resist) << "\n Environmental protection: " << int(armor->env_resist) << "\n Warmth: " << int(armor->warmth) << "\n Storage: " << int(armor->storage); } else if (is_book()) { it_book* book = dynamic_cast<it_book*>(type); if (book->type == sk_null) dump << " Just for fun.\n"; else { dump << " Can bring your " << skill_name(book->type) << " skill to " << int(book->level) << std::endl; if (book->req == 0) dump << " It can be understood by beginners.\n"; else dump << " Requires " << skill_name(book->type) << " level " << int(book->req) << " to understand.\n"; } dump << " Requires intelligence of " << int(book->intel) << std::endl; if (book->fun != 0) dump << " Reading this book affects your morale by " << (book->fun > 0 ? "+" : "") << int(book->fun) << std::endl; dump << " This book takes " << int(book->time) << " minutes to read."; } else if (is_tool()) { it_tool* tool = dynamic_cast<it_tool*>(type); dump << " Maximum " << tool->max_charges << " charges"; if (tool->ammo == AT_NULL) dump << "."; else dump << " of " << ammo_name(tool->ammo) << "."; } if (showtext) { dump << "\n\n" << type->description << "\n"; if (contents.size() > 0) { if (is_gun()) { for (int i = 0; i < contents.size(); i++) dump << "\n " << contents[i].type->description; } else dump << "\n " << contents[0].type->description; dump << "\n"; } } return dump.str(); }
bool monster::can_submerge() { return (has_flag(MF_NO_BREATHE) || has_flag(MF_SWIMS) || has_flag(MF_AQUATIC)) && !has_flag(MF_ELECTRONIC); }
// 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 (friendly != 0 || (g->u.has_trait(PF_ANIMALEMPATH) && has_flag(MF_ANIMAL))) { if (friendly > 0) friendly--; friendly_move(g); return; } /* if (!is_fleeing(g->u) && has_flag(MF_ANIMAL)) { stumble(g, false); moves = 0; return; } */ moves -= 100; bool moved=false; point next; int mondex = (plans.size() > 0 ? g->mon_at(plans[0].x, plans[0].y) : -1); if (plans.size() > 0 && (mondex == -1 || g->z[mondex].friendly != 0 || has_flag(MF_ATTACKMON)) && (can_move_to(g->m, 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 && type->melee_dice > 0 && (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->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; } else if (can_move_to(g->m, next.x, next.y) && g->is_empty(next.x, next.y)) move_to(g, next.x, next.y); 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); }
bool monster::can_drown() { return !has_flag(MF_SWIMS) && !has_flag(MF_AQUATIC) && !has_flag(MF_NO_BREATHE) && !has_flag(MF_FLIES); }
void monster::hit_player(game *g, player &p) { if (type->melee_dice == 0) // We don't attack, so just return return; 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 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(p, bphit); 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) g->add_msg("The %s hits %s %s.", name().c_str(), your.c_str(), body_part_name(bphit, side).c_str()); 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); } p.hit(g, bphit, side, dam, type->melee_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 (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); g->active_npc.erase(g->active_npc.begin() + index); plans.clear(); } } }
std::vector<tripoint> map::route( const tripoint &f, const tripoint &t, const pathfinding_settings &settings, const std::set<tripoint> &pre_closed ) const { /* TODO: If the origin or destination is out of bound, figure out the closest * in-bounds point and go to that, then to the real origin/destination. */ std::vector<tripoint> ret; if( f == t || !inbounds( f ) ) { return ret; } if( !inbounds( t ) ) { tripoint clipped = t; clip_to_bounds( clipped ); return route( f, clipped, settings, pre_closed ); } // First, check for a simple straight line on flat ground // Except when the line contains a pre-closed tile - we need to do regular pathing then static const auto non_normal = PF_SLOW | PF_WALL | PF_VEHICLE | PF_TRAP; if( f.z == t.z ) { const auto line_path = line_to( f, t ); const auto &pf_cache = get_pathfinding_cache_ref( f.z ); // Check all points for any special case (including just hard terrain) if( std::all_of( line_path.begin(), line_path.end(), [&pf_cache]( const tripoint & p ) { return !( pf_cache.special[p.x][p.y] & non_normal ); } ) ) { const std::set<tripoint> sorted_line( line_path.begin(), line_path.end() ); if( is_disjoint( sorted_line, pre_closed ) ) { return line_path; } } } // If expected path length is greater than max distance, allow only line path, like above if( rl_dist( f, t ) > settings.max_dist ) { return ret; } int max_length = settings.max_length; int bash = settings.bash_strength; bool doors = settings.allow_open_doors; bool trapavoid = settings.avoid_traps; const int pad = 16; // Should be much bigger - low value makes pathfinders dumb! int minx = std::min( f.x, t.x ) - pad; int miny = std::min( f.y, t.y ) - pad; int minz = std::min( f.z, t.z ); // TODO: Make this way bigger int maxx = std::max( f.x, t.x ) + pad; int maxy = std::max( f.y, t.y ) + pad; int maxz = std::max( f.z, t.z ); // Same TODO as above clip_to_bounds( minx, miny, minz ); clip_to_bounds( maxx, maxy, maxz ); pathfinder pf( minx, miny, maxx, maxy ); // Make NPCs not want to path through player // But don't make player pathing stop working for( const auto &p : pre_closed ) { if( p.x >= minx && p.x < maxx && p.y >= miny && p.y < maxy ) { pf.close_point( p ); } } // Start and end must not be closed pf.unclose_point( f ); pf.unclose_point( t ); pf.add_point( 0, 0, f, f ); bool done = false; do { auto cur = pf.get_next(); const int parent_index = flat_index( cur.x, cur.y ); auto &layer = pf.get_layer( cur.z ); auto &cur_state = layer.state[parent_index]; if( cur_state == ASL_CLOSED ) { continue; } if( layer.gscore[parent_index] > max_length ) { // Shortest path would be too long, return empty vector return std::vector<tripoint>(); } if( cur == t ) { done = true; break; } cur_state = ASL_CLOSED; const auto &pf_cache = get_pathfinding_cache_ref( cur.z ); const auto cur_special = pf_cache.special[cur.x][cur.y]; // 7 3 5 // 1 . 2 // 6 4 8 constexpr std::array<int, 8> x_offset{{ -1, 1, 0, 0, 1, -1, -1, 1 }}; constexpr std::array<int, 8> y_offset{{ 0, 0, -1, 1, -1, 1, -1, 1 }}; for( size_t i = 0; i < 8; i++ ) { const tripoint p( cur.x + x_offset[i], cur.y + y_offset[i], cur.z ); const int index = flat_index( p.x, p.y ); // @todo Remove this and instead have sentinels at the edges if( p.x < minx || p.x >= maxx || p.y < miny || p.y >= maxy ) { continue; } if( layer.state[index] == ASL_CLOSED ) { continue; } // Penalize for diagonals or the path will look "unnatural" int newg = layer.gscore[parent_index] + ( ( cur.x != p.x && cur.y != p.y ) ? 1 : 0 ); const auto p_special = pf_cache.special[p.x][p.y]; // @todo De-uglify, de-huge-n if( !( p_special & non_normal ) ) { // Boring flat dirt - the most common case above the ground newg += 2; } else { int part = -1; const maptile &tile = maptile_at_internal( p ); const auto &terrain = tile.get_ter_t(); const auto &furniture = tile.get_furn_t(); const vehicle *veh = veh_at_internal( p, part ); const int cost = move_cost_internal( furniture, terrain, veh, part ); // Don't calculate bash rating unless we intend to actually use it const int rating = ( bash == 0 || cost != 0 ) ? -1 : bash_rating_internal( bash, furniture, terrain, false, veh, part ); if( cost == 0 && rating <= 0 && ( !doors || !terrain.open ) && veh == nullptr ) { layer.state[index] = ASL_CLOSED; // Close it so that next time we won't try to calc costs continue; } newg += cost; if( cost == 0 ) { // Handle all kinds of doors // Only try to open INSIDE doors from the inside if( doors && terrain.open && ( !terrain.has_flag( "OPENCLOSE_INSIDE" ) || !is_outside( cur ) ) ) { // To open and then move onto the tile newg += 4; } else if( veh != nullptr ) { part = veh->obstacle_at_part( part ); int dummy = -1; if( doors && veh->part_flag( part, VPFLAG_OPENABLE ) && ( !veh->part_flag( part, "OPENCLOSE_INSIDE" ) || veh_at_internal( cur, dummy ) == veh ) ) { // Handle car doors, but don't try to path through curtains newg += 10; // One turn to open, 4 to move there } else if( part >= 0 && bash > 0 ) { // Car obstacle that isn't a door // @todo Account for armor int hp = veh->parts[part].hp(); if( hp / 20 > bash ) { // Threshold damage thing means we just can't bash this down layer.state[index] = ASL_CLOSED; continue; } else if( hp / 10 > bash ) { // Threshold damage thing means we will fail to deal damage pretty often hp *= 2; } newg += 2 * hp / bash + 8 + 4; } else if( part >= 0 ) { if( !doors || !veh->part_flag( part, VPFLAG_OPENABLE ) ) { // Won't be openable, don't try from other sides layer.state[index] = ASL_CLOSED; } continue; } } else if( rating > 1 ) { // Expected number of turns to bash it down, 1 turn to move there // and 5 turns of penalty not to trash everything just because we can newg += ( 20 / rating ) + 2 + 10; } else if( rating == 1 ) { // Desperate measures, avoid whenever possible newg += 500; } else { // Unbashable and unopenable from here if( !doors || !terrain.open ) { // Or anywhere else for that matter layer.state[index] = ASL_CLOSED; } continue; } } if( trapavoid && p_special & PF_TRAP ) { const auto &ter_trp = terrain.trap.obj(); const auto &trp = ter_trp.is_benign() ? tile.get_trap_t() : ter_trp; if( !trp.is_benign() ) { // For now make them detect all traps if( has_zlevels() && terrain.has_flag( TFLAG_NO_FLOOR ) ) { // Special case - ledge in z-levels // Warning: really expensive, needs a cache if( valid_move( p, tripoint( p.x, p.y, p.z - 1 ), false, true ) ) { tripoint below( p.x, p.y, p.z - 1 ); if( !has_flag( TFLAG_NO_FLOOR, below ) ) { // Otherwise this would have been a huge fall auto &layer = pf.get_layer( p.z - 1 ); // From cur, not p, because we won't be walking on air pf.add_point( layer.gscore[parent_index] + 10, layer.score[parent_index] + 10 + 2 * rl_dist( below, t ), cur, below ); } // Close p, because we won't be walking on it layer.state[index] = ASL_CLOSED; continue; } } else if( trapavoid ) { // Otherwise it's walkable newg += 500; } } } } // If not visited, add as open // If visited, add it only if we can do so with better score if( layer.state[index] == ASL_NONE || newg < layer.gscore[index] ) { pf.add_point( newg, newg + 2 * rl_dist( p, t ), cur, p ); } } if( !has_zlevels() || !( cur_special & PF_UPDOWN ) || !settings.allow_climb_stairs ) { // The part below is only for z-level pathing continue; } const maptile &parent_tile = maptile_at_internal( cur ); const auto &parent_terrain = parent_tile.get_ter_t(); if( settings.allow_climb_stairs && cur.z > minz && parent_terrain.has_flag( TFLAG_GOES_DOWN ) ) { tripoint dest( cur.x, cur.y, cur.z - 1 ); dest = vertical_move_destination<TFLAG_GOES_UP>( *this, dest ); if( inbounds( dest ) ) { auto &layer = pf.get_layer( dest.z ); pf.add_point( layer.gscore[parent_index] + 2, layer.score[parent_index] + 2 * rl_dist( dest, t ), cur, dest ); } } if( settings.allow_climb_stairs && cur.z < maxz && parent_terrain.has_flag( TFLAG_GOES_UP ) ) { tripoint dest( cur.x, cur.y, cur.z + 1 ); dest = vertical_move_destination<TFLAG_GOES_DOWN>( *this, dest ); if( inbounds( dest ) ) { auto &layer = pf.get_layer( dest.z ); pf.add_point( layer.gscore[parent_index] + 2, layer.score[parent_index] + 2 * rl_dist( dest, t ), cur, dest ); } } if( cur.z < maxz && parent_terrain.has_flag( TFLAG_RAMP ) && valid_move( cur, tripoint( cur.x, cur.y, cur.z + 1 ), false, true ) ) { auto &layer = pf.get_layer( cur.z + 1 ); for( size_t it = 0; it < 8; it++ ) { const tripoint above( cur.x + x_offset[it], cur.y + y_offset[it], cur.z + 1 ); pf.add_point( layer.gscore[parent_index] + 4, layer.score[parent_index] + 4 + 2 * rl_dist( above, t ), cur, above ); } } } while( !done && !pf.empty() ); if( done ) { ret.reserve( rl_dist( f, t ) * 2 ); tripoint cur = t; // Just to limit max distance, in case something weird happens for( int fdist = max_length; fdist != 0; fdist-- ) { const int cur_index = flat_index( cur.x, cur.y ); const auto &layer = pf.get_layer( cur.z ); const tripoint &par = layer.parent[cur_index]; if( cur == f ) { break; } ret.push_back( cur ); // Jumps are acceptable on 1 z-level changes // This is because stairs teleport the player too if( rl_dist( cur, par ) > 1 && abs( cur.z - par.z ) != 1 ) { debugmsg( "Jump in our route! %d:%d:%d->%d:%d:%d", cur.x, cur.y, cur.z, par.x, par.y, par.z ); return ret; } cur = par; } std::reverse( ret.begin(), ret.end() ); } return ret; }