void activity_handlers::pulp_do_turn( player_activity *act, player *p ) { const tripoint &pos = act->placement; static const int full_pulp_threshold = 4; const int move_cost = int(p->weapon.is_null() ? 80 : p->weapon.attack_time() * 0.8); // numbers logic: a str 8 character with a butcher knife (4 bash, 18 cut) // should have at least a 50% chance of damaging an intact zombie corpse (75 volume). // a str 8 character with a baseball bat (28 bash, 0 cut) should have around a 25% chance. int cut_power = p->weapon.type->melee_cut; // stabbing weapons are a lot less effective at pulping if( p->weapon.has_flag("STAB") || p->weapon.has_flag("SPEAR") ) { cut_power /= 2; } ///\xrefitem Stat_Effects_Strength "" "" Strength increases pulping power, with diminishing returns double pulp_power = sqrt((double)(p->str_cur + p->weapon.type->melee_dam)) * std::min(1.0, sqrt((double)(cut_power + 1))); ///\xrefitem Stat_Effects_Strength "" "" Strength caps pulping power pulp_power = std::min(pulp_power, (double)p->str_cur); pulp_power *= 20; // constant multiplier to get the chance right int moves = 0; int &num_corpses = act->index; // use this to collect how many corpse are pulped auto corpse_pile = g->m.i_at(pos); for( auto corpse = corpse_pile.begin(); corpse != corpse_pile.end(); ++corpse ) { if( !(corpse->is_corpse() && corpse->damage < full_pulp_threshold) ) { continue; // no corpse or already pulped } int damage = pulp_power / corpse->volume(); //Determine corpse's blood type. field_id type_blood = corpse->get_mtype()->bloodType(); do { moves += move_cost; const int mod_sta = ( (p->weapon.weight() / 100 ) + 20) * -1; p->mod_stat("stamina", mod_sta); // Increase damage as we keep smashing, // to insure that we eventually smash the target. if( x_in_y(pulp_power, corpse->volume()) ) { corpse->damage++; p->handle_melee_wear(); } // Splatter some blood around tripoint tmp = pos; if( type_blood != fd_null ) { for( tmp.x = pos.x - 1; tmp.x <= pos.x + 1; tmp.x++ ) { for( tmp.y = pos.y - 1; tmp.y <= pos.y + 1; tmp.y++ ) { if( !one_in(damage + 1) && type_blood != fd_null ) { g->m.add_field( tmp, type_blood, 1, 0 ); } } } } if( corpse->damage >= full_pulp_threshold ) { corpse->damage = full_pulp_threshold; corpse->active = false; num_corpses++; } if( moves >= p->moves ) { // enough for this turn; p->moves -= moves; return; } } while( corpse->damage < full_pulp_threshold ); } // If we reach this, all corpses have been pulped, finish the activity act->moves_left = 0; if( num_corpses == 0 ) { add_msg(m_bad, _("The corpse moved before you could finish smashing it!")); return; } // TODO: Factor in how long it took to do the smashing. add_msg(ngettext("The corpse is thoroughly pulped.", "The corpses are thoroughly pulped.", num_corpses)); }
void activity_handlers::butcher_finish( player_activity *act, player *p ) { // Corpses can disappear (rezzing!), so check for that auto items_here = g->m.i_at( p->pos() ); if( static_cast<int>( items_here.size() ) <= act->index || !( items_here[act->index].is_corpse() ) ) { add_msg(m_info, _("There's no corpse to butcher!")); return; } item &corpse_item = items_here[act->index]; const mtype *corpse = corpse_item.get_mtype(); std::vector<item> contents = corpse_item.contents; const int age = corpse_item.bday; g->m.i_rem( p->pos(), act->index ); const int factor = p->butcher_factor(); int pieces = 0; int skins = 0; int bones = 0; int fats = 0; int sinews = 0; int feathers = 0; int wool = 0; bool stomach = false; switch (corpse->size) { case MS_TINY: pieces = 1; skins = 1; bones = 1; fats = 1; sinews = 1; feathers = 2; wool = 1; break; case MS_SMALL: pieces = 2; skins = 2; bones = 4; fats = 2; sinews = 4; feathers = 6; wool = 2; break; case MS_MEDIUM: pieces = 4; skins = 4; bones = 9; fats = 4; sinews = 9; feathers = 11; wool = 4; break; case MS_LARGE: pieces = 8; skins = 8; bones = 14; fats = 8; sinews = 14; feathers = 17; wool = 8; break; case MS_HUGE: pieces = 16; skins = 16; bones = 21; fats = 16; sinews = 21; feathers = 24; wool = 16; break; } const int skill_level = p->skillLevel( skill_survival ); auto roll_butchery = [&] () { double skill_shift = 0.0; ///\xrefitem Skill_Effects_Survival "" "" Survival above 3 randomly increases Butcher rolls, below 3 decreases skill_shift += rng_float( 0, skill_level - 3 ); ///\xrefitem Stat_Effects_Dexterity "" "" Dexterity above 8 randomly increases Butcher rolls, slightly, below 8 decreases skill_shift += rng_float( 0, p->dex_cur - 8 ) / 4.0; ///\xrefitem Stat_Effects_Strength "" "" Strength below 4 randomly decreases Butcher rolls, slightly if( p->str_cur < 4 ) { skill_shift -= rng_float( 0, 5 * ( 4 - p->str_cur ) ) / 4.0; } if( factor < 0 ) { skill_shift -= rng_float( 0, -factor / 5.0 ); } return static_cast<int>( round( skill_shift ) ); }; int practice = std::max( 0, 4 + pieces + roll_butchery()); p->practice( skill_survival, practice ); // Lose some meat, skins, etc if the rolls are low pieces += std::min( 0, roll_butchery() ); skins += std::min( 0, roll_butchery() - 4 ); bones += std::min( 0, roll_butchery() - 2 ); fats += std::min( 0, roll_butchery() - 4 ); sinews += std::min( 0, roll_butchery() - 8 ); feathers += std::min( 0, roll_butchery() - 1 ); wool += std::min( 0, roll_butchery() ); stomach = roll_butchery() >= 0; if( bones > 0 ) { if( corpse->has_material("veggy") ) { g->m.spawn_item(p->pos(), "plant_sac", bones, 0, age); add_msg(m_good, _("You harvest some fluid bladders!")); } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "bone_tainted", bones / 2, 0, age); add_msg(m_good, _("You harvest some salvageable bones!")); } else if( corpse->has_flag(MF_BONES) && corpse->has_flag(MF_HUMAN) ) { g->m.spawn_item(p->pos(), "bone_human", bones, 0, age); add_msg(m_good, _("You harvest some salvageable bones!")); } else if( corpse->has_flag(MF_BONES) ) { g->m.spawn_item(p->pos(), "bone", bones, 0, age); add_msg(m_good, _("You harvest some usable bones!")); } } if( sinews > 0 ) { if( corpse->has_flag(MF_BONES) && !corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "sinew", sinews, 0, age); add_msg(m_good, _("You harvest some usable sinews!")); } else if( corpse->has_material("veggy") ) { g->m.spawn_item(p->pos(), "plant_fibre", sinews, 0, age); add_msg(m_good, _("You harvest some plant fibers!")); } } if( stomach ) { const itype_id meat = corpse->get_meat_itype(); if( meat == "meat" ) { if( corpse->size == MS_SMALL || corpse->size == MS_MEDIUM ) { g->m.spawn_item(p->pos(), "stomach", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } else if( corpse->size == MS_LARGE || corpse->size == MS_HUGE ) { g->m.spawn_item(p->pos(), "stomach_large", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } } else if( meat == "human_flesh" ) { if( corpse->size == MS_SMALL || corpse->size == MS_MEDIUM ) { g->m.spawn_item(p->pos(), "hstomach", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } else if( corpse->size == MS_LARGE || corpse->size == MS_HUGE ) { g->m.spawn_item(p->pos(), "hstomach_large", 1, 0, age); add_msg(m_good, _("You harvest the stomach!")); } } } if( (corpse->has_flag(MF_FUR) || corpse->has_flag(MF_LEATHER) || corpse->has_flag(MF_CHITIN)) && skins > 0 ) { add_msg(m_good, _("You manage to skin the %s!"), corpse->nname().c_str()); int fur = 0; int leather = 0; int human_leather = 0; int chitin = 0; while (skins > 0 ) { if( corpse->has_flag(MF_CHITIN) ) { chitin = rng(0, skins); skins -= chitin; skins = std::max(skins, 0); } if( corpse->has_flag(MF_FUR) ) { fur = rng(0, skins); skins -= fur; skins = std::max(skins, 0); } if( corpse->has_flag(MF_LEATHER) ) { if( corpse->has_flag(MF_HUMAN) ) { human_leather = rng(0, skins); skins -= human_leather; } else { leather = rng(0, skins); skins -= leather; } skins = std::max(skins, 0); } } if( chitin > 0 ) { g->m.spawn_item(p->pos(), "chitin_piece", chitin, 0, age); } if( fur > 0 ) { g->m.spawn_item(p->pos(), "raw_fur", fur, 0, age); } if( leather > 0 ) { g->m.spawn_item(p->pos(), "raw_leather", leather, 0, age); } if( human_leather ) { g->m.spawn_item(p->pos(), "raw_hleather", leather, 0, age); } } if( feathers > 0 ) { if( corpse->has_flag(MF_FEATHER) ) { g->m.spawn_item(p->pos(), "feather", feathers, 0, age); add_msg(m_good, _("You harvest some feathers!")); } } if( wool > 0 ) { if( corpse->has_flag(MF_WOOL) ) { g->m.spawn_item(p->pos(), "wool_staple", wool, 0, age); add_msg(m_good, _("You harvest some wool staples!")); } } if( fats > 0 ) { if( corpse->has_flag(MF_FAT) && corpse->has_flag(MF_POISON) ) { g->m.spawn_item(p->pos(), "fat_tainted", fats, 0, age); add_msg(m_good, _("You harvest some gooey fat!")); } else if( corpse->has_flag(MF_FAT) ) { g->m.spawn_item(p->pos(), "fat", fats, 0, age); add_msg(m_good, _("You harvest some fat!")); } } //Add a chance of CBM recovery. For shocker and cyborg corpses. //As long as the factor is above -4 (the sinew cutoff), you will be able to extract cbms if( corpse->has_flag(MF_CBM_CIV) ) { butcher_cbm_item( "bio_power_storage", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_common", p->pos(), age, roll_butchery() ); } // Zombie scientist bionics if( corpse->has_flag(MF_CBM_SCI) ) { butcher_cbm_item( "bio_power_storage", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_sci", p->pos(), age, roll_butchery() ); } // Zombie technician bionics if( corpse->has_flag(MF_CBM_TECH) ) { butcher_cbm_item( "bio_power_storage", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_tech", p->pos(), age, roll_butchery() ); } // Substation mini-boss bionics if( corpse->has_flag(MF_CBM_SUBS) ) { butcher_cbm_item( "bio_power_storage", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_subs", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_subs", p->pos(), age, roll_butchery() ); } // Payoff for butchering the zombie bio-op if( corpse->has_flag(MF_CBM_OP) ) { butcher_cbm_item( "bio_power_storage_mkII", p->pos(), age, roll_butchery() ); butcher_cbm_group( "bionics_op", p->pos(), age, roll_butchery() ); } //Add a chance of CBM power storage recovery. if( corpse->has_flag(MF_CBM_POWER) ) { butcher_cbm_item( "bio_power_storage", p->pos(), age, roll_butchery() ); } // Recover hidden items for( auto &content : contents ) { if( ( roll_butchery() + 10 ) * 5 > rng( 0, 100 ) ) { //~ %1$s - item name, %2$s - monster name add_msg( m_good, _( "You discover a %1$s in the %2$s!" ), content.tname().c_str(), corpse->nname().c_str() ); g->m.add_item_or_charges( p->pos(), content ); } else if( content.is_bionic() ) { g->m.spawn_item(p->pos(), "burnt_out_bionic", 1, 0, age); } } if( pieces <= 0 ) { add_msg(m_bad, _("Your clumsy butchering destroys the meat!")); } else { add_msg(m_good, _("You butcher the corpse.")); const itype_id meat = corpse->get_meat_itype(); if( meat == "null" ) { return; } item tmpitem(meat, age); tmpitem.set_mtype( corpse ); while ( pieces > 0 ) { pieces--; g->m.add_item_or_charges(p->pos(), tmpitem); } } }
void activity_handlers::make_zlave_finish( player_activity *act, player *p ) { static const int full_pulp_threshold = 4; auto items = g->m.i_at(p->pos()); std::string corpse_name = act->str_values[0]; item *body = NULL; for( auto it = items.begin(); it != items.end(); ++it ) { if( it->display_name() == corpse_name ) { body = &*it; } } if( body == NULL ) { add_msg(m_info, _("There's no corpse to make into a zombie slave!")); return; } int success = act->values[0]; if( success > 0 ) { p->practice( skill_firstaid, rng(2, 5) ); p->practice( skill_survival, rng(2, 5) ); p->add_msg_if_player(m_good, _("You slice muscles and tendons, and remove body parts until you're confident the zombie won't be able to attack you when it reainmates.")); body->set_var( "zlave", "zlave" ); //take into account the chance that the body yet can regenerate not as we need. if( one_in(10) ) { body->set_var( "zlave", "mutilated" ); } } else { if( success > -20 ) { p->practice( skill_firstaid, rng(3, 6) ); p->practice( skill_survival, rng(3, 6) ); p->add_msg_if_player(m_warning, _("You hack into the corpse and chop off some body parts. You think the zombie won't be able to attack when it reanimates.")); success += rng(1, 20); if( success > 0 && !one_in(5) ) { body->set_var( "zlave", "zlave" ); } else { body->set_var( "zlave", "mutilated" ); } } else { p->practice( skill_firstaid, rng(1, 8) ); p->practice( skill_survival, rng(1, 8) ); int pulp = rng(1, full_pulp_threshold); body->damage += pulp; if( body->damage >= full_pulp_threshold ) { body->damage = full_pulp_threshold; body->active = false; p->add_msg_if_player(m_warning, _("You cut up the corpse too much, it is thoroughly pulped.")); } else { p->add_msg_if_player(m_warning, _("You cut into the corpse trying to make it unable to attack, but you don't think you have it right.")); } } } }
void monster::knock_back_from(int x, int y) { if (x == posx() && y == posy()) return; // No effect point to(posx(), posy()); if (x < posx()) to.x++; if (x > posx()) to.x--; if (y < posy()) to.y++; if (y > posy()) to.y--; bool u_see = g->u_see(to.x, to.y); // First, see if we hit another monster int mondex = g->mon_at(to.x, to.y); if (mondex != -1) { monster *z = &(g->zombie(mondex)); apply_damage( z, bp_torso, z->type->size ); add_effect("stunned", 1); if (type->size > 1 + z->type->size) { z->knock_back_from(posx(), posy()); // Chain reaction! z->apply_damage( this, bp_torso, type->size ); z->add_effect("stunned", 1); } else if (type->size > z->type->size) { z->apply_damage( this, bp_torso, type->size ); z->add_effect("stunned", 1); } if (u_see) add_msg(_("The %s bounces off a %s!"), name().c_str(), z->name().c_str()); return; } int npcdex = g->npc_at(to.x, to.y); if (npcdex != -1) { npc *p = g->active_npc[npcdex]; apply_damage( p, bp_torso, 3 ); add_effect("stunned", 1); p->deal_damage( this, bp_torso, damage_instance( DT_BASH, type->size ) ); if (u_see) add_msg(_("The %s bounces off %s!"), name().c_str(), p->name.c_str()); return; } // If we're still in the function at this point, we're actually moving a tile! if (g->m.ter_at(to.x, to.y).has_flag(TFLAG_DEEP_WATER)) { if (g->m.has_flag("LIQUID", to.x, to.y) && 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.move_cost(to.x, to.y) == 0) { // It's some kind of wall. apply_damage( nullptr, bp_torso, type->size ); add_effect("stunned", 2); if (u_see) { add_msg(_("The %s bounces off a %s."), name().c_str(), g->m.tername(to.x, to.y).c_str()); } } else { // It's no wall setpos(to); } }
int vehicle::automatic_fire_turret( vehicle_part &pt ) { auto gun = turret_query( pt ); if( gun.query() != turret_data::status::ready ) { return 0; } tripoint pos = global_part_pos3( pt ); npc tmp; tmp.set_fake( true ); tmp.name = rmp_format( _( "<veh_player>The %s" ), pt.name().c_str() ); tmp.set_skill_level( gun.base()->gun_skill(), 8 ); tmp.set_skill_level( skill_id( "gun" ), 4 ); tmp.recoil = abs( velocity ) / 100 / 4; tmp.setpos( pos ); tmp.str_cur = 16; tmp.dex_cur = 8; tmp.per_cur = 12; // Assume vehicle turrets are friendly to the player. tmp.attitude = NPCATT_FOLLOW; int area = aoe_size( gun.ammo_effects() ); if( area > 0 ) { area += area == 1 ? 1 : 2; // Pad a bit for less friendly fire } tripoint targ = pos; auto &target = pt.target; if( target.first == target.second ) { // Manual target not set, find one automatically const bool u_see = g->u.sees( pos ); int boo_hoo; // @todo calculate chance to hit and cap range based upon this int range = std::min( gun.range(), 12 ); Creature *auto_target = tmp.auto_find_hostile_target( range, boo_hoo, area ); if( auto_target == nullptr ) { if( u_see && boo_hoo ) { add_msg( m_warning, ngettext( "%s points in your direction and emits an IFF warning beep.", "%s points in your direction and emits %d annoyed sounding beeps.", boo_hoo ), tmp.name.c_str(), boo_hoo ); } return 0; } targ = auto_target->pos(); } else if( target.first != target.second ) { // Target set manually // Make sure we didn't move between aiming and firing (it's a bug if we did) if( targ != target.first ) { target.second = target.first; return 0; } targ = target.second; // Remove the target target.second = target.first; } else { // Shouldn't happen target.first = target.second; return 0; } auto shots = gun.fire( tmp, targ ); if( g->u.sees( pos ) && shots ) { add_msg( _( "The %1$s fires its %2$s!" ), name.c_str(), pt.name().c_str() ); } return shots; }
bool fungal_effects::spread_fungus( const tripoint &p ) { int growth = 1; for( int i = p.x - 1; i <= p.x + 1; i++ ) { for( int j = p.y - 1; j <= p.y + 1; j++ ) { if( i == p.x && j == p.y ) { continue; } if( m.has_flag( "FUNGUS", tripoint( i, j, p.z ) ) ) { growth += 1; } } } bool converted = false; if( !m.has_flag_ter( "FUNGUS", p ) ) { // Terrain conversion if( m.has_flag_ter( "DIGGABLE", p ) ) { if( x_in_y( growth * 10, 100 ) ) { m.ter_set( p, t_fungus ); converted = true; } } else if( m.has_flag( "FLAT", p ) ) { if( m.has_flag( TFLAG_INDOORS, p ) ) { if( x_in_y( growth * 10, 500 ) ) { m.ter_set( p, t_fungus_floor_in ); converted = true; } } else if( m.has_flag( TFLAG_SUPPORTS_ROOF, p ) ) { if( x_in_y( growth * 10, 1000 ) ) { m.ter_set( p, t_fungus_floor_sup ); converted = true; } } else { if( x_in_y( growth * 10, 2500 ) ) { m.ter_set( p, t_fungus_floor_out ); converted = true; } } } else if( m.has_flag( "SHRUB", p ) ) { if( x_in_y( growth * 10, 200 ) ) { m.ter_set( p, t_shrub_fungal ); converted = true; } else if( x_in_y( growth, 1000 ) ) { m.ter_set( p, t_marloss ); converted = true; } } else if( m.has_flag( "THIN_OBSTACLE", p ) ) { if( x_in_y( growth * 10, 150 ) ) { m.ter_set( p, t_fungus_mound ); converted = true; } } else if( m.has_flag( "YOUNG", p ) ) { if( x_in_y( growth * 10, 500 ) ) { m.ter_set( p, t_tree_fungal_young ); converted = true; } } else if( m.has_flag( "WALL", p ) ) { if( x_in_y( growth * 10, 5000 ) ) { converted = true; m.ter_set( p, t_fungus_wall ); } } // Furniture conversion if( converted ) { if( m.has_flag( "FLOWER", p ) ) { m.furn_set( p, f_flower_fungal ); } else if( m.has_flag( "ORGANIC", p ) ) { if( m.furn( p ).obj().movecost == -10 ) { m.furn_set( p, f_fungal_mass ); } else { m.furn_set( p, f_fungal_clump ); } } else if( m.has_flag( "PLANT", p ) ) { // Replace the (already existing) seed m.i_at( p )[0] = item( "fungal_seeds", calendar::turn ); } } return true; } else { // Everything is already fungus if( growth == 9 ) { return false; } for( int i = p.x - 1; i <= p.x + 1; i++ ) { for( int j = p.y - 1; j <= p.y + 1; j++ ) { tripoint dest( i, j, p.z ); // One spread on average if( !m.has_flag( "FUNGUS", dest ) && one_in( 9 - growth ) ) { //growth chance is 100 in X simplified if( m.has_flag( "DIGGABLE", dest ) ) { m.ter_set( dest, t_fungus ); converted = true; } else if( m.has_flag( "FLAT", dest ) ) { if( m.has_flag( TFLAG_INDOORS, dest ) ) { if( one_in( 5 ) ) { m.ter_set( dest, t_fungus_floor_in ); converted = true; } } else if( m.has_flag( TFLAG_SUPPORTS_ROOF, dest ) ) { if( one_in( 10 ) ) { m.ter_set( dest, t_fungus_floor_sup ); converted = true; } } else { if( one_in( 25 ) ) { m.ter_set( dest, t_fungus_floor_out ); converted = true; } } } else if( m.has_flag( "SHRUB", dest ) ) { if( one_in( 2 ) ) { m.ter_set( dest, t_shrub_fungal ); converted = true; } else if( one_in( 25 ) ) { m.ter_set( dest, t_marloss ); converted = true; } } else if( m.has_flag( "THIN_OBSTACLE", dest ) ) { if( x_in_y( 10, 15 ) ) { m.ter_set( dest, t_fungus_mound ); converted = true; } } else if( m.has_flag( "YOUNG", dest ) ) { if( one_in( 5 ) ) { if( m.get_field_strength( p, fd_fungal_haze ) != 0 ) { if( one_in( 8 ) ) { // young trees are vulnerable m.ter_set( dest, t_fungus ); gm.summon_mon( mon_fungal_blossom, p ); if( gm.u.sees( p ) ) { add_msg( m_warning, _( "The young tree blooms forth into a fungal blossom!" ) ); } } else if( one_in( 4 ) ) { m.ter_set( dest, t_marloss_tree ); } } else { m.ter_set( dest, t_tree_fungal_young ); } converted = true; } } else if( m.has_flag( "TREE", dest ) ) { if( one_in( 10 ) ) { if( m.get_field_strength( p, fd_fungal_haze ) != 0 ) { if( one_in( 10 ) ) { m.ter_set( dest, t_fungus ); gm.summon_mon( mon_fungal_blossom, p ); if( gm.u.sees( p ) ) { add_msg( m_warning, _( "The tree blooms forth into a fungal blossom!" ) ); } } else if( one_in( 6 ) ) { m.ter_set( dest, t_marloss_tree ); } } else { m.ter_set( dest, t_tree_fungal ); } converted = true; } } else if( m.has_flag( "WALL", dest ) ) { if( one_in( 50 ) ) { converted = true; m.ter_set( dest, t_fungus_wall ); } } if( converted ) { if( m.has_flag( "FLOWER", dest ) ) { m.furn_set( dest, f_flower_fungal ); } else if( m.has_flag( "ORGANIC", dest ) ) { if( m.furn( dest ).obj().movecost == -10 ) { m.furn_set( dest, f_fungal_mass ); } else { m.furn_set( dest, f_fungal_clump ); } } else if( m.has_flag( "PLANT", dest ) ) { // Replace the (already existing) seed m.i_at( p )[0] = item( "fungal_seeds", calendar::turn ); } } } } } return false; } }
void activity_on_turn_move_loot( player_activity &, player &p ) { auto &mgr = zone_manager::get_manager(); if( g->m.check_vehicle_zones( g->get_levz() ) ) { mgr.cache_vzones(); } const auto abspos = g->m.getabs( p.pos() ); const auto &src_set = mgr.get_near( zone_type_id( "LOOT_UNSORTED" ), abspos ); vehicle *src_veh, *dest_veh; int src_part, dest_part; // Nuke the current activity, leaving the backlog alone. p.activity = player_activity(); // sort source tiles by distance const auto &src_sorted = get_sorted_tiles_by_distance( abspos, src_set ); if( !mgr.is_sorting() ) { mgr.start_sort( src_sorted ); } for( auto &src : src_sorted ) { const auto &src_loc = g->m.getlocal( src ); bool is_adjacent_or_closer = square_dist( p.pos(), src_loc ) <= 1; // skip tiles in IGNORE zone and tiles on fire (to prevent taking out wood off the lit brazier) // and inaccessible furniture, like filled charcoal kiln if( mgr.has( zone_type_id( "LOOT_IGNORE" ), src ) || g->m.get_field( src_loc, fd_fire ) != nullptr || !g->m.can_put_items_ter_furn( src_loc ) ) { continue; } auto items = std::vector<item *>(); //Check source for cargo part //map_stack and vehicle_stack are different types but inherit from item_stack //TODO: use one for loop if( const cata::optional<vpart_reference> vp = g->m.veh_at( src_loc ).part_with_feature( "CARGO", false ) ) { src_veh = &vp->vehicle(); src_part = vp->part_index(); for( auto &it : src_veh->get_items( src_part ) ) { if( !it.made_of_from_type( LIQUID ) ) { // skip unpickable liquid items.push_back( &it ); } } } else { src_veh = nullptr; src_part = -1; for( auto &it : g->m.i_at( src_loc ) ) { if( !it.made_of_from_type( LIQUID ) ) { // skip unpickable liquid items.push_back( &it ); } } } //Skip items that have already been processed for( auto it = items.begin() + mgr.get_num_processed( src ); it < items.end(); it++ ) { mgr.increment_num_processed( src ); const auto id = mgr.get_near_zone_type_for_item( **it, abspos ); // checks whether the item is already on correct loot zone or not // if it is, we can skip such item, if not we move the item to correct pile // think empty bag on food pile, after you ate the content if( !mgr.has( id, src ) ) { const auto &dest_set = mgr.get_near( id, abspos ); for( auto &dest : dest_set ) { const auto &dest_loc = g->m.getlocal( dest ); //Check destination for cargo part if( const cata::optional<vpart_reference> vp = g->m.veh_at( dest_loc ).part_with_feature( "CARGO", false ) ) { dest_veh = &vp->vehicle(); dest_part = vp->part_index(); } else { dest_veh = nullptr; dest_part = -1; } // skip tiles with inaccessible furniture, like filled charcoal kiln if( !g->m.can_put_items_ter_furn( dest_loc ) ) { continue; } units::volume free_space; // if there's a vehicle with space do not check the tile beneath if( dest_veh ) { free_space = dest_veh->free_volume( dest_part ); } else { free_space = g->m.free_volume( dest_loc ); } // check free space at destination if( free_space >= ( *it )->volume() ) { // before we move any item, check if player is at or adjacent to the loot source tile if( !is_adjacent_or_closer ) { std::vector<tripoint> route; bool adjacent = false; // get either direct route or route to nearest adjacent tile if source tile is impassable if( g->m.passable( src_loc ) ) { route = g->m.route( p.pos(), src_loc, p.get_pathfinding_settings(), p.get_path_avoid() ); } else { // immpassable source tile (locker etc.), get route to nerest adjacent tile instead route = route_adjacent( p, src_loc ); adjacent = true; } // check if we found path to source / adjacent tile if( route.empty() ) { add_msg( m_info, _( "You can't reach the source tile. Try to sort out loot without a cart." ) ); return; } // shorten the route to adjacent tile, if necessary if( !adjacent ) { route.pop_back(); } // set the destination and restart activity after player arrives there // we don't need to check for safe mode, activity will be restarted only if // player arrives on destination tile p.set_destination( route, player_activity( activity_id( "ACT_MOVE_LOOT" ) ) ); // didn't actually process so decrement mgr.decrement_num_processed( src ); return; } move_item( **it, ( *it )->count(), src_loc, dest_loc, src_veh, src_part ); // moved item away from source so decrement mgr.decrement_num_processed( src ); break; } } if( p.moves <= 0 ) { // Restart activity and break from cycle. p.assign_activity( activity_id( "ACT_MOVE_LOOT" ) ); return; } } } } // If we got here without restarting the activity, it means we're done add_msg( m_info, _( "You sorted out every item you could." ) ); mgr.end_sort(); }
//++++++++++++++++++++++++++++++++++++++++++++++++ //CONMAN: do_fifo_cmd(): exercises command sent by fifo //++++++++++++++++++++++++++++++++++++++++++++++++ int do_fifo_cmd() { //terminate netfilter program if(cmcmd.cmd == 'Q') { terminate_loop = 1; return 0; } //find session: find max session index if no session provided struct session *curr_sess, *try_sess, *tmp_sess; curr_sess = NULL; unsigned max_index = 0; HASH_ITER(hh, sess_hash, try_sess, tmp_sess) { if(try_sess != NULL){ if(cmcmd.sess == -1){ if(try_sess->index >= max_index){ curr_sess = try_sess; max_index = try_sess->index; } } } if((int)try_sess->index == cmcmd.sess) { curr_sess = try_sess; break; } } if(curr_sess==NULL) { sprintf(msg_buf,"do_fifo_cmd: sess with id=%d not found - FIFO CMD ABORTED", cmcmd.sess); add_msg(msg_buf); return 0; } if(curr_sess->conman_state != '0') { sprintf(msg_buf,"do_fifo_cmd: sess->conman_state=%c != 0 - FIFO CMD ABORTED", curr_sess->conman_state); add_msg(msg_buf); return 0; } if(curr_sess->sess_state < ESTABLISHED || curr_sess->sess_state >= TIME_WAIT) { curr_sess->conman_state = '0'; add_msg(msg_buf); sprintf(msg_buf,"do_fifo_cmd: sess_state=%d not established - FIFO CMD ABORTED", curr_sess->sess_state); return 0; } switch(cmcmd.cmd) { case 'A': return add_sfl_fifo(curr_sess); break; case 'D': return delete_sfl_fifo(curr_sess); break; case 'S': return switch_sfl_fifo(curr_sess); break; case 'B': return break_sfl_fifo(curr_sess); break; default: return 0; } }
//++++++++++++++++++++++++++++++++++++++++++++++++ //CONMAN: do_break(struct session *sess) //++++++++++++++++++++++++++++++++++++++++++++++++ int break_sfl_fifo(struct session *sess) { //present active subflow will be interrupted //if candidate is up, it becomes active subflow //if no candidate exists but loc IP address is provided, a new subflow is created //if no candidate and no new local IP address but USE_PORT_FOR_NEW_SUBFLOWS, new port on existing loc IP is used //otherwise nothing happpens //break event is set whenever a new subflow has to be created //find candidate subflow struct subflow *new_sfl = NULL; new_sfl = find_subflow_in_session(sess, (cmcmd.sfl>-1), (size_t) cmcmd.sfl, 1); //if new_sfl == NULL try to create new subflow if(new_sfl == NULL) { sprintf(msg_buf, "break_sfl_fifo: candidate subflow not found -> creating new subflow"); add_msg(msg_buf); //get fourtuple for new subflow (all except active) //note: act_subflow != NULL at this point cmcmd.ip_loc = 0; cmcmd.prt_loc = 0; cmcmd.ip_rem = 0; cmcmd.prt_rem = 0; struct fourtuple ft; if(!determine_fourtuple(sess, &ft)) { sprintf(msg_buf,"break_sfl_fifo: new subflow cannot be created - FIFO CMD ABORTED"); add_msg(msg_buf); return 0; } if(UPDATE_DEFAULT_ROUTE) update_default_route(ft.ip_loc); //set last subflow to active subflow sess->act_subflow->broken = 1; sess->cdsn_loc = sess->highest_dan_loc-1; unsigned char backup = 0; if(initiate_cand_subflow(sess, &ft, backup) == 0){ sprintf(msg_buf,"break_sfl_fifo: initiating new subflow creates error - FIFO CMD ABORTED"); add_msg(msg_buf); return 0; } } else { sprintf(msg_buf,"break_sfl_fifo: sess id=%zu - deleting old sfl id=%zu and using new sfl id=%zu", sess->index, sess->act_subflow->index, new_sfl->index); add_msg(msg_buf); break_active_sfl(sess, new_sfl);//switches to new subflow in "break" manner //send break ack: TPprio for new subflow and REMOVE_ADDR on old address send_break_ack(sess->act_subflow, sess->last_subflow->addr_id_loc); if(sess->act_subflow->addr_id_loc == sess->last_subflow->addr_id_loc) send_reset_subflow(sess->last_subflow); create_prio_event(&sess->ft, sess->last_subflow->addr_id_loc);//to resend breal ack return 1; } return 1; }
std::vector<item> game::multidrop(std::vector<item> &dropped_worn, int &freed_volume_capacity) { WINDOW* w_inv = newwin(TERRAIN_WINDOW_HEIGHT, TERRAIN_WINDOW_WIDTH + (use_narrow_sidebar() ? 45 : 55), VIEW_OFFSET_Y, VIEW_OFFSET_X); const int maxitems = TERRAIN_WINDOW_HEIGHT - 5; freed_volume_capacity = 0; u.inv.restack(&u); u.inv.sort(); int drp_line_width=getmaxx(w_inv)-90; const std::string drp_line_padding = ( drp_line_width > 1 ? std::string(drp_line_width, ' ') : " "); std::map<int, int> dropping; // Count of how many we'll drop from each position int count = 0; // The current count std::vector<char> dropped_armor; // Always single, not counted int dropped_weapon = 0; bool warned_about_bionic = false; // Printed add_msg re: dropping bionics print_inv_statics(w_inv, _("Multidrop:"), dropped_armor, dropped_weapon); int base_weight = u.weight_carried(); int base_volume = u.volume_carried(); int ch = (int)'.'; int start = 0, cur_it = 0, max_it; indexed_invslice stacks = u.inv.slice_filter(); CategoriesVector CATEGORIES; std::vector<int> firsts = find_firsts(stacks, CATEGORIES); int selected = -1; int selected_pos = INT_MIN; int next_category_at = 0; int prev_category_at = 0; bool inCategoryMode = false; std::vector<int> category_order; category_order.reserve(firsts.size()); // Items are not guaranteed to be in the same order as their categories, in fact they almost never are. // So we sort the categories by which items actually show up first in the inventory. for (int current_item = 0; current_item < u.inv.size(); ++current_item) { for (int i = 1; i < CATEGORIES.size(); ++i) { if (current_item == firsts[i - 1]) { category_order.push_back(i - 1); } } } do { // Find the inventory position of the first item in the previous and next category (in relation // to the currently selected category). for (int i = 0; i < category_order.size(); ++i) { if (selected > firsts[category_order[i]] && prev_category_at <= firsts[category_order[i]]) { prev_category_at = firsts[category_order[i]]; } if (selected < firsts[category_order[i]] && next_category_at <= selected) { next_category_at = firsts[category_order[i]]; } } inventory drop_subset = u.inv.subset(dropping); int new_weight = base_weight - drop_subset.weight(); int new_volume = base_volume - drop_subset.volume(); for (int i = 0; i < dropped_armor.size(); ++i) { new_weight -= u.i_at(dropped_armor[i]).weight(); } if (dropped_weapon == -1) { new_weight -= u.weapon.weight(); } else if (dropped_weapon > 0) { item tmp(u.weapon); tmp.charges = dropped_weapon; new_weight -= tmp.weight(); } print_inv_weight_vol(w_inv, new_weight, new_volume, calc_volume_capacity(dropped_armor)); int cur_line = 2; max_it = 0; int drp_line = 1; // Print weapon to be dropped, the first position is reserved for high visibility mvwprintw(w_inv, 0, 90, "%s", drp_line_padding.c_str()); if (dropped_weapon != 0) { if (dropped_weapon == -1) { mvwprintz(w_inv, 0, 90, c_ltblue, "%c + %s", u.weapon.invlet, u.weapname().c_str()); } else { mvwprintz(w_inv, 0, 90, c_ltblue, "%c # %s {%d}", u.weapon.invlet, u.weapon.tname().c_str(), dropped_weapon); } mvwprintw(w_inv, drp_line, 90, "%s", drp_line_padding.c_str()); drp_line++; } // Print worn items to be dropped bool dropping_a = false; if (u.worn.size() > 0){ for (int k = 0; k < u.worn.size(); k++) { bool dropping_w = false; for (int j = 0; j < dropped_armor.size() && !dropping_w; j++) { if (dropped_armor[j] == u.worn[k].invlet) { dropping_w = true; dropping_a = true; mvwprintw(w_inv, drp_line, 90, "%s", drp_line_padding.c_str()); mvwprintz(w_inv, drp_line, 90, c_cyan, "%c + %s", u.worn[k].invlet, u.worn[k].tname().c_str()); drp_line++; } } } } if(dropping_a) { mvwprintw(w_inv, drp_line, 90, "%s", drp_line_padding.c_str()); drp_line++; } for (cur_it = start; cur_it < start + maxitems && cur_line < maxitems+3; cur_it++) { // Clear the current line; mvwprintw(w_inv, cur_line, 0, " "); mvwprintw(w_inv, drp_line, 90, "%s", drp_line_padding.c_str()); mvwprintw(w_inv, drp_line + 1, 90, "%s", drp_line_padding.c_str()); // Print category header for (int i = 1; i < CATEGORIES.size(); i++) { if (cur_it == firsts[i-1]) { mvwprintz(w_inv, cur_line, 0, c_magenta, CATEGORIES[i].name.c_str()); cur_line++; } } if ( selected < start && selected > -1 ) selected = start; if (cur_it < stacks.size()) { item& it = stacks[cur_it].first->front(); if( cur_it == selected) { selected_pos = stacks[cur_it].second; } const char invlet = it.invlet == 0 ? ' ' : it.invlet; nc_color selected_line_color = inCategoryMode ? c_white_red : h_white; mvwputch (w_inv, cur_line, 0, (cur_it == selected ? selected_line_color : c_white), invlet); char icon = '-'; if (dropping[cur_it] >= (it.count_by_charges() ? it.charges : stacks[cur_it].first->size())) { icon = '+'; } else if (dropping[cur_it] > 0) { icon = '#'; } nc_color col = ( cur_it == selected ? selected_line_color : it.color_in_inventory() ); mvwprintz(w_inv, cur_line, 1, col, " %c %s", icon, it.tname().c_str()); if (stacks[cur_it].first->size() > 1) { wprintz(w_inv, col, " x %d", stacks[cur_it].first->size()); } if (it.charges > 0) { wprintz(w_inv, col, " (%d)", it.charges); } else if (it.contents.size() == 1 && it.contents[0].charges > 0) { wprintw(w_inv, " (%d)", it.contents[0].charges); } if (icon=='+'||icon=='#') { mvwprintz(w_inv, drp_line, 90, col, "%c %c %s", invlet, icon, it.tname().c_str()); if (icon=='+') { if (stacks[cur_it].first->size() > 1) { wprintz(w_inv, col, " x %d", stacks[cur_it].first->size()); } if (it.charges > 0) { wprintz(w_inv, col, " (%d)", it.charges); } } if (icon=='#') { wprintz(w_inv, col, " {%d}", dropping[cur_it]); } drp_line++; } } cur_line++; max_it=cur_it; } if (inCategoryMode) { mvwprintz(w_inv, maxitems + 4, 32, c_white_red, _("In category select mode! Press [TAB] to enter item select mode.")); } else { mvwprintz(w_inv, maxitems + 4, 32, h_white, _("In item select mode! Press [TAB] to enter category select mode.")); } if (start > 0) { mvwprintw(w_inv, maxitems + 4, 0, _("< Go Back")); } if (cur_it < u.inv.size()) { mvwprintw(w_inv, maxitems + 4, 12, _("> More items")); } wrefresh(w_inv); /* back to (int)getch() as input() mangles arrow keys ch = input(); */ ch = getch(); if (ch == '\t') { inCategoryMode = !inCategoryMode; } else if ( ch == '<' || ch == KEY_PPAGE ) { if( start > 0) { for (int i = 1; i < maxitems+4; i++) mvwprintz(w_inv, i, 0, c_black, " "); start -= maxitems; if (start < 0) start = 0; mvwprintw(w_inv, maxitems + 4, 0, " "); if ( selected > -1 ) selected = start; // oy, the cheese } } else if ( ch == '>' || ch == KEY_NPAGE ) { if ( cur_it < u.inv.size()) { start = cur_it; mvwprintw(w_inv, maxitems + 4, 12, " "); for (int i = 1; i < maxitems+4; i++) mvwprintz(w_inv, i, 0, c_black, " "); if ( selected < start && selected > -1 ) selected = start; } } else if ( ch == KEY_DOWN ) { if ( selected < 0 ) { selected = start; } else { if (inCategoryMode) { selected < firsts[category_order[category_order.size() - 1]] ? selected = next_category_at : 0; } else { selected++; } next_category_at = prev_category_at = 0; } if ( selected > max_it ) { if( cur_it < u.inv.size() ) { start = cur_it; mvwprintw(w_inv, maxitems + 4, 12, " "); for (int i = 1; i < maxitems+4; i++) mvwprintz(w_inv, i, 0, c_black, " "); } else { selected = u.inv.size() - 1; // wraparound? } } } else if ( ch == KEY_UP ) { inCategoryMode ? selected = prev_category_at : selected--; next_category_at = prev_category_at = 0; if ( selected < -1 ) { selected = -1; // wraparound? } else if ( selected < start ) { if ( start > 0 ) { for (int i = 1; i < maxitems+4; i++) mvwprintz(w_inv, i, 0, c_black, " "); start -= maxitems; if (start < 0) start = 0; mvwprintw(w_inv, maxitems + 4, 0, " "); } } } else if (ch >= '0'&& ch <= '9') { ch = (char)ch - '0'; count *= 10; count += ch; } else { // todo: reformat and maybe rewrite item* it; int it_pos; if ( ch == '\t' || ch == KEY_RIGHT || ch == KEY_LEFT ) { it_pos = selected_pos; it = &u.inv.find_item(it_pos); } else { it = &u.inv.item_by_letter((char)ch); it_pos = u.inv.position_by_item(it); } if (it == 0 || it->is_null()) { // Not from inventory int found = false; for (int i = 0; i < dropped_armor.size() && !found; i++) { if (dropped_armor[i] == ch) { dropped_armor.erase(dropped_armor.begin() + i); found = true; print_inv_statics(w_inv, _("Multidrop:"), dropped_armor, dropped_weapon); } } if (!found && ch == u.weapon.invlet && !u.weapon.is_null()) { if (u.weapon.has_flag("NO_UNWIELD")) { if (!warned_about_bionic) { add_msg(_("You cannot drop your %s."), u.weapon.tname().c_str()); warned_about_bionic = true; } } else { // user selected weapon, which is a normal item if (count == 0) { // No count given, invert the selection status: drop all <-> drop none if (dropped_weapon == 0) { dropped_weapon = -1; } else { dropped_weapon = 0; } } else if (u.weapon.count_by_charges() && count < u.weapon.charges) { // can drop part of weapon and count is valid for this dropped_weapon = count; } else { dropped_weapon = -1; } count = 0; print_inv_statics(w_inv, _("Multidrop:"), dropped_armor, dropped_weapon); } } else if (!found) { dropped_armor.push_back(ch); print_inv_statics(w_inv, _("Multidrop:"), dropped_armor, dropped_weapon); } } else { int index = -1; for (int i = 0; i < stacks.size(); ++i) { if (&(stacks[i].first->front()) == it) { index = i; break; } } if (index == -1) { debugmsg("Inventory got out of sync with inventory slice?"); } if (count == 0) { if (it->count_by_charges()) { if (dropping[it_pos] == 0) { dropping[it_pos] = -1; } else { dropping[it_pos] = 0; } } else { if (dropping[it_pos] == 0) { dropping[it_pos] = stacks[index].first->size(); } else { dropping[it_pos] = 0; } } } else if (count >= stacks[index].first->size() && !it->count_by_charges()) { dropping[it_pos] = stacks[index].first->size(); } else { dropping[it_pos] = count; } count = 0; } } } while (ch != '\n' && ch != KEY_ESCAPE && ch != ' '); werase(w_inv); delwin(w_inv); erase(); refresh_all(); std::vector<item> ret; if (ch != '\n') return ret; // Canceled! // We iterate backwards because deletion will invalidate later indices. for (std::map<int,int>::reverse_iterator it = dropping.rbegin(); it != dropping.rend(); ++it) { if (it->second == -1) ret.push_back( u.inv.remove_item( it->first)); else if (it->second && u.inv.find_item( it->first).count_by_charges()) { int charges = u.inv.find_item( it->first).charges;// >= it->second ? : it->second; ret.push_back( u.inv.reduce_charges( it->first, it->second > charges ? charges : it->second)); } else if (it->second) for (int j = it->second; j > 0; j--) ret.push_back( u.inv.remove_item( it->first)); } if (dropped_weapon == -1) { ret.push_back(u.remove_weapon()); } else if (dropped_weapon > 0) { ret.push_back(u.weapon); u.weapon.charges -= dropped_weapon; ret.back().charges = dropped_weapon; } for (int i = 0; i < dropped_armor.size(); i++) { int wornpos = u.invlet_to_position(dropped_armor[i]); const it_armor *ita = dynamic_cast<const it_armor *>(u.i_at(dropped_armor[i]).type); if (wornpos == INT_MIN || !u.takeoff(wornpos, true)) { continue; } u.moves -= 250; // same as in game::takeoff if(ita != 0) { freed_volume_capacity += ita->storage; } // Item could have been dropped after taking it off if (&u.inv.item_by_letter(dropped_armor[i]) != &u.inv.nullitem) { dropped_worn.push_back(u.i_rem(dropped_armor[i])); } } return ret; }
void game::place_construction(constructable *con) { refresh_all(); inventory total_inv = crafting_inventory(); std::vector<point> valid; for (int x = u.posx - 1; x <= u.posx + 1; x++) { for (int y = u.posy - 1; y <= u.posy + 1; y++) { if (x == u.posx && y == u.posy) y++; construct test; bool place_okay = (test.*(con->able))(this, point(x, y)); for (int i = 0; i < con->stages.size() && !place_okay; i++) { if (m.ter(x, y) == con->stages[i].terrain) place_okay = true; } if (place_okay) { // Make sure we're not trying to continue a construction that we can't finish int starting_stage = 0, max_stage = -1; for (int i = 0; i < con->stages.size(); i++) { if (m.ter(x, y) == con->stages[i].terrain) starting_stage = i + 1; } for(int i = starting_stage; i < con->stages.size(); i++) { if (player_can_build(u, total_inv, con, i, true, true)) max_stage = i; else break; } if (max_stage >= starting_stage) { valid.push_back(point(x, y)); m.drawsq(w_terrain, u, x, y, true, false); wrefresh(w_terrain); } } } } mvprintz(0, 0, c_red, "Pick a direction in which to construct:"); int dirx, diry; get_direction(this, dirx, diry, input()); if (dirx == -2) { add_msg("Invalid direction."); return; } dirx += u.posx; diry += u.posy; bool point_is_okay = false; for (int i = 0; i < valid.size() && !point_is_okay; i++) { if (valid[i].x == dirx && valid[i].y == diry) point_is_okay = true; } if (!point_is_okay) { add_msg("You cannot build there!"); return; } // Figure out what stage to start at, and what stage is the maximum int starting_stage = 0, max_stage = 0; for (int i = 0; i < con->stages.size(); i++) { if (m.ter(dirx, diry) == con->stages[i].terrain) starting_stage = i + 1; if (player_can_build(u, total_inv, con, i, true)) max_stage = i; } u.assign_activity(ACT_BUILD, con->stages[starting_stage].time * 1000, con->id); u.moves = 0; std::vector<int> stages; for (int i = starting_stage; i <= max_stage; i++) stages.push_back(i); u.activity.values = stages; u.activity.placement = point(dirx, diry); }
void Creature::add_effect( const efftype_id &eff_id, int dur, body_part bp, bool permanent, int intensity, bool force ) { // Check our innate immunity if( !force && is_immune_effect( eff_id ) ) { return; } if( !eff_id.is_valid() ) { debugmsg( "Invalid effect, ID: %s", eff_id.c_str() ); return; } const effect_type &type = eff_id.obj(); // Mutate to a main (HP'd) body_part if necessary. if (type.get_main_parts()) { bp = mutate_to_main_part(bp); } bool found = false; // Check if we already have it auto matching_map = effects.find(eff_id); if (matching_map != effects.end()) { auto &bodyparts = matching_map->second; auto found_effect = bodyparts.find(bp); if (found_effect != bodyparts.end()) { found = true; effect &e = found_effect->second; // If we do, mod the duration, factoring in the mod value e.mod_duration(dur * e.get_dur_add_perc() / 100); // Limit to max duration if (e.get_max_duration() > 0 && e.get_duration() > e.get_max_duration()) { e.set_duration(e.get_max_duration()); } // Adding a permanent effect makes it permanent if( e.is_permanent() ) { e.pause_effect(); } // Set intensity if value is given if (intensity > 0) { e.set_intensity(intensity); // Else intensity uses the type'd step size if it already exists } else if (e.get_int_add_val() != 0) { e.mod_intensity(e.get_int_add_val()); } // Bound intensity by [1, max intensity] if (e.get_intensity() < 1) { add_msg( m_debug, "Bad intensity, ID: %s", e.get_id().c_str() ); e.set_intensity(1); } else if (e.get_intensity() > e.get_max_intensity()) { e.set_intensity(e.get_max_intensity()); } } } if( found == false ) { // If we don't already have it then add a new one // Then check if the effect is blocked by another for( auto &elem : effects ) { for( auto &_effect_it : elem.second ) { for( const auto blocked_effect : _effect_it.second.get_blocks_effects() ) { if (blocked_effect == eff_id) { // The effect is blocked by another, return return; } } } } // Now we can make the new effect for application effect e(&type, dur, bp, permanent, intensity, calendar::turn); // Bound to max duration if (e.get_max_duration() > 0 && e.get_duration() > e.get_max_duration()) { e.set_duration(e.get_max_duration()); } // Force intensity if it is duration based if( e.get_int_dur_factor() != 0 ) { // + 1 here so that the lowest is intensity 1, not 0 e.set_intensity( ( e.get_duration() / e.get_int_dur_factor() ) + 1 ); } // Bound new effect intensity by [1, max intensity] if (e.get_intensity() < 1) { add_msg( m_debug, "Bad intensity, ID: %s", e.get_id().c_str() ); e.set_intensity(1); } else if (e.get_intensity() > e.get_max_intensity()) { e.set_intensity(e.get_max_intensity()); } effects[eff_id][bp] = e; if (is_player()) { // Only print the message if we didn't already have it if(type.get_apply_message() != "") { add_msg(type.gain_game_message_type(), _(type.get_apply_message().c_str())); } add_memorial_log(pgettext("memorial_male", type.get_apply_memorial_log().c_str()), pgettext("memorial_female", type.get_apply_memorial_log().c_str())); } // Perform any effect addition effects. bool reduced = resists_effect(e); add_eff_effects(e, reduced); } }
/** * Attempts to harm a creature with a projectile. * * @param source Pointer to the creature who shot the projectile. * @param attack A structure describing the attack and its results. */ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) { const double missed_by = attack.missed_by; if( missed_by >= 1.0 ) { // Total miss return; } const projectile &proj = attack.proj; dealt_damage_instance &dealt_dam = attack.dealt_dam; const auto &proj_effects = proj.proj_effects; const bool u_see_this = g->u.sees(*this); const int avoid_roll = dodge_roll(); // Do dice(10, speed) instead of dice(speed, 10) because speed could potentially be > 10000 const int diff_roll = dice( 10, proj.speed ); // Partial dodge, capped at [0.0, 1.0], added to missed_by const double dodge_rescaled = avoid_roll / static_cast<double>( diff_roll ); const double goodhit = missed_by + std::max( 0.0, std::min( 1.0, dodge_rescaled ) ) ; if( goodhit >= 1.0 ) { // "Avoid" rather than "dodge", because it includes removing self from the line of fire // rather than just Matrix-style bullet dodging if( source != nullptr && g->u.sees( *source ) ) { add_msg_player_or_npc( m_warning, _("You avoid %s projectile!"), _("<npcname> avoids %s projectile."), source->disp_name(true).c_str() ); } else { add_msg_player_or_npc( m_warning, _("You avoid an incoming projectile!"), _("<npcname> avoids an incoming projectile.") ); } attack.missed_by = 1.0; // Arbitrary value return; } // Bounce applies whether it does damage or not. if( proj.proj_effects.count( "BOUNCE" ) ) { add_effect( effect_bounced, 1); } body_part bp_hit; double hit_value = missed_by + rng_float(-0.5, 0.5); // Headshots considered elsewhere if( hit_value <= 0.4 ) { bp_hit = bp_torso; } else if (one_in(4)) { if( one_in(2)) { bp_hit = bp_leg_l; } else { bp_hit = bp_leg_r; } } else { if( one_in(2)) { bp_hit = bp_arm_l; } else { bp_hit = bp_arm_r; } } double damage_mult = 1.0; std::string message = ""; game_message_type gmtSCTcolor = m_neutral; if( goodhit < 0.1 ) { message = _("Headshot!"); gmtSCTcolor = m_headshot; damage_mult *= rng_float(2.45, 3.35); bp_hit = bp_head; // headshot hits the head, of course } else if( goodhit < 0.2 ) { message = _("Critical!"); gmtSCTcolor = m_critical; damage_mult *= rng_float(1.75, 2.3); } else if( goodhit < 0.4 ) { message = _("Good hit!"); gmtSCTcolor = m_good; damage_mult *= rng_float(1, 1.5); } else if( goodhit < 0.6 ) { damage_mult *= rng_float(0.5, 1); } else if( goodhit < 0.8 ) { message = _("Grazing hit."); gmtSCTcolor = m_grazing; damage_mult *= rng_float(0, .25); } else { damage_mult *= 0; } if( source != nullptr && !message.empty() ) { source->add_msg_if_player(m_good, message.c_str()); } attack.missed_by = goodhit; // copy it, since we're mutating damage_instance impact = proj.impact; if( proj_effects.count("NOGIB") > 0 ) { impact.add_effect("NOGIB"); } impact.mult_damage(damage_mult); dealt_dam = deal_damage(source, bp_hit, impact); dealt_dam.bp_hit = bp_hit; // Apply ammo effects to target. const std::string target_material = get_material(); if (proj.proj_effects.count("FLAME")) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood" ) ) { add_effect( effect_onfire, rng(8, 20)); } else if (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) { add_effect( effect_onfire, rng(5, 10)); } } else if (proj.proj_effects.count("INCENDIARY") ) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood") ) { add_effect( effect_onfire, rng(2, 6)); } else if ( (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) && one_in(4) ) { add_effect( effect_onfire, rng(1, 4)); } } else if (proj.proj_effects.count("IGNITE")) { if (0 == target_material.compare("veggy") || 0 == target_material.compare("cotton") || 0 == target_material.compare("wool") || 0 == target_material.compare("paper") || 0 == target_material.compare("wood") ) { add_effect( effect_onfire, rng(6, 6)); } else if (0 == target_material.compare("flesh") || 0 == target_material.compare("iflesh") ) { add_effect( effect_onfire, rng(10, 10)); } } if( bp_hit == bp_head && proj_effects.count( "BLINDS_EYES" ) ) { // TODO: Change this to require bp_eyes add_env_effect( effect_blind, bp_eyes, 5, rng( 3, 10 ) ); } if( proj_effects.count( "APPLY_SAP" ) ) { add_effect( effect_sap, dealt_dam.total_damage() ); } int stun_strength = 0; if (proj.proj_effects.count("BEANBAG")) { stun_strength = 4; } if (proj.proj_effects.count("LARGE_BEANBAG")) { stun_strength = 16; } if( stun_strength > 0 ) { switch( get_size() ) { case MS_TINY: stun_strength *= 4; break; case MS_SMALL: stun_strength *= 2; break; case MS_MEDIUM: default: break; case MS_LARGE: stun_strength /= 2; break; case MS_HUGE: stun_strength /= 4; break; } add_effect( effect_stunned, rng(stun_strength / 2, stun_strength) ); } if(u_see_this) { if( damage_mult == 0 ) { if( source != nullptr ) { add_msg( source->is_player() ? _("You miss!") : _("The shot misses!") ); } } else if( dealt_dam.total_damage() == 0 ) { //~ 1$ - monster name, 2$ - monster's bodypart add_msg(_("The shot reflects off %1$s %2$s!"), disp_name(true).c_str(), skin_name().c_str()); } else if( is_player() ) { //monster hits player ranged //~ Hit message. 1$s is bodypart name in accusative. 2$d is damage value. add_msg_if_player(m_bad, _( "You were hit in the %1$s for %2$d damage." ), body_part_name_accusative(bp_hit).c_str( ), dealt_dam.total_damage()); } else if( source != nullptr ) { if( source->is_player() ) { //player hits monster ranged SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(dealt_dam.total_damage(), get_hp_max(), true).first, m_good, message, gmtSCTcolor); if (get_hp() > 0) { SCT.add(posx(), posy(), direction_from(0, 0, posx() - source->posx(), posy() - source->posy()), get_hp_bar(get_hp(), get_hp_max(), true).first, m_good, //~ "hit points", used in scrolling combat text _("hp"), m_neutral, "hp"); } else { SCT.removeCreatureHP(); } add_msg(m_good, _("You hit %s for %d damage."), disp_name().c_str(), dealt_dam.total_damage()); } else if( u_see_this ) { //~ 1$ - shooter, 2$ - target add_msg(_("%1$s shoots %2$s."), source->disp_name().c_str(), disp_name().c_str()); } } } check_dead_state(); attack.hit_critter = this; attack.missed_by = goodhit; }
body_part Creature::select_body_part(Creature *source, int hit_roll) const { // Get size difference (-1,0,1); int szdif = source->get_size() - get_size(); if(szdif < -1) { szdif = -1; } else if (szdif > 1) { szdif = 1; } add_msg( m_debug, "hit roll = %d", hit_roll); add_msg( m_debug, "source size = %d", source->get_size() ); add_msg( m_debug, "target size = %d", get_size() ); add_msg( m_debug, "difference = %d", szdif ); std::map<body_part, double> hit_weights = default_hit_weights[szdif]; // If the target is on the ground, even small/tiny creatures may target eyes/head. Also increases chances of larger creatures. // Any hit modifiers to locations should go here. (Tags, attack style, etc) if(is_on_ground()) { hit_weights[bp_eyes] += 1; hit_weights[bp_head] += 5; } //Adjust based on hit roll: Eyes, Head & Torso get higher, while Arms and Legs get lower. //This should eventually be replaced with targeted attacks and this being miss chances. // pow() is unstable at 0, so don't apply any changes. if( hit_roll != 0 ) { hit_weights[bp_eyes] *= std::pow(hit_roll, 1.15); hit_weights[bp_head] *= std::pow(hit_roll, 1.35); hit_weights[bp_torso] *= std::pow(hit_roll, 1); hit_weights[bp_arm_l] *= std::pow(hit_roll, 0.95); hit_weights[bp_arm_r] *= std::pow(hit_roll, 0.95); hit_weights[bp_leg_l] *= std::pow(hit_roll, 0.975); hit_weights[bp_leg_r] *= std::pow(hit_roll, 0.975); } // Debug for seeing weights. add_msg( m_debug, "eyes = %f", hit_weights.at( bp_eyes ) ); add_msg( m_debug, "head = %f", hit_weights.at( bp_head ) ); add_msg( m_debug, "torso = %f", hit_weights.at( bp_torso ) ); add_msg( m_debug, "arm_l = %f", hit_weights.at( bp_arm_l ) ); add_msg( m_debug, "arm_r = %f", hit_weights.at( bp_arm_r ) ); add_msg( m_debug, "leg_l = %f", hit_weights.at( bp_leg_l ) ); add_msg( m_debug, "leg_r = %f", hit_weights.at( bp_leg_r ) ); double totalWeight = 0; for( const auto &hit_weight : hit_weights ) { totalWeight += hit_weight.second; } double roll = rng_float(0, totalWeight); body_part selected_part = bp_torso; for( const auto &hit_candidate : hit_weights) { roll -= hit_candidate.second; if(roll <= 0) { selected_part = hit_candidate.first; break; } } add_msg( m_debug, "selected part: %s", body_part_name(selected_part).c_str() ); return selected_part; }
static void on_receive_block(struct r3964_info *pInfo) { unsigned int length; struct r3964_client_info *pClient; struct r3964_block_header *pBlock; length=pInfo->rx_position; /* compare byte checksum characters: */ if(pInfo->flags & R3964_BCC) { if(pInfo->bcc!=pInfo->last_rx) { TRACE_PE("checksum error - got %x but expected %x", pInfo->last_rx, pInfo->bcc); pInfo->flags |= R3964_CHECKSUM; } } /* check for errors (parity, overrun,...): */ if(pInfo->flags & R3964_ERROR) { TRACE_PE("on_receive_block - transmission failed error %x", pInfo->flags & R3964_ERROR); put_char(pInfo, NAK); flush(pInfo); if(pInfo->nRetry<R3964_MAX_RETRIES) { pInfo->state=R3964_WAIT_FOR_RX_REPEAT; pInfo->nRetry++; mod_timer(&pInfo->tmr, jiffies + R3964_TO_RX_PANIC); } else { TRACE_PE("on_receive_block - failed after max retries"); pInfo->state=R3964_IDLE; } return; } /* received block; submit DLE: */ put_char(pInfo, DLE); flush(pInfo); del_timer_sync(&pInfo->tmr); TRACE_PS(" rx success: got %d chars", length); /* prepare struct r3964_block_header: */ pBlock = kmalloc(length+sizeof(struct r3964_block_header), GFP_KERNEL); TRACE_M("on_receive_block - kmalloc %x",(int)pBlock); if(pBlock==NULL) return; pBlock->length = length; pBlock->data = ((unsigned char*)pBlock)+sizeof(struct r3964_block_header); pBlock->locks = 0; pBlock->next = NULL; pBlock->owner = NULL; memcpy(pBlock->data, pInfo->rx_buf, length); /* queue block into rx_queue: */ add_rx_queue(pInfo, pBlock); /* notify attached client processes: */ for(pClient=pInfo->firstClient; pClient; pClient=pClient->next) { if(pClient->sig_flags & R3964_SIG_DATA) { add_msg(pClient, R3964_MSG_DATA, length, R3964_OK, pBlock); } } wake_up_interruptible (&pInfo->read_wait); pInfo->state = R3964_IDLE; trigger_transmit(pInfo); }
// 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; } // 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)) { 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)) { add_msg(m_warning, _("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()) { 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 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 (g->is_in_sunlight(posx(), posy()) && has_flag(MF_SUNDEATH)) { 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)) && !has_effect("pacified") ) { 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 (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 (auto &i : g->active_npc) { if (plans.back().x == i->posx && plans.back().y == i->posy) { current_attitude = attitude(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) || (has_flag(MF_BASHES) && g->m.bash_rating(bash_skill(), plans[0].x, plans[0].y) > 0))){ // 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 event::actualize() { switch( type ) { case EVENT_HELP: debugmsg("Currently disabled while NPC and monster factions are being rewritten."); break; case EVENT_ROBOT_ATTACK: { const auto u_pos = g->u.global_sm_location(); if (rl_dist(u_pos, map_point) <= 4) { const mtype_id& robot_type = one_in( 2 ) ? mon_copbot : mon_riotbot; g->u.add_memorial_log( pgettext("memorial_male", "Became wanted by the police!"), pgettext("memorial_female", "Became wanted by the police!")); int robx = (u_pos.x > map_point.x ? 0 - SEEX * 2 : SEEX * 4); int roby = (u_pos.y > map_point.y ? 0 - SEEY * 2 : SEEY * 4); g->summon_mon(robot_type, tripoint(robx, roby, g->u.posz())); } } break; case EVENT_SPAWN_WYRMS: { if (g->get_levz() >= 0) { return; } g->u.add_memorial_log(pgettext("memorial_male", "Drew the attention of more dark wyrms!"), pgettext("memorial_female", "Drew the attention of more dark wyrms!")); int num_wyrms = rng(1, 4); for (int i = 0; i < num_wyrms; i++) { int tries = 0; tripoint monp = g->u.pos(); do { monp.x = rng(0, SEEX * MAPSIZE); monp.y = rng(0, SEEY * MAPSIZE); tries++; } while (tries < 10 && !g->is_empty(monp) && rl_dist(g->u.pos(), monp) <= 2); if (tries < 10) { g->m.ter_set(monp, t_rock_floor); g->summon_mon(mon_dark_wyrm, monp); } } // You could drop the flag, you know. if (g->u.has_amount("petrified_eye", 1)) { sounds::sound(g->u.pos(), 60, ""); if (!g->u.is_deaf()) { add_msg(_("The eye you're carrying lets out a tortured scream!")); g->u.add_morale(MORALE_SCREAM, -15, 0, 300, 5); } } if (!one_in(25)) { // They just keep coming! g->add_event(EVENT_SPAWN_WYRMS, int(calendar::turn) + rng(15, 25)); } } break; case EVENT_AMIGARA: { g->u.add_memorial_log(pgettext("memorial_male", "Angered a group of amigara horrors!"), pgettext("memorial_female", "Angered a group of amigara horrors!")); int num_horrors = rng(3, 5); int faultx = -1, faulty = -1; bool horizontal = false; for (int x = 0; x < SEEX * MAPSIZE && faultx == -1; x++) { for (int y = 0; y < SEEY * MAPSIZE && faulty == -1; y++) { if (g->m.ter(x, y) == t_fault) { faultx = x; faulty = y; horizontal = (g->m.ter(x - 1, y) == t_fault || g->m.ter(x + 1, y) == t_fault); } } } for (int i = 0; i < num_horrors; i++) { int tries = 0; int monx = -1, mony = -1; do { if (horizontal) { monx = rng(faultx, faultx + 2 * SEEX - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(monx, faulty + n) == t_rock_floor) { mony = faulty + n; } } } else { // Vertical fault mony = rng(faulty, faulty + 2 * SEEY - 8); for (int n = -1; n <= 1; n++) { if (g->m.ter(faultx + n, mony) == t_rock_floor) { monx = faultx + n; } } } tries++; } while ((monx == -1 || mony == -1 || !g->is_empty({monx, mony, g->u.posz()})) && tries < 10); if (tries < 10) { g->summon_mon(mon_amigara_horror, tripoint(monx, mony, g->u.posz())); } } } break; case EVENT_ROOTS_DIE: g->u.add_memorial_log(pgettext("memorial_male", "Destroyed a triffid grove."), pgettext("memorial_female", "Destroyed a triffid grove.")); for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_root_wall && one_in(3)) g->m.ter_set(x, y, t_underbrush); } } break; case EVENT_TEMPLE_OPEN: { g->u.add_memorial_log(pgettext("memorial_male", "Opened a strange temple."), pgettext("memorial_female", "Opened a strange temple.")); bool saw_grate = false; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_grate) { g->m.ter_set(x, y, t_stairs_down); if (!saw_grate && g->u.sees(x, y)) saw_grate = true; } } } if (saw_grate) add_msg(_("The nearby grates open to reveal a staircase!")); } break; case EVENT_TEMPLE_FLOOD: { bool flooded = false; ter_id flood_buf[SEEX*MAPSIZE][SEEY*MAPSIZE]; for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) flood_buf[x][y] = g->m.ter(x, y); } for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) { if (g->m.ter(x, y) == t_water_sh) { bool deepen = false; for (int wx = x - 1; wx <= x + 1 && !deepen; wx++) { for (int wy = y - 1; wy <= y + 1 && !deepen; wy++) { if (g->m.ter(wx, wy) == t_water_dp) deepen = true; } } if (deepen) { flood_buf[x][y] = t_water_dp; flooded = true; } } else if (g->m.ter(x, y) == t_rock_floor) { bool flood = false; for (int wx = x - 1; wx <= x + 1 && !flood; wx++) { for (int wy = y - 1; wy <= y + 1 && !flood; wy++) { if (g->m.ter(wx, wy) == t_water_dp || g->m.ter(wx, wy) == t_water_sh) flood = true; } } if (flood) { flood_buf[x][y] = t_water_sh; flooded = true; } } } } if (!flooded) return; // We finished flooding the entire chamber! // Check if we should print a message if (flood_buf[g->u.posx()][g->u.posy()] != g->m.ter(g->u.posx(), g->u.posy())) { if (flood_buf[g->u.posx()][g->u.posy()] == t_water_sh) { add_msg(m_warning, _("Water quickly floods up to your knees.")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached knees."), pgettext("memorial_female", "Water level reached knees.")); } else { // Must be deep water! add_msg(m_warning, _("Water fills nearly to the ceiling!")); g->u.add_memorial_log(pgettext("memorial_male", "Water level reached the ceiling."), pgettext("memorial_female", "Water level reached the ceiling.")); g->plswim(g->u.pos()); } } // flood_buf is filled with correct tiles; now copy them back to g->m for (int x = 0; x < SEEX * MAPSIZE; x++) { for (int y = 0; y < SEEY * MAPSIZE; y++) g->m.ter_set(x, y, flood_buf[x][y]); } g->add_event(EVENT_TEMPLE_FLOOD, int(calendar::turn) + rng(2, 3)); } break; case EVENT_TEMPLE_SPAWN: { static const std::array<mtype_id, 4> temple_monsters = { { mon_sewer_snake, mon_dermatik, mon_spider_widow_giant, mon_spider_cellar_giant } }; const mtype_id &montype = random_entry( temple_monsters ); int tries = 0, x, y; do { x = rng(g->u.posx() - 5, g->u.posx() + 5); y = rng(g->u.posy() - 5, g->u.posy() + 5); tries++; } while (tries < 20 && !g->is_empty({x, y, g->u.posz()}) && rl_dist(x, y, g->u.posx(), g->u.posy()) <= 2); if (tries < 20) { g->summon_mon(montype, tripoint(x, y, g->u.posz())); } } break; default: break; // Nothing happens for other events } }
int monster::move_to(int x, int y, bool force) { // Make sure that we can move there, unless force is true. if(!force) if(!g->is_empty(x, y) || !can_move_to(x, y)) { return 0; } if (has_effect("beartrap") || has_effect("tied")) { moves = 0; return 0; } if (!plans.empty()) { plans.erase(plans.begin()); } if (!force) { moves -= calc_movecost(posx(), posy(), x, y); } //Check for moving into/out of water bool was_water = g->m.is_divable(posx(), posy()); bool will_be_water = g->m.is_divable(x, y); if(was_water && !will_be_water && g->u_see(x, y)) { //Use more dramatic messages for swimming monsters add_msg(m_warning, _("A %s %s from the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("leaps") : _("emerges"), g->m.tername(posx(), posy()).c_str()); } else if(!was_water && will_be_water && g->u_see(x, y)) { add_msg(m_warning, _("A %s %s into the %s!"), name().c_str(), has_flag(MF_SWIMS) || has_flag(MF_AQUATIC) ? _("dives") : _("sinks"), g->m.tername(x, y).c_str()); } setpos(x, y); footsteps(x, y); if(is_hallucination()) { //Hallucinations don't do any of the stuff after this point return 1; } if (type->size != MS_TINY && g->m.has_flag("SHARP", posx(), posy()) && !one_in(4)) { apply_damage( nullptr, bp_torso, rng( 2, 3 ) ); } if (type->size != MS_TINY && g->m.has_flag("ROUGH", posx(), posy()) && one_in(6)) { apply_damage( nullptr, bp_torso, rng( 1, 2 ) ); } if (g->m.has_flag("UNSTABLE", x, y)) { add_effect("bouldering", 1, 1, true); } else if (has_effect("bouldering")) { remove_effect("bouldering"); } if (!digging() && !has_flag(MF_FLIES) && g->m.tr_at(posx(), posy()) != tr_null) { // Monster stepped on a trap! trap* tr = traplist[g->m.tr_at(posx(), posy())]; if (dice(3, type->sk_dodge + 1) < dice(3, tr->get_avoidance())) { tr->trigger(this, posx(), posy()); } } // 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 (has_flag(MF_VERMIN)) { factor *= 100; } if (one_in(factor)) { g->m.ter_set(posx(), posy(), t_dirtmound); } } // Acid trail monsters leave... a trail of acid if (has_flag(MF_ACIDTRAIL)){ g->m.add_field(posx(), posy(), fd_acid, 3); } if (has_flag(MF_SLUDGETRAIL)) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { const int fstr = 3 - (abs(dx) + abs(dy)); if (fstr >= 2) { g->m.add_field(posx() + dx, posy() + dy, fd_sludge, fstr); } } } } if (has_flag(MF_LEAKSGAS)){ if (one_in(6)){ g->m.add_field(posx() + rng(-1,1), posy() + rng(-1, 1), fd_toxic_gas, 3); } } return 1; }
void mission_start::place_npc_software(mission *miss) { npc* dev = g->find_npc(miss->npc_id); if (dev == NULL) { debugmsg("Couldn't find NPC! %d", miss->npc_id); return; } g->u.i_add( item("usb_drive", 0) ); add_msg(_("%s gave you a USB drive."), dev->name.c_str()); std::string type = "house"; switch (dev->myclass) { case NC_HACKER: miss->item_id = "software_hacking"; break; case NC_DOCTOR: miss->item_id = "software_medical"; type = "s_pharm"; miss->follow_up = MISSION_GET_ZOMBIE_BLOOD_ANAL; break; case NC_SCIENTIST: miss->item_id = "software_math"; break; default: miss->item_id = "software_useless"; } int dist = 0; point place; if (type == "house") { place = random_house_in_closest_city(); } else { place = overmap_buffer.find_closest(dev->global_omt_location(), type, dist, false); } miss->target = place; overmap_buffer.reveal(place, 6, g->get_levz()); tinymap compmap; compmap.load(place.x * 2, place.y * 2, g->get_levz(), false); point comppoint; oter_id oter = overmap_buffer.ter(place.x, place.y, 0); if( is_ot_type("house", oter) || is_ot_type("s_pharm", oter) || oter == "" ) { std::vector<point> valid; for (int x = 0; x < SEEX * 2; x++) { for (int y = 0; y < SEEY * 2; y++) { if (compmap.ter(x, y) == t_floor && compmap.furn(x, y) == f_null) { bool okay = false; int wall = 0; for (int x2 = x - 1; x2 <= x + 1 && !okay; x2++) { for (int y2 = y - 1; y2 <= y + 1 && !okay; y2++) { if (compmap.furn(x2, y2) == f_bed || compmap.furn(x2, y2) == f_dresser) { okay = true; valid.push_back( point(x, y) ); } if ( compmap.has_flag_ter("WALL", x2, y2) ) { wall++; } } } if ( wall == 5 ) { if ( compmap.is_last_ter_wall( true, x, y, SEEX * 2, SEEY * 2, NORTH ) && compmap.is_last_ter_wall( true, x, y, SEEX * 2, SEEY * 2, SOUTH ) && compmap.is_last_ter_wall( true, x, y, SEEX * 2, SEEY * 2, WEST ) && compmap.is_last_ter_wall( true, x, y, SEEX * 2, SEEY * 2, EAST ) ) { valid.push_back( point(x, y) ); } } } } } if (valid.empty()) { comppoint = point( rng(6, SEEX * 2 - 7), rng(6, SEEY * 2 - 7) ); } else { comppoint = valid[rng(0, valid.size() - 1)]; } } compmap.ter_set(comppoint.x, comppoint.y, t_console); computer *tmpcomp = compmap.add_computer( tripoint( comppoint, g->get_levz() ), string_format(_("%s's Terminal"), dev->name.c_str()), 0); tmpcomp->mission_id = miss->uid; tmpcomp->add_option(_("Download Software"), COMPACT_DOWNLOAD_SOFTWARE, 0); compmap.save(); }
edible_rating player::can_eat( const item &food, bool interactive, bool force ) const { if( is_npc() || force ) { // Just to be sure interactive = false; } const std::string &itname = food.tname(); // Helper to avoid ton of `if( interactive )` // Prints if interactive is true, does nothing otherwise const auto maybe_print = [interactive, &itname] ( game_message_type type, const char *str ) { if( interactive ) { add_msg( type, str, itname.c_str() ); } }; // As above, but for queries // Asks if interactive and not force // Always true if force // Never true otherwise const auto maybe_query = [force, interactive, &itname, this]( const char *str ) { if( force ) { return true; } else if( !interactive ) { return false; } return query_yn( str, itname.c_str() ); }; const auto comest = food.type->comestible.get(); if( comest == nullptr ) { maybe_print( m_info, _( "That doesn't look edible." ) ); return INEDIBLE; } if( comest->tool != "null" ) { bool has = has_amount( comest->tool, 1 ); if( item::count_by_charges( comest->tool ) ) { has = has_charges( comest->tool, 1 ); } if( !has ) { if( interactive ) { add_msg_if_player( m_info, _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ); } return NO_TOOL; } } if( is_underwater() ) { maybe_print( m_info, _( "You can't do that while underwater." ) ); return INEDIBLE; } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( "M_DEPENDENT" ) && food.type->id != "mycus_fruit" ) { maybe_print( m_info, _( "We can't eat that. It's not right for us." ) ); return INEDIBLE_MUTATION; } const bool drinkable = comest->comesttype == "DRINK" && !food.has_flag( "USE_EAT_VERB" ); // Here's why PROBOSCIS is such a negative trait. if( has_trait( "PROBOSCIS" ) && !drinkable ) { maybe_print( m_info, _( "Ugh, you can't drink that!" ) ); return INEDIBLE_MUTATION; } int capacity = stomach_capacity(); // TODO: Move this cache to a structure and pass it around // to speed up checking entire inventory for edibles const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); const bool eathealth = has_trait( "EATHEALTH" ); const bool slimespawner = has_trait( "SLIMESPAWNER" ); const int nutr = nutrition_for( food.type ); const int quench = comest->quench; bool spoiled = food.rotten(); const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; const bool overeating = get_hunger() < 0 && nutr >= 5 && !gourmand && !eathealth && !slimespawner && !hibernate; if( interactive && hibernate && ( get_hunger() >= -60 && get_thirst() >= -60 ) && ( temp_hunger < -60 || temp_thirst < -60 ) ) { if( !maybe_query( _( "You're adequately fueled. Prepare for hibernation?" ) ) ) { return TOO_FULL; } } const bool carnivore = has_trait( "CARNIVORE" ); if( carnivore && nutr > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { maybe_print( m_info, _( "Eww. Inedible plant stuff!" ) ); return INEDIBLE_MUTATION; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! maybe_print( m_info, _( "The thought of eating that makes you feel sick. You decide not to." ) ); return INEDIBLE_MUTATION; } if( food.has_flag( "CANNIBALISM" ) ) { if( !has_trait_flag( "CANNIBAL" ) && !maybe_query( _( "The thought of eating that makes you feel sick. Really do it?" ) ) ) { return CANNIBALISM; } } if( is_allergic( food ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } if( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } const bool saprophage = has_trait( "SAPROPHAGE" ); // The item is solid food const bool chew = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( spoiled ) { if( !saprophage && !has_trait( "SAPROVORE" ) && !maybe_query( _( "This %s smells awful! Eat it?" ) ) ) { return ROTTEN; } } else if( saprophage && chew && !food.has_flag( "FERTILIZER" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hardcoding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. maybe_print( m_info, _( "It's too fresh, let it age a little first." ) ); return ROTTEN; } // Print at most one of those bool overfull = false; if( overeating ) { overfull = !maybe_query( _( "You're full. Force yourself to eat?" ) ); } else if( ( ( nutr > 0 && temp_hunger < capacity ) || ( comest->quench > 0 && temp_thirst < capacity ) ) && !eathealth && !slimespawner ) { overfull = !maybe_query( _( "You will not be able to finish it all. Consume it?" ) ); } if( overfull ) { return TOO_FULL; } // All checks ended, it's edible (or we're pretending it is) return EDIBLE; }