/* * load player from ginormous json blob */ void player::deserialize(JsonIn &jsin) { JsonObject data = jsin.get_object(); JsonArray parray; json_load_common_variables( data ); std::string prof_ident="(null)"; if ( data.read("profession",prof_ident) && profession::exists(prof_ident) ) { prof = profession::prof(prof_ident); } else { debugmsg("Tried to use non-existent profession '%s'", prof_ident.c_str()); } data.read("activity",activity); data.read("backlog",backlog); data.read("driving_recoil",driving_recoil); data.read("in_vehicle",in_vehicle); data.read("controlling_vehicle",controlling_vehicle); data.read("grab_point", grab_point); std::string grab_typestr="OBJECT_NONE"; if( grab_point.x != 0 || grab_point.y != 0 ) { grab_typestr = "OBJECT_VEHICLE"; data.read( "grab_type", grab_typestr); } if ( obj_type_id.find(grab_typestr) != obj_type_id.end() ) { grab_type = (object_type)obj_type_id[grab_typestr]; } data.read( "blocks_left", num_blocks); data.read( "focus_pool", focus_pool); data.read( "style_selected", style_selected ); data.read( "health", health ); data.read( "mutations", my_mutations ); set_highest_cat_level(); drench_mut_calc(); parray = data.get_array("temp_cur"); if ( parray.size() == num_bp ) { for(int i=0; i < num_bp; i++) { temp_cur[i]=parray.get_int(i); } } else { debugmsg("Error, incompatible temp_cur in save file %s",parray.str().c_str()); } parray = data.get_array("temp_conv"); if ( parray.size() == num_bp ) { for(int i=0; i < num_bp; i++) { temp_conv[i]=parray.get_int(i); } } else { debugmsg("Error, incompatible temp_conv in save file %s",parray.str().c_str()); } parray = data.get_array("frostbite_timer"); if ( parray.size() == num_bp ) { for(int i=0; i < num_bp; i++) { frostbite_timer[i]=parray.get_int(i); } } else { debugmsg("Error, incompatible frostbite_timer in save file %s",parray.str().c_str()); } parray = data.get_array("learned_recipes"); if ( !parray.empty() ) { learned_recipes.clear(); std::string pstr=""; while ( parray.has_more() ) { if ( parray.read_next(pstr) ) { learned_recipes[ pstr ] = recipe_by_name( pstr ); } } } data.read("morale", morale); data.read( "active_mission", active_mission ); data.read( "active_missions", active_missions ); data.read( "failed_missions", failed_missions ); data.read( "completed_missions", completed_missions ); stats & pstats = *lifetime_stats(); data.read("player_stats",pstats); inv.clear(); if ( data.has_member("inv") ) { JsonIn* jip = data.get_raw("inv"); inv.json_load_items( *jip ); } if ( data.has_member("invcache") ) { JsonIn* jip = data.get_raw("invcache"); inv.json_load_invcache( *jip ); } worn.clear(); data.read("worn", worn); weapon.contents.clear(); data.read("weapon", weapon); }
void player::fire_gun(int tarx, int tary, bool burst) { item ammotmp; item* gunmod = weapon.active_gunmod(); it_ammo *curammo = NULL; item *used_weapon = NULL; if (weapon.has_flag("CHARGE")) { // It's a charger gun, so make up a type // Charges maxes out at 8. int charges = weapon.num_charges(); it_ammo *tmpammo = dynamic_cast<it_ammo*>(itypes["charge_shot"]); tmpammo->damage = charges * charges; tmpammo->pierce = (charges >= 4 ? (charges - 3) * 2.5 : 0); if (charges <= 4) tmpammo->dispersion = 14 - charges * 2; else // 5, 12, 21, 32 tmpammo->dispersion = charges * (charges - 4); tmpammo->recoil = tmpammo->dispersion * .8; tmpammo->ammo_effects.clear(); // Reset effects. if (charges == 8) { tmpammo->ammo_effects.insert("EXPLOSIVE_BIG"); } else if (charges >= 6) { tmpammo->ammo_effects.insert("EXPLOSIVE"); } if (charges >= 5){ tmpammo->ammo_effects.insert("FLAME"); } else if (charges >= 4) { tmpammo->ammo_effects.insert("INCENDIARY"); } if (gunmod != NULL) { // TODO: range calculation in case of active gunmod. used_weapon = gunmod; } else { used_weapon = &weapon; } curammo = tmpammo; used_weapon->curammo = tmpammo; } else if (gunmod != NULL) { used_weapon = gunmod; curammo = used_weapon->curammo; } else {// Just a normal gun. If we're here, we know curammo is valid. curammo = weapon.curammo; used_weapon = &weapon; } ammotmp = item(curammo, 0); ammotmp.charges = 1; if (!used_weapon->is_gun() && !used_weapon->is_gunmod()) { debugmsg("%s tried to fire a non-gun (%s).", name.c_str(), used_weapon->tname().c_str()); return; } projectile proj; // damage will be set later proj.aoe_size = 0; proj.ammo = curammo; proj.speed = 1000; std::set<std::string> *curammo_effects = &curammo->ammo_effects; if(gunmod == NULL){ std::set<std::string> *gun_effects = &dynamic_cast<it_gun*>(used_weapon->type)->ammo_effects; proj.proj_effects.insert(gun_effects->begin(),gun_effects->end()); } proj.proj_effects.insert(curammo_effects->begin(),curammo_effects->end()); proj.wide = (curammo->phase == LIQUID || proj.proj_effects.count("SHOT") || proj.proj_effects.count("BOUNCE")); proj.drops = (curammo->type == "bolt" || curammo->type == "arrow"); //int x = xpos(), y = ypos(); // Have to use the gun, gunmods don't have a type it_gun* firing = dynamic_cast<it_gun*>(weapon.type); if (has_trait("TRIGGERHAPPY") && one_in(30)) burst = true; if (burst && used_weapon->burst_size() < 2) burst = false; // Can't burst fire a semi-auto // Use different amounts of time depending on the type of gun and our skill if (!proj.proj_effects.count("BOUNCE")) { moves -= time_to_fire(*this, firing); } // Decide how many shots to fire int num_shots = 1; if (burst) num_shots = used_weapon->burst_size(); if (num_shots > used_weapon->num_charges() && !used_weapon->has_flag("CHARGE") && !used_weapon->has_flag("NO_AMMO")) num_shots = used_weapon->num_charges(); if (num_shots == 0) debugmsg("game::fire() - num_shots = 0!"); int ups_drain = 0; int adv_ups_drain = 0; if (weapon.has_flag("USE_UPS")) { ups_drain = 5; adv_ups_drain = 3; } else if (weapon.has_flag("USE_UPS_20")) { ups_drain = 20; adv_ups_drain = 12; } else if (weapon.has_flag("USE_UPS_40")) { ups_drain = 40; adv_ups_drain = 24; } // cap our maximum burst size by the amount of UPS power left if (ups_drain > 0 || adv_ups_drain > 0) while (!(has_charges("UPS_off", ups_drain*num_shots) || has_charges("UPS_on", ups_drain*num_shots) || has_charges("adv_UPS_off", adv_ups_drain*num_shots) || has_charges("adv_UPS_on", adv_ups_drain*num_shots))) { num_shots--; } const bool debug_retarget = false; // this will inevitably be needed //const bool wildly_spraying = false; // stub for now. later, rng based on stress/skill/etc at the start, int weaponrange = weapon.range(); // this is expensive, let's cache. todo: figure out if we need weapon.range(&p); for (int curshot = 0; curshot < num_shots; curshot++) { // Burst-fire weapons allow us to pick a new target after killing the first int zid = g->mon_at(tarx, tary); if ( curshot > 0 && (zid == -1 || g->zombie(zid).hp <= 0) ) { std::vector<point> new_targets; new_targets.clear(); if ( debug_retarget == true ) { mvprintz(curshot,5,c_red,"[%d] %s: retarget: mon_at(%d,%d)",curshot,name.c_str(),tarx,tary); if(zid == -1) { printz(c_red, " = -1"); } else { printz(c_red, ".hp=%d", g->zombie(zid).hp); } } for (unsigned long int i = 0; i < g->num_zombies(); i++) { monster &z = g->zombie(i); int dummy; // search for monsters in radius if (rl_dist(z.posx(), z.posy(), tarx, tary) <= std::min(2 + skillLevel("gun"), weaponrange) && rl_dist(xpos(),ypos(),z.xpos(),z.ypos()) <= weaponrange && sees(&z, dummy) ) { if (!z.is_dead_state()) new_targets.push_back(point(z.xpos(), z.ypos())); // oh you're not dead and I don't like you. Hello! } } if ( new_targets.empty() == false ) { /* new victim! or last victim moved */ int target_picked = rng(0, new_targets.size() - 1); /* 1 victim list unless wildly spraying */ tarx = new_targets[target_picked].x; tary = new_targets[target_picked].y; zid = g->mon_at(tarx, tary); /* debug */ if (debug_retarget) printz(c_ltgreen, " NEW:(%d:%d,%d) %d,%d (%s)[%d] hp: %d", target_picked, new_targets[target_picked].x, new_targets[target_picked].y, tarx, tary, g->zombie(zid).name().c_str(), zid, g->zombie(zid).hp); } else if ( ( !has_trait("TRIGGERHAPPY") || /* double ta TRIPLE TAP! wait, no... */ one_in(3) /* on second though...everyone double-taps at times. */ ) && ( skillLevel("gun") >= 7 || /* unless trained */ one_in(7 - skillLevel("gun")) /* ...sometimes */ ) ) { return; // No targets, so return } else if (debug_retarget) { printz(c_red, " new targets.empty()!"); } } else if (debug_retarget) { const int zid = g->mon_at(tarx, tary); mvprintz(curshot,5,c_red,"[%d] %s: target == mon_at(%d,%d)[%d] %s hp %d",curshot, name.c_str(), tarx ,tary, zid, g->zombie(zid).name().c_str(), g->zombie(zid).hp); } // Drop a shell casing if appropriate. itype_id casing_type = curammo->casing; if (casing_type != "NULL" && !casing_type.empty()) { item casing; casing.make(itypes[casing_type]); // Casing needs a charges of 1 to stack properly with other casings. casing.charges = 1; if( used_weapon->has_gunmod("brass_catcher") != -1 ) { i_add( casing ); } else { int x = 0; int y = 0; int count = 0; do { x = xpos() - 1 + rng(0, 2); y = ypos() - 1 + rng(0, 2); count++; // Try not to drop the casing on a wall if at all possible. } while( g->m.move_cost( x, y ) == 0 && count < 10 ); g->m.add_item_or_charges(x, y, casing); } } // Use up a round (or 100) if (used_weapon->has_flag("FIRE_100")) { used_weapon->charges -= 100; } else if (used_weapon->has_flag("FIRE_50")) { used_weapon->charges -= 50; } else if (used_weapon->has_flag("CHARGE")) { used_weapon->active = false; used_weapon->charges = 0; } else if (!used_weapon->has_flag("NO_AMMO")) { used_weapon->charges--; } // Drain UPS power if (has_charges("adv_UPS_off", adv_ups_drain)) { use_charges("adv_UPS_off", adv_ups_drain); } else if (has_charges("adv_UPS_on", adv_ups_drain)) { use_charges("adv_UPS_on", adv_ups_drain); } else if (has_charges("UPS_off", ups_drain)) { use_charges("UPS_off", ups_drain); } else if (has_charges("UPS_on", ups_drain)) { use_charges("UPS_on", ups_drain); } if (firing->skill_used != Skill::skill("archery") && firing->skill_used != Skill::skill("throw")) { // Current guns have a durability between 5 and 9. // Misfire chance is between 1/64 and 1/1024. if (is_underwater() && !weapon.has_flag("WATERPROOF_GUN") && one_in(firing->durability)) { g->add_msg_player_or_npc(this, _("Your weapon misfires with a wet click!"), _("<npcname>'s weapon misfires with a wet click!") ); return; } else if (one_in(2 << firing->durability)) { g->add_msg_player_or_npc(this, _("Your weapon misfires!"), _("<npcname>'s weapon misfires!") ); return; } } make_gun_sound_effect(*this, burst, used_weapon); double total_dispersion = get_weapon_dispersion(used_weapon); //debugmsg("%f",total_dispersion); int range = rl_dist(xpos(), ypos(), tarx, tary); // penalties for point-blank if (range < (firing->volume/3) && firing->ammo != "shot") total_dispersion *= double(firing->volume/3) / double(range); // rifle has less range penalty past LONG_RANGE if (firing->skill_used == Skill::skill("rifle") && range > LONG_RANGE) total_dispersion *= 1 - 0.4*double(range - LONG_RANGE) / double(range); if (curshot > 0) { if (recoil_add(*this) % 2 == 1) { recoil++; } recoil += recoil_add(*this) / 2; } else { recoil += recoil_add(*this); } int mtarx = tarx; int mtary = tary; int adjusted_damage = used_weapon->gun_damage(); proj.impact = damage_instance::physical(0,adjusted_damage,0); double missed_by = projectile_attack(proj, mtarx, mtary, total_dispersion); if (missed_by <= .1) { // TODO: check head existence for headshot practice(g->turn, firing->skill_used, 5); lifetime_stats()->headshots++; } else if (missed_by <= .2) { practice(g->turn, firing->skill_used, 3); } else if (missed_by <= .4) { practice(g->turn, firing->skill_used, 2); } else if (missed_by <= .6) { practice(g->turn, firing->skill_used, 1); } } if (used_weapon->num_charges() == 0) { used_weapon->curammo = NULL; } }