void map::add_item(int x, int y, item new_item) { if (!inbounds(x, y)) return; if (has_flag(noitem, x, y) || i_at(x, y).size() >= 24) {// Too many items there std::vector<point> okay; for (int i = x - 1; i <= x + 1; i++) { for (int j = y - 1; j <= y + 1; j++) { if (inbounds(i, j) && move_cost(i, j) > 0 && !has_flag(noitem, i, j) && i_at(i, j).size() < 24) okay.push_back(point(i, j)); } } if (okay.size() == 0) { for (int i = x - 2; i <= x + 2; i++) { for (int j = y - 2; j <= y + 2; j++) { if (inbounds(i, j) && move_cost(i, j) > 0 && !has_flag(noitem, i, j) && i_at(i, j).size()<24) okay.push_back(point(i, j)); } } } if (okay.size() == 0) // STILL? return; point choice = okay[rng(0, okay.size() - 1)]; add_item(choice.x, choice.y, new_item); return; } int nonant; cast_to_nonant(x, y, nonant); grid[nonant].itm[x][y].push_back(new_item); }
void map::process_active_items(game *g) { it_tool* tmp; iuse use; for (int i = 0; i < SEEX * 3; i++) { for (int j = 0; j < SEEY * 3; j++) { for (int n = 0; n < i_at(i, j).size(); n++) { if (i_at(i, j)[n].active) { tmp = dynamic_cast<it_tool*>(i_at(i, j)[n].type); (use.*tmp->use)(g, &(g->u), &i_at(i, j)[n], true); if (tmp->turns_per_charge > 0 && g->turn % tmp->turns_per_charge == 0) i_at(i, j)[n].charges--; if (i_at(i, j)[n].charges <= 0) { (use.*tmp->use)(g, &(g->u), &i_at(i, j)[n], false); if (tmp->revert_to == itm_null) { i_at(i, j).erase(i_at(i, j).begin() + n); n--; } else i_at(i, j)[n].type = g->itypes[tmp->revert_to]; } } } } } }
void map::debug() { mvprintw(0, 0, "MAP DEBUG"); getch(); for (int i = 0; i <= SEEX * 2; i++) { for (int j = 0; j <= SEEY * 2; j++) { if (i_at(i, j).size() > 0) { mvprintw(1, 0, "%d, %d: %d items", i, j, i_at(i, j).size()); mvprintw(2, 0, "%c, %d", i_at(i, j)[0].symbol(), i_at(i, j)[0].color()); getch(); } } } getch(); }
point map::find_item(item *it) { point ret; for (ret.x = 0; ret.x < SEEX * 3; ret.x++) { for (ret.y = 0; ret.y < SEEY * 3; ret.y++) { for (int i = 0; i < i_at(ret.x, ret.y).size(); i++) { if (it == &i_at(ret.x, ret.y)[i]) return ret; } } } ret.x = -1; ret.y = -1; return ret; }
// Why put this in a Big Switch? Why not let bionics have pointers to // functions, much like monsters and items? // // Well, because like diseases, which are also in a Big Switch, bionics don't // share functions.... void player::activate_bionic(int b, game *g) { bionic bio = my_bionics[b]; int power_cost = bionics[bio.id].power_cost; if (weapon.type->id == itm_bio_claws && bio.id == bio_claws) power_cost = 0; if (power_level < power_cost) { if (my_bionics[b].powered) { g->add_msg("Your %s powers down.", bionics[bio.id].name.c_str()); my_bionics[b].powered = false; } else g->add_msg("You cannot power your %s", bionics[bio.id].name.c_str()); return; } if (my_bionics[b].powered && my_bionics[b].charge > 0) { // Already-on units just lose a bit of charge my_bionics[b].charge--; } else { // Not-on units, or those with zero charge, have to pay the power cost if (bionics[bio.id].charge_time > 0) { my_bionics[b].powered = true; my_bionics[b].charge = bionics[bio.id].charge_time; } power_level -= power_cost; } std::string junk; std::vector<point> traj; std::vector<std::string> good; std::vector<std::string> bad; WINDOW* w; int dirx, diry, t, index; unsigned int l; item tmp_item; switch (bio.id) { case bio_painkiller: pkill += 6; pain -= 2; if (pkill > pain) pkill = pain; break; case bio_nanobots: healall(4); break; case bio_resonator: g->sound(posx, posy, 30, "VRRRRMP!"); for (int i = posx - 1; i <= posx + 1; i++) { for (int j = posy - 1; j <= posy + 1; j++) { g->m.bash(i, j, 40, junk); g->m.bash(i, j, 40, junk); // Multibash effect, so that doors &c will fall g->m.bash(i, j, 40, junk); if (g->m.is_destructable(i, j) && rng(1, 10) >= 4) g->m.ter(i, j) = t_rubble; } } break; case bio_time_freeze: moves += 100 * power_level; power_level = 0; g->add_msg("Your speed suddenly increases!"); if (one_in(3)) { g->add_msg("Your muscles tear with the strain."); hurt(g, bp_arms, 0, rng(5, 10)); hurt(g, bp_arms, 1, rng(5, 10)); hurt(g, bp_legs, 0, rng(7, 12)); hurt(g, bp_legs, 1, rng(7, 12)); hurt(g, bp_torso, 0, rng(5, 15)); } if (one_in(5)) add_disease(DI_TELEGLOW, rng(50, 400), g); break; case bio_teleport: g->teleport(); add_disease(DI_TELEGLOW, 300, g); break; // TODO: More stuff here (and bio_blood_filter) case bio_blood_anal: w = newwin(20, 40, 3, 10); wborder(w, LINE_XOXO, LINE_XOXO, LINE_OXOX, LINE_OXOX, LINE_OXXO, LINE_OOXX, LINE_XXOO, LINE_XOOX ); if (has_disease(DI_FUNGUS)) bad.push_back("Fungal Parasite"); if (has_disease(DI_DERMATIK)) bad.push_back("Insect Parasite"); if (has_disease(DI_POISON)) bad.push_back("Poison"); if (radiation > 0) bad.push_back("Irradiated"); if (has_disease(DI_PKILL1)) good.push_back("Minor Painkiller"); if (has_disease(DI_PKILL2)) good.push_back("Moderate Painkiller"); if (has_disease(DI_PKILL3)) good.push_back("Heavy Painkiller"); if (has_disease(DI_PKILL_L)) good.push_back("Slow-Release Painkiller"); if (has_disease(DI_DRUNK)) good.push_back("Alcohol"); if (has_disease(DI_CIG)) good.push_back("Nicotine"); if (has_disease(DI_HIGH)) good.push_back("Intoxicant: Other"); if (has_disease(DI_TOOK_PROZAC)) good.push_back("Prozac"); if (has_disease(DI_TOOK_FLUMED)) good.push_back("Antihistamines"); if (has_disease(DI_ADRENALINE)) good.push_back("Adrenaline Spike"); if (good.size() == 0 && bad.size() == 0) mvwprintz(w, 1, 1, c_white, "No effects."); else { for (unsigned int line = 1; line < 39 && line <= good.size() + bad.size(); line++) { if (line <= bad.size()) mvwprintz(w, line, 1, c_red, bad[line - 1].c_str()); else mvwprintz(w, line, 1, c_green, good[line - 1 - bad.size()].c_str()); } } wrefresh(w); refresh(); getch(); delwin(w); break; case bio_blood_filter: rem_disease(DI_FUNGUS); rem_disease(DI_POISON); rem_disease(DI_PKILL1); rem_disease(DI_PKILL2); rem_disease(DI_PKILL3); rem_disease(DI_PKILL_L); rem_disease(DI_DRUNK); rem_disease(DI_CIG); rem_disease(DI_HIGH); rem_disease(DI_TOOK_PROZAC); rem_disease(DI_TOOK_FLUMED); rem_disease(DI_ADRENALINE); break; case bio_evap: if (query_yn("Drink directly? Otherwise you will need a container.")) { tmp_item = item(g->itypes[itm_water], 0); thirst -= 50; if (has_trait(PF_GOURMAND) && thirst < -60) { g->add_msg("You can't finish it all!"); thirst = -60; } else if (!has_trait(PF_GOURMAND) && thirst < -20) { g->add_msg("You can't finish it all!"); thirst = -20; } } else { t = g->inv("Choose a container:"); if (i_at(t).type == 0) { g->add_msg("You don't have that item!"); power_level += bionics[bio_evap].power_cost; } else if (!i_at(t).is_container()) { g->add_msg("That %s isn't a container!", i_at(t).tname().c_str()); power_level += bionics[bio_evap].power_cost; } else { it_container *cont = dynamic_cast<it_container*>(i_at(t).type); if (i_at(t).volume_contained() + 1 > cont->contains) { g->add_msg("There's no space left in your %s.", i_at(t).tname().c_str()); power_level += bionics[bio_evap].power_cost; } else if (!(cont->flags & con_wtight)) { g->add_msg("Your %s isn't watertight!", i_at(t).tname().c_str()); power_level += bionics[bio_evap].power_cost; } else { g->add_msg("You pour water into your %s.", i_at(t).tname().c_str()); i_at(t).put_in(item(g->itypes[itm_water], 0)); } } } break; case bio_lighter: g->draw(); mvprintw(0, 0, "Torch in which direction?"); get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_lighter].power_cost; return; } dirx += posx; diry += posy; if (!g->m.add_field(g, dirx, diry, fd_fire, 1)) // Unsuccessful. g->add_msg("You can't light a fire there."); break; case bio_claws: if (weapon.type->id == itm_bio_claws) { g->add_msg("You withdraw your claws."); weapon = ret_null; } else if (weapon.type->id != 0) { g->add_msg("Your claws extend, forcing you to drop your %s.", weapon.tname().c_str()); g->m.add_item(posx, posy, weapon); weapon = item(g->itypes[itm_bio_claws], 0); weapon.invlet = '#'; } else { g->add_msg("Your claws extend!"); weapon = item(g->itypes[itm_bio_claws], 0); weapon.invlet = '#'; } break; case bio_blaster: tmp_item = weapon; weapon = item(g->itypes[itm_bio_blaster], 0); weapon.curammo = dynamic_cast<it_ammo*>(g->itypes[itm_bio_fusion]); weapon.charges = 1; g->refresh_all(); g->plfire(false); weapon = tmp_item; break; case bio_laser: tmp_item = weapon; weapon = item(g->itypes[itm_v29], 0); weapon.curammo = dynamic_cast<it_ammo*>(g->itypes[itm_laser_pack]); weapon.charges = 1; g->refresh_all(); g->plfire(false); weapon = tmp_item; break; case bio_emp: g->draw(); mvprintw(0, 0, "Fire EMP in which direction?"); get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_emp].power_cost; return; } dirx += posx; diry += posy; g->emp_blast(dirx, diry); break; case bio_hydraulics: g->add_msg("Your muscles hiss as hydraulic strength fills them!"); break; case bio_water_extractor: for (unsigned int i = 0; i < g->m.i_at(posx, posy).size(); i++) { item tmp = g->m.i_at(posx, posy)[i]; if (tmp.type->id == itm_corpse && query_yn("Extract water from the %s", tmp.tname().c_str())) { i = g->m.i_at(posx, posy).size() + 1; // Loop is finished t = g->inv("Choose a container:"); if (i_at(t).type == 0) { g->add_msg("You don't have that item!"); power_level += bionics[bio_water_extractor].power_cost; } else if (!i_at(t).is_container()) { g->add_msg("That %s isn't a container!", i_at(t).tname().c_str()); power_level += bionics[bio_water_extractor].power_cost; } else { it_container *cont = dynamic_cast<it_container*>(i_at(t).type); if (i_at(t).volume_contained() + 1 > cont->contains) { g->add_msg("There's no space left in your %s.", i_at(t).tname().c_str()); power_level += bionics[bio_water_extractor].power_cost; } else { g->add_msg("You pour water into your %s.", i_at(t).tname().c_str()); i_at(t).put_in(item(g->itypes[itm_water], 0)); } } } if (i == g->m.i_at(posx, posy).size() - 1) // We never chose a corpse power_level += bionics[bio_water_extractor].power_cost; } break; case bio_magnet: for (int i = posx - 10; i <= posx + 10; i++) { for (int j = posy - 10; j <= posy + 10; j++) { if (g->m.i_at(i, j).size() > 0) { if (g->m.sees(i, j, posx, posy, -1, t)) traj = line_to(i, j, posx, posy, t); else traj = line_to(i, j, posx, posy, 0); } traj.insert(traj.begin(), point(i, j)); for (unsigned int k = 0; k < g->m.i_at(i, j).size(); k++) { if (g->m.i_at(i, j)[k].made_of(IRON) || g->m.i_at(i, j)[k].made_of(STEEL)){ tmp_item = g->m.i_at(i, j)[k]; g->m.i_rem(i, j, k); for (l = 0; l < traj.size(); l++) { index = g->mon_at(traj[l].x, traj[l].y); if (index != -1) { if (g->z[index].hurt(tmp_item.weight() * 2)) g->kill_mon(index, true); g->m.add_item(traj[l].x, traj[l].y, tmp_item); l = traj.size() + 1; } else if (l > 0 && g->m.move_cost(traj[l].x, traj[l].y) == 0) { g->m.bash(traj[l].x, traj[l].y, tmp_item.weight() * 2, junk); g->sound(traj[l].x, traj[l].y, 12, junk); if (g->m.move_cost(traj[l].x, traj[l].y) == 0) { g->m.add_item(traj[l - 1].x, traj[l - 1].y, tmp_item); l = traj.size() + 1; } } } if (l == traj.size()) g->m.add_item(posx, posy, tmp_item); } } } } break; case bio_lockpick: g->draw(); mvprintw(0, 0, "Unlock in which direction?"); get_direction(g, dirx, diry, input()); if (dirx == -2) { g->add_msg("Invalid direction."); power_level += bionics[bio_lockpick].power_cost; return; } dirx += posx; diry += posy; if (g->m.ter(dirx, diry) == t_door_locked) { moves -= 40; g->add_msg("You unlock the door."); g->m.ter(dirx, diry) = t_door_c; } else g->add_msg("You can't unlock that %s.", g->m.tername(dirx, diry).c_str()); break; // Unused enums added for completeness. default: break; } }
void map::generate_lightmap() { memset(lm, 0, sizeof(lm)); memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: Profit! */ memset(light_source_buffer, 0, sizeof(light_source_buffer)); constexpr int dir_x[] = { 0, -1 , 1, 0 }; // [0] constexpr int dir_y[] = { -1, 0 , 0, 1 }; // [1][X][2] constexpr int dir_d[] = { 180, 270, 0, 90 }; // [3] const bool u_is_inside = !is_outside(g->u.posx(), g->u.posy()); const float natural_light = g->natural_light_level(); const float hl = natural_light / 2; if (natural_light > LIGHT_SOURCE_BRIGHT) { // Apply sunlight, first light source so just assign for (int sx = DAYLIGHT_LEVEL - hl; sx < LIGHTMAP_CACHE_X - hl; ++sx) { for (int sy = DAYLIGHT_LEVEL - hl; sy < LIGHTMAP_CACHE_Y - hl; ++sy) { // In bright light indoor light exists to some degree if (!is_outside(sx, sy)) { lm[sx][sy] = LIGHT_AMBIENT_LOW; } else if (g->u.posx() == sx && g->u.posy() == sy ) { //Only apply daylight on square where player is standing to avoid flooding // the lightmap when in less than total sunlight. lm[sx][sy] = natural_light; } } } } apply_character_light( g->u ); for( auto &n : g->active_npc ) { apply_character_light( *n ); } // LIGHTMAP_CACHE_X = MAPSIZE * SEEX // LIGHTMAP_CACHE_Y = MAPSIZE * SEEY // Traverse the submaps in order for (int smx = 0; smx < my_MAPSIZE; ++smx) { for (int smy = 0; smy < my_MAPSIZE; ++smy) { auto const cur_submap = get_submap_at_grid( smx, smy ); for (int sx = 0; sx < SEEX; ++sx) { for (int sy = 0; sy < SEEY; ++sy) { const int x = sx + smx * SEEX; const int y = sy + smy * SEEY; // When underground natural_light is 0, if this changes we need to revisit // Only apply this whole thing if the player is inside, // buildings will be shadowed when outside looking in. if (natural_light > LIGHT_SOURCE_BRIGHT && u_is_inside && !is_outside(x, y)) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(x + dir_x[i], y + dir_y[i]) && is_outside(x + dir_x[i], y + dir_y[i])) { lm[x][y] = natural_light; if (light_transparency(x, y) > LIGHT_TRANSPARENCY_SOLID) { apply_light_arc(x, y, dir_d[i], natural_light); } } } } if (cur_submap->lum[sx][sy]) { auto items = i_at(x, y); add_light_from_items(x, y, items.begin(), items.end()); } const ter_id terrain = cur_submap->ter[sx][sy]; if (terrain == t_lava) { add_light_source(x, y, 50 ); } else if (terrain == t_console) { add_light_source(x, y, 3 ); } else if (terrain == t_utility_light) { add_light_source(x, y, 35 ); } for( auto &fld : cur_submap->fld[sx][sy] ) { const field_entry *cur = &fld.second; // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 160); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 60); } else { add_light_source(x, y, 16); } break; case fd_fire_vent: case fd_flame_burst: add_light_source(x, y, 8); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 8); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 1); } else { apply_light_source(x, y, LIGHT_SOURCE_LOCAL, trigdist); // kinda a hack as the square will still get marked } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source(x, y, 30); } else if (2 == cur->getFieldDensity()) { add_light_source(x, y, 16); } else { add_light_source(x, y, 8); } break; case fd_laser: apply_light_source(x, y, 1, trigdist); break; case fd_spotlight: add_light_source(x, y, 20); break; case fd_dazzling: add_light_source(x, y, 2); break; default: //Suppress warnings break; } } } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { auto &critter = g->zombie(i); if(critter.is_hallucination()) { continue; } int mx = critter.posx(); int my = critter.posy(); if (INBOUNDS(mx, my)) { if (critter.has_effect("onfire")) { apply_light_source(mx, my, 3, trigdist); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (critter.type->luminance > 0) { apply_light_source(mx, my, critter.type->luminance, trigdist); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( auto &vv : vehs ) { vehicle *v = vv.v; if(v->lights_on) { int dir = v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for( auto &light_indice : light_indices ) { veh_luminance += ( v->part_info( light_indice ).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source(px, py, SQRT_2); // Add a little surrounding light apply_light_arc( px, py, dir + v->parts[light_indice].direction, veh_luminance, 45 ); } } } } if(v->overhead_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for( auto &light_indice : light_indices ) { if( ( calendar::turn % 2 && v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) || ( !( calendar::turn % 2 ) && v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) ) || ( !v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) && !v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } } // why reinvent the [lightmap] wheel if(v->dome_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_DOME_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } if(v->aisle_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_AISLE_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } if(v->has_atomic_lights) { // atomic light is always on std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_ATOMIC_LIGHT); for( auto &light_indice : light_indices ) { int px = vv.x + v->parts[light_indice].precalc[0].x; int py = vv.y + v->parts[light_indice].precalc[0].y; if(INBOUNDS(px, py)) { add_light_source( px, py, v->part_info( light_indice ).bonus ); } } } for( size_t p = 0; p < v->parts.size(); ++p ) { int px = vv.x + v->parts[p].precalc[0].x; int py = vv.y + v->parts[p].precalc[0].y; if( !INBOUNDS( px, py ) ) { continue; } if( v->part_flag( p, VPFLAG_CARGO ) && !v->part_flag( p, "COVERED" ) ) { add_light_from_items( px, py, v->get_items(p).begin(), v->get_items(p).end() ); } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if ( light_source_buffer[sx][sy] > 0. ) { apply_light_source(sx, sy, light_source_buffer[sx][sy], ( trigdist && light_source_buffer[sx][sy] > 3. ) ); } } } if (g->u.has_active_bionic("bio_night") ) { for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if (rl_dist(sx, sy, g->u.posx(), g->u.posy()) < 15) { lm[sx][sy] = 0; } } } } }
bool map::process_fields_in_submap(game *g, int gridn) { bool found_field = false; field *cur; field_id curtype; for (int locx = 0; locx < SEEX; locx++) { for (int locy = 0; locy < SEEY; locy++) { cur = &(grid[gridn].fld[locx][locy]); int x = locx + SEEX * (gridn % my_MAPSIZE), y = locy + SEEY * int(gridn / my_MAPSIZE); curtype = cur->type; if (!found_field && curtype != fd_null) found_field = true; if (cur->density > 3 || cur->density < 1) debugmsg("Whoooooa density of %d", cur->density); if (cur->age == 0) // Don't process "newborn" fields curtype = fd_null; int part; vehicle *veh; switch (curtype) { case fd_null: break; // Do nothing, obviously. OBVIOUSLY. case fd_blood: case fd_bile: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 250; break; case fd_acid: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 20; for (int i = 0; i < i_at(x, y).size(); i++) { item *melting = &(i_at(x, y)[i]); if (melting->made_of(LIQUID) || melting->made_of(VEGGY) || melting->made_of(FLESH) || melting->made_of(POWDER) || melting->made_of(COTTON) || melting->made_of(WOOL) || melting->made_of(PAPER) || melting->made_of(PLASTIC) || (melting->made_of(GLASS) && !one_in(3)) || one_in(4)) { // Acid destructable objects here melting->damage++; if (melting->damage >= 5 || (melting->made_of(PAPER) && melting->damage >= 3)) { cur->age += melting->volume(); for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } } break; case fd_sap: break; // It doesn't do anything. case fd_fire: { // Consume items as fuel to help us grow/last longer. bool destroyed = false; int vol = 0, smoke = 0, consumed = 0; for (int i = 0; i < i_at(x, y).size() && consumed < cur->density * 2; i++) { destroyed = false; vol = i_at(x, y)[i].volume(); item *it = &(i_at(x, y)[i]); if (it->is_ammo() && it->ammo_type() != AT_BATT && it->ammo_type() != AT_NAIL && it->ammo_type() != AT_BB && it->ammo_type() != AT_BOLT && it->ammo_type() != AT_ARROW) { cur->age /= 2; cur->age -= 600; destroyed = true; smoke += 6; consumed++; } else if (it->made_of(PAPER)) { destroyed = it->burn(cur->density * 3); consumed++; if (cur->density == 1) cur->age -= vol * 10; if (vol >= 4) smoke++; } else if ((it->made_of(WOOD) || it->made_of(VEGGY))) { if (vol <= cur->density * 10 || cur->density == 3) { cur->age -= 4; destroyed = it->burn(cur->density); smoke++; consumed++; } else if (it->burnt < cur->density) { destroyed = it->burn(1); smoke++; } } else if ((it->made_of(COTTON) || it->made_of(WOOL))) { if (vol <= cur->density * 5 || cur->density == 3) { cur->age--; destroyed = it->burn(cur->density); smoke++; consumed++; } else if (it->burnt < cur->density) { destroyed = it->burn(1); smoke++; } } else if (it->made_of(FLESH)) { if (vol <= cur->density * 5 || (cur->density == 3 && one_in(vol / 20))) { cur->age--; destroyed = it->burn(cur->density); smoke += 3; consumed++; } else if (it->burnt < cur->density * 5 || cur->density >= 2) { destroyed = it->burn(1); smoke++; } } else if (it->made_of(LIQUID)) { switch (it->type->id) { // TODO: Make this be not a hack. case itm_whiskey: case itm_vodka: case itm_rum: case itm_tequila: cur->age -= 300; smoke += 6; break; default: cur->age += rng(80 * vol, 300 * vol); smoke++; } destroyed = true; consumed++; } else if (it->made_of(POWDER)) { cur->age -= vol; destroyed = true; smoke += 2; } else if (it->made_of(PLASTIC)) { smoke += 3; if (it->burnt <= cur->density * 2 || (cur->density == 3 && one_in(vol))) { destroyed = it->burn(cur->density); if (one_in(vol + it->burnt)) cur->age--; } } if (destroyed) { for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } veh = &(veh_at(x, y, part)); if (veh->type != veh_null && (veh->parts[part].flags & VHP_FUEL_TANK) && veh->fuel_type == AT_GAS) { if (cur->density > 1 && one_in (8) && veh->fuel > 0) veh->explode (g, x, y); } // Consume the terrain we're on if (has_flag(explodes, x, y)) { ter(x, y) = ter_id(int(ter(x, y)) + 1); cur->age = 0; cur->density = 3; g->explosion(x, y, 40, 0, true); } else if (has_flag(inflammable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; smoke += 15; if (cur->density == 3) ter(x, y) = t_ash; } else if (has_flag(flammable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; smoke += 15; if (cur->density == 3) ter(x, y) = t_rubble; } else if (has_flag(meltable, x, y) && one_in(32 - cur->density * 10)) { cur->age -= cur->density * cur->density * 40; if (cur->density == 3) ter(x, y) = t_b_metal; } else if (terlist[ter(x, y)].flags & mfb(swimmable)) cur->age += 800; // Flames die quickly on water // If we consumed a lot, the flames grow higher while (cur->density < 3 && cur->age < 0) { cur->age += 300; cur->density++; } // If the flames are in a pit, it can't spread to non-pit bool in_pit = (ter(x, y) == t_pit); // If the flames are REALLY big, they contribute to adjacent flames if (cur->density == 3 && cur->age < 0) { // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3 && cur->age < 0; i++) { for (int j = 0; j < 3 && cur->age < 0; j++) { int fx = x + ((i + starti) % 3) - 1, fy = y + ((j + startj) % 3) - 1; if (field_at(fx, fy).type == fd_fire && field_at(fx, fy).density < 3 && (!in_pit || ter(fx, fy) == t_pit)) { field_at(fx, fy).density++; field_at(fx, fy).age = 0; cur->age = 0; } } } } // Consume adjacent fuel / terrain to spread. // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { int fx = x + ((i + starti) % 3) - 1, fy = y + ((j + startj) % 3) - 1; if (INBOUNDS(fx, fy)) { int spread_chance = 20 * (cur->density - 1) + 10 * smoke; if (has_flag(explodes, fx, fy) && one_in(8 - cur->density)) { ter(fx, fy) = ter_id(int(ter(fx, fy)) + 1); g->explosion(fx, fy, 40, 0, true); } else if ((i != 0 || j != 0) && rng(1, 100) < spread_chance && (!in_pit || ter(fx, fy) == t_pit) && ((cur->density == 3 && (has_flag(flammable, fx, fy) || one_in(20))) || flammable_items_at(fx, fy) || field_at(fx, fy).type == fd_web)) { if (field_at(fx, fy).type == fd_smoke || field_at(fx, fy).type == fd_web) field_at(fx, fy) = field(fd_fire, 1, 0); else add_field(g, fx, fy, fd_fire, 1); } else { bool nosmoke = true; for (int ii = -1; ii <= 1; ii++) { for (int jj = -1; jj <= 1; jj++) { if (field_at(x+ii, y+jj).type == fd_fire && field_at(x+ii, y+jj).density == 3) smoke++; else if (field_at(x+ii, y+jj).type == fd_smoke) nosmoke = false; } } // If we're not spreading, maybe we'll stick out some smoke, huh? if (move_cost(fx, fy) > 0 && (!one_in(smoke) || (nosmoke && one_in(40))) && rng(3, 35) < cur->density * 10 && cur->age < 1000) { smoke--; add_field(g, fx, fy, fd_smoke, rng(1, cur->density)); } } } } } } break; case fd_smoke: for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 50; if (one_in(2)) { std::vector <point> spread; for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if ((field_at(x+a, y+b).type == fd_smoke && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; if (field_at(p.x, p.y).type == fd_smoke && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_smoke, 1)){ cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_tear_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 30; // One in three chance that it spreads (less than smoke!) if (one_in(3)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby teargas grows thicker if (field_at(p.x, p.y).type == fd_tear_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke is converted into teargas } else if (field_at(p.x, p.y).type == fd_smoke) { field_at(p.x, p.y).type = fd_tear_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_tear_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_toxic_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_toxic_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby toxic gas grows thicker if (field_at(p.x, p.y).type == fd_toxic_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke & teargas is converted into toxic gas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_toxic_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_toxic_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_nuke_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; // Increase long-term radiation in the land underneath radiation(x, y) += rng(0, cur->density); if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_toxic_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby nukegas grows thicker if (field_at(p.x, p.y).type == fd_nuke_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke, tear, and toxic gas is converted into nukegas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_toxic_gas || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_nuke_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_nuke_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_gas_vent: for (int i = x - 1; i <= x + 1; i++) { for (int j = y - 1; j <= y + 1; j++) { if (field_at(i, j).type == fd_toxic_gas && field_at(i, j).density < 3) field_at(i, j).density++; else add_field(g, i, j, fd_toxic_gas, 3); } } break; case fd_fire_vent: if (cur->density > 1) { if (one_in(3)) cur->density--; } else { cur->type = fd_flame_burst; cur->density = 3; } break; case fd_flame_burst: if (cur->density > 1) cur->density--; else { cur->type = fd_fire_vent; cur->density = 3; } break; case fd_electricity: if (!one_in(5)) { // 4 in 5 chance to spread std::vector<point> valid; if (move_cost(x, y) == 0 && cur->density > 1) { // We're grounded int tries = 0; while (tries < 10 && cur->age < 50) { int cx = x + rng(-1, 1), cy = y + rng(-1, 1); if (move_cost(cx, cy) != 0 && field_at(cx, cy).is_null()) { add_field(g, cx, cy, fd_electricity, 1); cur->density--; tries = 0; } else tries++; } } else { // We're not grounded; attempt to ground for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (move_cost(x + a, y + b) == 0 && // Grounded tiles first field_at(x + a, y + b).is_null()) valid.push_back(point(x + a, y + b)); } } if (valid.size() == 0) { // Spread to adjacent space, then int px = x + rng(-1, 1), py = y + rng(-1, 1); if (move_cost(px, py) > 0 && field_at(px, py).type == fd_electricity && field_at(px, py).density < 3) field_at(px, py).density++; else if (move_cost(px, py) > 0) add_field(g, px, py, fd_electricity, 1); cur->density--; } while (valid.size() > 0 && cur->density > 0) { int index = rng(0, valid.size() - 1); add_field(g, valid[index].x, valid[index].y, fd_electricity, 1); cur->density--; valid.erase(valid.begin() + index); } } } break; case fd_fatigue: if (cur->density < 3 && int(g->turn) % 3600 == 0 && one_in(10)) cur->density++; else if (cur->density == 3 && one_in(600)) { // Spawn nether creature! mon_id type = mon_id(rng(mon_flying_polyp, mon_blank)); monster creature(g->mtypes[type]); creature.spawn(x + rng(-3, 3), y + rng(-3, 3)); g->z.push_back(creature); } break; } cur->age++; if (fieldlist[cur->type].halflife > 0) { if (cur->age > 0 && dice(3, cur->age) > dice(3, fieldlist[cur->type].halflife)) { cur->age = 0; cur->density--; } if (cur->density <= 0) { // Totally dissapated. grid[gridn].field_count--; grid[gridn].fld[locx][locy] = field(); } } } } return found_field; }
void map::generate_lightmap() { memset(lm, 0, sizeof(lm)); memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: Profit! */ memset(light_source_buffer, 0, sizeof(light_source_buffer)); const int dir_x[] = { 1, 0 , -1, 0 }; const int dir_y[] = { 0, 1 , 0, -1 }; const int dir_d[] = { 180, 270, 0, 90 }; const float held_luminance = g->u.active_light(); const float natural_light = g->natural_light_level(); if (natural_light > LIGHT_SOURCE_BRIGHT) { // Apply sunlight, first light source so just assign for(int sx = DAYLIGHT_LEVEL - (natural_light / 2); sx < LIGHTMAP_CACHE_X - (natural_light / 2); ++sx) { for(int sy = DAYLIGHT_LEVEL - (natural_light / 2); sy < LIGHTMAP_CACHE_Y - (natural_light / 2); ++sy) { // In bright light indoor light exists to some degree if (!is_outside(sx, sy)) { lm[sx][sy] = LIGHT_AMBIENT_LOW; } else if (g->u.posx == sx && g->u.posy == sy ) { //Only apply daylight on square where player is standing to avoid flooding // the lightmap when in less than total sunlight. lm[sx][sy] = natural_light; } } } } // Apply player light sources if (held_luminance > LIGHT_AMBIENT_LOW) { apply_light_source(g->u.posx, g->u.posy, held_luminance, trigdist); } for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { const ter_id terrain = ter(sx, sy); const std::vector<item> &items = i_at(sx, sy); field ¤t_field = field_at(sx, sy); // When underground natural_light is 0, if this changes we need to revisit // Only apply this whole thing if the player is inside, // buildings will be shadowed when outside looking in. if (natural_light > LIGHT_AMBIENT_LOW && !is_outside(g->u.posx, g->u.posy) ) { if (!is_outside(sx, sy)) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(sx + dir_x[i], sy + dir_y[i]) && is_outside(sx + dir_x[i], sy + dir_y[i])) { lm[sx][sy] = natural_light; if (light_transparency(sx, sy) > LIGHT_TRANSPARENCY_SOLID) { apply_light_arc(sx, sy, dir_d[i], natural_light); } } } } } for( std::vector<item>::const_iterator itm = items.begin(); itm != items.end(); ++itm ) { float ilum = 0.0; // brightness int iwidth = 0; // 0-360 degrees. 0 is a circular light_source int idir = 0; // otherwise, it's a light_arc pointed in this direction if ( itm->getlight(ilum, iwidth, idir ) ) { if ( iwidth > 0 ) { apply_light_arc( sx, sy, idir, ilum, iwidth ); } else { add_light_source(sx, sy, ilum); } } } if(terrain == t_lava) { add_light_source(sx, sy, 50 ); } if(terrain == t_console) { add_light_source(sx, sy, 3 ); } if(terrain == t_emergency_light) { add_light_source(sx, sy, 3 ); } if(terrain == t_utility_light) { add_light_source(sx, sy, 35 ); } field_entry *cur = NULL; for(std::map<field_id, field_entry *>::iterator field_list_it = current_field.getFieldStart(); field_list_it != current_field.getFieldEnd(); ++field_list_it) { cur = field_list_it->second; if(cur == NULL) { continue; } // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 160); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 60); } else { add_light_source(sx, sy, 16); } break; case fd_fire_vent: case fd_flame_burst: add_light_source(sx, sy, 8); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 8); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 1); } else { apply_light_source(sx, sy, LIGHT_SOURCE_LOCAL, trigdist); // kinda a hack as the square will still get marked } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source(sx, sy, 30); } else if (2 == cur->getFieldDensity()) { add_light_source(sx, sy, 16); } else { add_light_source(sx, sy, 8); } break; case fd_laser: apply_light_source(sx, sy, 1, trigdist); break; case fd_spotlight: add_light_source(sx, sy, 20); break; case fd_dazzling: add_light_source(sx, sy, 2); break; default: //Suppress warnings break; } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { int mx = g->zombie(i).posx(); int my = g->zombie(i).posy(); if (INBOUNDS(mx, my)) { if (g->zombie(i).has_effect("onfire")) { apply_light_source(mx, my, 3, trigdist); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (g->zombie(i).type->luminance > 0) { apply_light_source(mx, my, g->zombie(i).type->luminance, trigdist); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( size_t v = 0; v < vehs.size(); ++v ) { if(vehs[v].v->lights_on) { int dir = vehs[v].v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = vehs[v].v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { veh_luminance += ( vehs[v].v->part_info(*part).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { int px = vehs[v].x + vehs[v].v->parts[*part].precalc_dx[0]; int py = vehs[v].y + vehs[v].v->parts[*part].precalc_dy[0]; if(INBOUNDS(px, py)) { apply_light_arc(px, py, dir + vehs[v].v->parts[*part].direction, veh_luminance, 45); } } } } if(vehs[v].v->overhead_lights_on) { std::vector<int> light_indices = vehs[v].v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for (std::vector<int>::iterator part = light_indices.begin(); part != light_indices.end(); ++part) { if((calendar::turn % 2 && vehs[v].v->part_info(*part).has_flag(VPFLAG_ODDTURN)) || (!(calendar::turn % 2) && vehs[v].v->part_info(*part).has_flag(VPFLAG_EVENTURN)) || (!vehs[v].v->part_info(*part).has_flag(VPFLAG_EVENTURN) && !vehs[v].v->part_info(*part).has_flag(VPFLAG_ODDTURN))) { int px = vehs[v].x + vehs[v].v->parts[*part].precalc_dx[0]; int py = vehs[v].y + vehs[v].v->parts[*part].precalc_dy[0]; if(INBOUNDS(px, py)) { add_light_source( px, py, vehs[v].v->part_info(*part).bonus ); } } } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if ( light_source_buffer[sx][sy] > 0. ) { apply_light_source(sx, sy, light_source_buffer[sx][sy], ( trigdist && light_source_buffer[sx][sy] > 3. ) ); } } } if (g->u.has_active_bionic("bio_night") ) { for(int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx) { for(int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy) { if (rl_dist(sx, sy, g->u.posx, g->u.posy) < 15) { lm[sx][sy] = 0; } } } } }
bool player::disassemble( int dis_pos ) { return disassemble( i_at( dis_pos ), dis_pos, false ); }
void map::generate_lightmap( const int zlev ) { auto &map_cache = get_cache( zlev ); auto &lm = map_cache.lm; auto &sm = map_cache.sm; auto &outside_cache = map_cache.outside_cache; std::memset(lm, 0, sizeof(lm)); std::memset(sm, 0, sizeof(sm)); /* Bulk light sources wastefully cast rays into neighbors; a burning hospital can produce significant slowdown, so for stuff like fire and lava: * Step 1: Store the position and luminance in buffer via add_light_source, for efficient checking of neighbors. * Step 2: After everything else, iterate buffer and apply_light_source only in non-redundant directions * Step 3: ???? * Step 4: Profit! */ auto &light_source_buffer = map_cache.light_source_buffer; std::memset(light_source_buffer, 0, sizeof(light_source_buffer)); constexpr int dir_x[] = { 0, -1 , 1, 0 }; // [0] constexpr int dir_y[] = { -1, 0 , 0, 1 }; // [1][X][2] constexpr int dir_d[] = { 90, 0, 180, 270 }; // [3] const float natural_light = g->natural_light_level( zlev ); const float inside_light = (natural_light > LIGHT_SOURCE_BRIGHT) ? LIGHT_AMBIENT_LOW + 1.0 : LIGHT_AMBIENT_MINIMAL; // Apply sunlight, first light source so just assign for( int sx = 0; sx < LIGHTMAP_CACHE_X; ++sx ) { for( int sy = 0; sy < LIGHTMAP_CACHE_Y; ++sy ) { // In bright light indoor light exists to some degree if( !outside_cache[sx][sy] ) { lm[sx][sy] = inside_light; } else { lm[sx][sy] = natural_light; } } } apply_character_light( g->u ); for( auto &n : g->active_npc ) { apply_character_light( *n ); } // Traverse the submaps in order for (int smx = 0; smx < my_MAPSIZE; ++smx) { for (int smy = 0; smy < my_MAPSIZE; ++smy) { auto const cur_submap = get_submap_at_grid( smx, smy, zlev ); for (int sx = 0; sx < SEEX; ++sx) { for (int sy = 0; sy < SEEY; ++sy) { const int x = sx + smx * SEEX; const int y = sy + smy * SEEY; const tripoint p( x, y, zlev ); // Project light into any openings into buildings. if (natural_light > LIGHT_SOURCE_BRIGHT && !outside_cache[p.x][p.y]) { // Apply light sources for external/internal divide for(int i = 0; i < 4; ++i) { if (INBOUNDS(p.x + dir_x[i], p.y + dir_y[i]) && outside_cache[p.x + dir_x[i]][p.y + dir_y[i]]) { lm[p.x][p.y] = natural_light; if (light_transparency( p ) > LIGHT_TRANSPARENCY_SOLID) { apply_directional_light( p, dir_d[i], natural_light ); } } } } if( cur_submap->lum[sx][sy] && has_items( p ) ) { auto items = i_at( p ); add_light_from_items( p, items.begin(), items.end() ); } const ter_id terrain = cur_submap->ter[sx][sy]; if (terrain == t_lava) { add_light_source( p, 50 ); } else if (terrain == t_console) { add_light_source( p, 10 ); } else if (terrain == t_utility_light) { add_light_source( p, 240 ); } for( auto &fld : cur_submap->fld[sx][sy] ) { const field_entry *cur = &fld.second; // TODO: [lightmap] Attach light brightness to fields switch(cur->getFieldType()) { case fd_fire: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_fire_vent: case fd_flame_burst: add_light_source( p, 20 ); break; case fd_electricity: case fd_plasma: if (3 == cur->getFieldDensity()) { add_light_source( p, 20 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 4 ); } else { // Kinda a hack as the square will still get marked. apply_light_source( p, LIGHT_SOURCE_LOCAL ); } break; case fd_incendiary: if (3 == cur->getFieldDensity()) { add_light_source( p, 160 ); } else if (2 == cur->getFieldDensity()) { add_light_source( p, 60 ); } else { add_light_source( p, 20 ); } break; case fd_laser: apply_light_source( p, 4 ); break; case fd_spotlight: add_light_source( p, 80 ); break; case fd_dazzling: add_light_source( p, 5 ); break; default: //Suppress warnings break; } } } } } } for (size_t i = 0; i < g->num_zombies(); ++i) { auto &critter = g->zombie(i); if(critter.is_hallucination()) { continue; } const tripoint &mp = critter.pos(); if( inbounds( mp ) ) { if (critter.has_effect( effect_onfire)) { apply_light_source( mp, 8 ); } // TODO: [lightmap] Attach natural light brightness to creatures // TODO: [lightmap] Allow creatures to have light attacks (ie: eyebot) // TODO: [lightmap] Allow creatures to have facing and arc lights if (critter.type->luminance > 0) { apply_light_source( mp, critter.type->luminance ); } } } // Apply any vehicle light sources VehicleList vehs = get_vehicles(); for( auto &vv : vehs ) { vehicle *v = vv.v; if(v->lights_on) { int dir = v->face.dir(); float veh_luminance = 0.0; float iteration = 1.0; std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CONE_LIGHT); for( auto &light_indice : light_indices ) { veh_luminance += ( v->part_info( light_indice ).bonus / iteration ); iteration = iteration * 1.1; } if (veh_luminance > LL_LIT) { for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp ) ) { add_light_source( pp, SQRT_2 ); // Add a little surrounding light apply_light_arc( pp, dir + v->parts[light_indice].direction, veh_luminance, 45 ); } } } } if(v->overhead_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_CIRCLE_LIGHT); for( auto &light_indice : light_indices ) { if( ( calendar::turn % 2 && v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) || ( !( calendar::turn % 2 ) && v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) ) || ( !v->part_info( light_indice ).has_flag( VPFLAG_EVENTURN ) && !v->part_info( light_indice ).has_flag( VPFLAG_ODDTURN ) ) ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if(inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } } // why reinvent the [lightmap] wheel if(v->dome_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_DOME_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } if(v->aisle_lights_on) { std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_AISLE_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if( inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } if(v->has_atomic_lights) { // atomic light is always on std::vector<int> light_indices = v->all_parts_with_feature(VPFLAG_ATOMIC_LIGHT); for( auto &light_indice : light_indices ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[light_indice].precalc[0]; if(inbounds( pp )) { add_light_source( pp, v->part_info( light_indice ).bonus ); } } } for( size_t p = 0; p < v->parts.size(); ++p ) { tripoint pp = tripoint( vv.x, vv.y, vv.z ) + v->parts[p].precalc[0]; if( !inbounds( pp ) ) { continue; } if( v->part_flag( p, VPFLAG_CARGO ) && !v->part_flag( p, "COVERED" ) ) { add_light_from_items( pp, v->get_items(p).begin(), v->get_items(p).end() ); } } } /* Now that we have position and intensity of all bulk light sources, apply_ them This may seem like extra work, but take a 12x12 raging inferno: unbuffered: (12^2)*(160*4) = apply_light_ray x 92160 buffered: (12*4)*(160) = apply_light_ray x 7680 */ const tripoint cache_start( 0, 0, zlev ); const tripoint cache_end( LIGHTMAP_CACHE_X, LIGHTMAP_CACHE_Y, zlev ); for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( light_source_buffer[p.x][p.y] > 0.0 ) { apply_light_source( p, light_source_buffer[p.x][p.y] ); } } if (g->u.has_active_bionic("bio_night") ) { for( const tripoint &p : points_in_rectangle( cache_start, cache_end ) ) { if( rl_dist( p, g->u.pos() ) < 15 ) { lm[p.x][p.y] = LIGHT_AMBIENT_MINIMAL; } } } }
bool map::process_fields(game *g) { bool found_field = false; field *cur; field_id curtype; for (int x = 0; x < SEEX * 3; x++) { for (int y = 0; y < SEEY * 3; y++) { cur = &field_at(x, y); curtype = cur->type; if (!found_field && curtype != fd_null) found_field = true; if (cur->density > 3) debugmsg("Whoooooa density of %d", cur->density); if (cur->age == 0) // Don't process "newborn" fields curtype = fd_null; switch (curtype) { case fd_null: break; // Do nothing, obviously. OBVIOUSLY. case fd_blood: case fd_bile: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 250; break; case fd_acid: if (has_flag(swimmable, x, y)) // Dissipate faster in water cur->age += 20; for (int i = 0; i < i_at(x, y).size(); i++) { item *melting = &(i_at(x, y)[i]); if (melting->made_of(LIQUID) || melting->made_of(VEGGY) || melting->made_of(FLESH) || melting->made_of(POWDER) || melting->made_of(COTTON) || melting->made_of(WOOL) || melting->made_of(PAPER) || melting->made_of(PLASTIC) || (melting->made_of(GLASS) && !one_in(3)) || one_in(4)) { // Acid destructable objects here melting->damage++; if (melting->damage >= 5 || (melting->made_of(PAPER) && melting->damage >= 3)) { cur->age += melting->volume(); for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } } break; case fd_fire: // Consume items as fuel to help us grow/last longer. bool destroyed; int vol; for (int i = 0; i < i_at(x, y).size(); i++) { destroyed = false; vol = i_at(x, y)[i].volume(); if (i_at(x, y)[i].is_ammo()) { cur->age /= 2; cur->age -= 300; destroyed = true; } else if (i_at(x, y)[i].made_of(PAPER)) { cur->age -= vol * 10; destroyed = true; } else if ((i_at(x, y)[i].made_of(WOOD) || i_at(x, y)[i].made_of(VEGGY)) && (vol <= cur->density*10-(cur->age>0 ? rng(0,cur->age/10) : 0) || cur->density == 3)) { cur->age -= vol * 10; destroyed = true; } else if ((i_at(x, y)[i].made_of(COTTON) || i_at(x, y)[i].made_of(FLESH)|| i_at(x, y)[i].made_of(WOOL)) && (vol <= cur->density*2 || (cur->density == 3 && one_in(vol)))) { cur->age -= vol * 5; destroyed = true; } else if (i_at(x, y)[i].made_of(LIQUID) || i_at(x, y)[i].made_of(POWDER)|| i_at(x, y)[i].made_of(PLASTIC)|| (cur->density >= 2 && i_at(x, y)[i].made_of(GLASS)) || (cur->density == 3 && i_at(x, y)[i].made_of(IRON))) { switch (i_at(x, y)[i].type->id) { // TODO: Make this be not a hack. case itm_whiskey: case itm_vodka: case itm_rum: case itm_tequila: cur->age -= 220; break; } destroyed = true; } if (destroyed) { for (int m = 0; m < i_at(x, y)[i].contents.size(); m++) i_at(x, y).push_back( i_at(x, y)[i].contents[m] ); i_at(x, y).erase(i_at(x, y).begin() + i); i--; } } // Consume the terrain we're on if (terlist[ter(x, y)].flags & mfb(flammable) && one_in(8 - cur->density)) { cur->age -= cur->density * cur->density * 40; if (cur->density == 3) ter(x, y) = t_rubble; } else if (terlist[ter(x, y)].flags & mfb(explodes)) { ter(x, y) = ter_id(int(ter(x, y)) + 1); cur->age = 0; cur->density = 3; g->explosion(x, y, 40, 0, true); } else if (terlist[ter(x, y)].flags & mfb(swimmable)) cur->age += 800; // Flames die quickly on water // If we consumed a lot, the flames grow higher while (cur->density < 3 && cur->age < 0) { cur->age += 300; cur->density++; } // If the flames are REALLY big, they contribute to adjacent flames if (cur->density == 3 && cur->age < 0) { // If the flames are in a pit, it can't spread to non-pit bool in_pit = (ter(x, y) == t_pit); // Randomly offset our x/y shifts by 0-2, to randomly pick a square to spread to int starti = rng(0, 2); int startj = rng(0, 2); for (int i = 0; i < 3 && cur->age < 0; i++) { for (int j = 0; j < 3 && cur->age < 0; j++) { if (field_at(x+((i+starti)%3), y+((j+startj)%3)).type == fd_fire && field_at(x+((i+starti)%3), y+((j+startj)%3)).density < 3 && (!in_pit || ter(x+((i+starti)%3), y+((j+startj)%3)) == t_pit)) { field_at(x+((i+starti)%3), y+((j+startj)%3)).density++; field_at(x+((i+starti)%3), y+((j+startj)%3)).age = 0; cur->age = 0; } } } } // Consume adjacent fuel / terrain to spread. for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (x+i >= 0 && y+j >= 0 && x+i < SEEX * 3 && y+j <= SEEY * 3) { if (has_flag(explodes, x + i, y + j) && one_in(8 - cur->density)) { ter(x + i, y + i) = ter_id(int(ter(x + i, y + i)) + 1); g->explosion(x+i, y+j, 40, 0, true); } else if ((i != 0 || j != 0) && (i_at(x+i, y+j).size() > 0 || rng(15, 120) < cur->density * 10)) { if (field_at(x+i, y+j).type == fd_smoke) field_at(x+i, y+j) = field(fd_fire, 1, 0); // Fire in pits can only spread to adjacent pits else if (ter(x, y) != t_pit || ter(x + i, y + j) == t_pit) add_field(g, x+i, y+j, fd_fire, 1); // If we're not spreading, maybe we'll stick out some smoke, huh? } else if (move_cost(x+i, y+j) > 0 && rng(7, 40) < cur->density * 10 && cur->age < 1000) { add_field(g, x+i, y+j, fd_smoke, rng(1, cur->density)); } } } } break; case fd_smoke: for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 50; if (one_in(2)) { std::vector <point> spread; for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if ((field_at(x+a, y+b).type == fd_smoke && field_at(x+a, y+b).density < 3) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; if (field_at(p.x, p.y).type == fd_smoke && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_smoke, 1)){ cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_tear_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 30; // One in three chance that it spreads (less than smoke!) if (one_in(3)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby teargas grows thicker if (field_at(p.x, p.y).type == fd_tear_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke is converted into teargas } else if (field_at(p.x, p.y).type == fd_smoke) { field_at(p.x, p.y).type = fd_tear_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_tear_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_nuke_gas: // Reset nearby scents to zero for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) g->scent(x+i, y+j) = 0; } if (is_outside(x, y)) cur->age += 40; // Increase long-term radiation in the land underneath radiation(x, y) += rng(0, cur->density); if (one_in(2)) { std::vector <point> spread; // Pick all eligible points to spread to for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (((field_at(x+a, y+b).type == fd_smoke || field_at(x+a, y+b).type == fd_tear_gas || field_at(x+a, y+b).type == fd_nuke_gas ) && field_at(x+a, y+b).density < 3 ) || (field_at(x+a, y+b).is_null() && move_cost(x+a, y+b) > 0)) spread.push_back(point(x+a, y+b)); } } // Then, spread to a nearby point if (cur->density > 0 && cur->age > 0 && spread.size() > 0) { point p = spread[rng(0, spread.size() - 1)]; // Nearby nukegas grows thicker if (field_at(p.x, p.y).type == fd_nuke_gas && field_at(p.x, p.y).density < 3) { field_at(p.x, p.y).density++; cur->density--; // Nearby smoke & teargas is converted into nukegas } else if (field_at(p.x, p.y).type == fd_smoke || field_at(p.x, p.y).type == fd_tear_gas) { field_at(p.x, p.y).type = fd_nuke_gas; // Or, just create a new field. } else if (cur->density > 0 && move_cost(p.x, p.y) > 0 && add_field(g, p.x, p.y, fd_nuke_gas, 1)) { cur->density--; field_at(p.x, p.y).age = cur->age; } } } break; case fd_electricity: if (!one_in(5)) { // 4 in 5 chance to spread std::vector<point> valid; if (move_cost(x, y) == 0 && cur->density > 1) { // We're grounded int tries = 0; while (tries < 10 && cur->age < 50) { int cx = x + rng(-1, 1), cy = y + rng(-1, 1); if (move_cost(cx, cy) != 0 && field_at(cx, cy).is_null()) { add_field(g, cx, cy, fd_electricity, 1); cur->density--; tries = 0; } else tries++; } } else { // We're not grounded; attempt to ground for (int a = -1; a <= 1; a++) { for (int b = -1; b <= 1; b++) { if (move_cost(x + a, y + b) == 0 && // Grounded tiles first field_at(x + a, y + b).is_null()) valid.push_back(point(x + a, y + b)); } } if (valid.size() == 0) { // Spread to adjacent space, then int px = x + rng(-1, 1), py = y + rng(-1, 1); if (move_cost(px, py) > 0 && field_at(px, py).type == fd_electricity && field_at(px, py).density < 3) field_at(px, py).density++; else if (move_cost(px, py) > 0) add_field(g, px, py, fd_electricity, 1); cur->density--; } while (valid.size() > 0 && cur->density > 0) { int index = rng(0, valid.size() - 1); add_field(g, valid[index].x, valid[index].y, fd_electricity, 1); cur->density--; valid.erase(valid.begin() + index); } } } break; case fd_fatigue: if (cur->density < 3 && g->turn % 3600 == 0 && one_in(10)) cur->density++; else if (cur->density == 3 && one_in(3600)) { // Spawn nether creature! mon_id type = mon_id(rng(mon_flying_polyp, mon_blank)); monster creature(g->mtypes[type]); creature.spawn(x + rng(-3, 3), y + rng(-3, 3)); g->z.push_back(creature); } break; } if (fieldlist[cur->type].halflife > 0) { cur->age++; if (cur->age > 0 && dice(3, cur->age) > dice(3, fieldlist[cur->type].halflife)) { cur->age = 0; cur->density--; } if (cur->density <= 0) // Totally dissapated. field_at(x, y) = field(); } } } return found_field; }
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 map::i_clear(int x, int y) { i_at(x, y).clear(); }
void map::i_rem(int x, int y, int index) { if (index > i_at(x, y).size() - 1) return; i_at(x, y).erase(i_at(x, y).begin() + index); }
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 map::bash(int x, int y, int str, std::string &sound) { sound = ""; for (int i = 0; i < i_at(x, y).size(); i++) { // Destroy glass items (maybe) if (i_at(x, y)[i].made_of(GLASS) && one_in(2)) { if (sound == "") sound = "A " + i_at(x, y)[i].tname() + " shatters! "; else sound = "Some items shatter! "; 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--; } } switch (ter(x, y)) { case t_door_c: case t_door_locked: if (str >= rng(0, 40)) { sound += "smash!"; ter(x, y) = t_door_b; return true; } else { sound += "whump!"; return true; } break; case t_door_b: if (str >= rng(0, 30)) { sound += "crash!"; ter(x, y) = t_door_frame; int num_boards = rng(2, 6); for (int i = 0; i < num_boards; i++) add_item(x, y, (*itypes)[itm_2x4], 0); return true; } else { sound += "wham!"; return true; } break; case t_window: if (str >= rng(0, 6)) { sound += "glass breaking!"; ter(x, y) = t_window_frame; return true; } else { sound += "whack!"; return true; } break; case t_door_boarded: if (str >= dice(3, 50)) { sound += "crash!"; ter(x, y) = t_door_frame; int num_boards = rng(0, 2); for (int i = 0; i < num_boards; i++) add_item(x, y, (*itypes)[itm_2x4], 0); return true; } else { sound += "wham!"; return true; } break; case t_window_boarded: if (str >= dice(3, 30)) { sound += "crash!"; ter(x, y) = t_window_frame; int num_boards = rng(0, 2) * rng(0, 1); for (int i = 0; i < num_boards; i++) add_item(x, y, (*itypes)[itm_2x4], 0); return true; } else { sound += "wham!"; return true; } break; case t_paper: if (str >= dice(2, 6)) { sound += "rrrrip!"; ter(x, y) = t_dirt; return true; } else { sound += "slap!"; return true; } break; case t_toilet: if (str >= dice(8, 10)) { sound += "porcelain breaking!"; ter(x, y) = t_rubble; return true; } else { sound += "whunk!"; return true; } break; case t_dresser: if (str >= dice(3, 45)) { sound += "smash!"; ter(x, y) = t_floor; int num_boards = rng(4, 12); for (int i = 0; i < num_boards; i++) add_item(x, y, (*itypes)[itm_2x4], 0); return true; } else { sound += "whump."; return true; } break; case t_wall_glass_h: case t_wall_glass_v: if (str >= rng(0, 20)) { sound += "glass breaking!"; ter(x, y) = t_floor; return true; } else { sound += "whack!"; return true; } break; case t_reinforced_glass_h: case t_reinforced_glass_v: if (str >= rng(60, 100)) { sound += "glass breaking!"; ter(x, y) = t_floor; return true; } else { sound += "whack!"; return true; } break; case t_tree_young: if (str >= rng(0, 50)) { sound += "crunch!"; ter(x, y) = t_underbrush; int num_sticks = rng(0, 3); for (int i = 0; i < num_sticks; i++) add_item(x, y, (*itypes)[itm_stick], 0); return true; } else { sound += "whack!"; return true; } break; case t_underbrush: if (str >= rng(0, 30) && !one_in(4)) { sound += "crunch."; ter(x, y) = t_dirt; return true; } else { sound += "brush."; return true; } break; case t_marloss: if (str > rng(0, 40)) { sound += "crunch!"; ter(x, y) = t_fungus; return true; } else { sound += "whack!"; return true; } break; case t_vat: if (str >= dice(2, 20)) { sound += "ker-rash!"; ter(x, y) = t_floor; return true; } else { sound += "plunk."; return true; } } if (move_cost(x, y) == 0) { sound += "thump!"; return true; } return false; // If we kick empty space, the action is cancelled }