diff --git a/basecamp.cpp b/basecamp.cpp new file mode 100644 index 0000000000000..6efb717eefeb2 --- /dev/null +++ b/basecamp.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "basecamp.h" + +basecamp::basecamp() + : name() + , posx(0), posy(0) +{ + +} + +basecamp::basecamp(std::string const& name_, int const posx_, int const posy_) + : name(name_) + , posx(posx_), posy(posy_) +{ + +} + +std::string basecamp::board_name() const +{ + return name + " Board"; +} + +std::string basecamp::save_data() const +{ + std::stringstream data; + + // TODO: This will lose underscores, is that a problem? + // strip spaces from name + std::string savename = name; + replace(savename.begin(), savename.end(), ' ', '_'); + + data << savename << " " << posx << " " << posy; + return data.str(); +} + +void basecamp::load_data(std::string const& data) +{ + std::stringstream stream(data); + stream >> name >> posx >> posy; + + // add space to name + replace(name.begin(), name.end(), '_', ' '); +} diff --git a/basecamp.h b/basecamp.h new file mode 100644 index 0000000000000..59238c30c3f28 --- /dev/null +++ b/basecamp.h @@ -0,0 +1,29 @@ +#ifndef _BASECAMP_H_ +#define _BASECAMP_H_ + +#include + +class basecamp +{ +public: + basecamp(); + basecamp(std::string const& name_, int const posx_, int const posy_); + + inline bool is_valid() const { return !name.empty(); } + inline int board_x() const { return posx; } + inline int board_y() const { return posy; } + inline std::string const& camp_name() const { return name; } + + std::string board_name() const; + + // Save/load + std::string save_data() const; + void load_data(std::string const& data); + +private: + std::string name; + int posx, posy; // location of associated bulletin board +}; + + +#endif diff --git a/construction.cpp b/construction.cpp index 158e26299dd0c..8f9fe00ce9180 100644 --- a/construction.cpp +++ b/construction.cpp @@ -192,6 +192,15 @@ void game::init_construction() COMP(itm_2x4, 8, NULL); COMP(itm_nail, 40, NULL); +// Base stuff + CONSTRUCT("Build Bulletin Board", 0, &construct::able_empty, + &construct::done_nothing); + STAGE(t_bulletin, 10) + TOOL(itm_saw, NULL); + TOOL(itm_hammer, itm_hatchet, itm_nailgun, NULL); + COMP(itm_2x4, 4, NULL); + COMP(itm_nail, 8, NULL); + // Household stuff CONSTRUCT("Build Dresser", 1, &construct::able_indoors, &construct::done_nothing); diff --git a/dialogue.h b/dialogue.h index e9fd0067f10cd..23574aa15e863 100644 --- a/dialogue.h +++ b/dialogue.h @@ -38,6 +38,7 @@ struct talk_function void mission_favor (game *g, npc *p); void give_equipment (game *g, npc *p); void start_trade (game *g, npc *p); + void assign_base(game *g, npc *p); void follow (game *g, npc *p); // p follows u void deny_follow (game *g, npc *p); // p gets DI_ASKED_TO_FOLLOW void deny_lead (game *g, npc *p); // p gets DI_ASKED_TO_LEAD @@ -58,6 +59,7 @@ struct talk_function void start_training (game *g, npc *p); void toggle_use_guns (game *g, npc *p); + void toggle_use_silent (game *g, npc *p); void toggle_use_grenades (game *g, npc *p); void set_engagement_none (game *g, npc *p); void set_engagement_close (game *g, npc *p); diff --git a/game.cpp b/game.cpp index d86ce22cdb627..1803e80a88c75 100644 --- a/game.cpp +++ b/game.cpp @@ -5218,17 +5218,26 @@ void game::examine() } while (u.cash >= 10 && query_yn("Play again?")); } } else if (m.ter(examx, examy) == t_bulletin) { -// TODO: Bulletin Boards - switch (menu("Bulletin Board", "Check jobs", "Check events", - "Check other notices", "Post notice", "Cancel", NULL)) { - case 1: - break; - case 2: - break; - case 3: - break; - case 4: - break; + basecamp *camp = m.camp_at(examx, examy); + if (camp && camp->board_x() == examx && camp->board_y() == examy) { + std::vector options; + options.push_back("Cancel"); + int choice = menu_vec(camp->board_name().c_str(), options) - 1; + } + else { + bool create_camp = m.allow_camp(examx, examy); + std::vector options; + if (create_camp) + options.push_back("Create camp"); + options.push_back("Cancel"); + // TODO: Other Bulletin Boards + int choice = menu_vec("Bulletin Board", options) - 1; + if (choice >= 0 && choice < options.size()) { + if (options[choice] == "Create camp") { + // TODO: Allow text entry for name + m.add_camp("Home", examx, examy); + } + } } } else if (m.ter(examx, examy) == t_fault) { popup("\ diff --git a/item.cpp b/item.cpp index 9a5c8935f63ce..33a76135d694f 100644 --- a/item.cpp +++ b/item.cpp @@ -1176,6 +1176,19 @@ bool item::is_gun() return type->is_gun(); } +bool item::is_silent() +{ + if ( is_null() ) + return false; + + // So far only gun code uses this check + return type->is_gun() && ( + noise() < 5 || // almost silent + curammo->type == AT_BOLT || // crossbows + curammo->type == AT_ARROW // bows + ); +} + bool item::is_gunmod() { if( is_null() ) diff --git a/item.h b/item.h index 23125a3bfc9d2..39d8864c6e533 100644 --- a/item.h +++ b/item.h @@ -117,6 +117,7 @@ class item bool is_bashing_weapon(); bool is_cutting_weapon(); bool is_gun(); + bool is_silent(); bool is_gunmod(); bool is_bionic(); bool is_ammo(); diff --git a/map.cpp b/map.cpp index 167bf05b60917..2de642f27ec16 100644 --- a/map.cpp +++ b/map.cpp @@ -219,7 +219,7 @@ void map::board_vehicle(game *g, int x, int y, player *p) return; } veh->parts[seat_part].set_flag(vehicle_part::passenger_flag); - veh->parts[seat_part].passenger_id = 0; // Player is 0 + veh->parts[seat_part].passenger_id = p->id; // Player is 0 p->posx = x; p->posy = y; @@ -2409,6 +2409,49 @@ computer* map::computer_at(const int x, const int y) return &(grid[nonant]->comp); } +bool map::allow_camp(const int x, const int y, const int radius) +{ + return camp_at(x, y, radius) == NULL; +} + +basecamp* map::camp_at(const int x, const int y, const int radius) +{ + // locate the nearest camp in a CAMPSIZE radius + if (!INBOUNDS(x, y)) + return NULL; + + const int sx = std::max(0, x / SEEX - CAMPSIZE); + const int sy = std::max(0, y / SEEY - CAMPSIZE); + const int ex = std::min(MAPSIZE - 1, x / SEEX + CAMPSIZE); + const int ey = std::min(MAPSIZE - 1, y / SEEY + CAMPSIZE); + + for( int ly = sy; ly < ey; ++ly ) + { + for( int lx = sx; lx < ex; ++lx ) + { + int nonant = lx + ly * my_MAPSIZE; + if (grid[nonant]->camp.is_valid()) + { + // we only allow on camp per size radius, kinda + return &(grid[nonant]->camp); + } + } + } + + return NULL; +} + +void map::add_camp(const std::string& name, const int x, const int y) +{ + if (!allow_camp(x, y)) { + dbg(D_ERROR) << "map::add_camp: Attempting to add camp when one in local area."; + return; + } + + const int nonant = int(x / SEEX) + int(y / SEEY) * my_MAPSIZE; + grid[nonant]->camp = basecamp(name, x, y); +} + void map::debug() { mvprintw(0, 0, "MAP DEBUG"); diff --git a/map.h b/map.h index d7485bd84163d..dfc832d6dfc9a 100644 --- a/map.h +++ b/map.h @@ -19,6 +19,8 @@ #include "graffiti.h" #define MAPSIZE 11 +#define CAMPSIZE 1 +#define CAMPCHECK 3 class player; class item; @@ -164,6 +166,11 @@ class map // Computers computer* computer_at(const int x, const int y); + // Camps + bool allow_camp(const int x, const int y, const int radius = CAMPCHECK); + basecamp* camp_at(const int x, const int y, const int radius = CAMPSIZE); + void add_camp(const std::string& name, const int x, const int y); + // Graffiti graffiti graffiti_at(int x, int y); bool add_graffiti(game *g, int x, int y, std::string contents); diff --git a/mapbuffer.cpp b/mapbuffer.cpp index 8b6a1df0c0c37..2e7558f4b3bee 100644 --- a/mapbuffer.cpp +++ b/mapbuffer.cpp @@ -167,6 +167,10 @@ void mapbuffer::save() if (sm->comp.name != "") fout << "c " << sm->comp.save_data() << std::endl; + // Output base camp if any + if (sm->camp.is_valid()) + fout << "B " << sm->camp.save_data() << std::endl; + // Output the graffiti for (int j = 0; j < SEEY; j++) { for (int i = 0; i < SEEX; i++) { @@ -281,6 +285,9 @@ void mapbuffer::load() } else if (string_identifier == "c") { getline(fin, databuff); sm->comp.load_data(databuff); + } else if (string_identifier == "B") { + getline(fin, databuff); + sm->camp.load_data(databuff); } else if (string_identifier == "G") { std::string s; int j; diff --git a/mapdata.h b/mapdata.h index 54a031294f235..94d3270abf893 100644 --- a/mapdata.h +++ b/mapdata.h @@ -11,6 +11,7 @@ #include "computer.h" #include "vehicle.h" #include "graffiti.h" +#include "basecamp.h" #include class game; @@ -798,6 +799,7 @@ struct submap { std::vector spawns; std::vector vehicles; computer comp; + basecamp camp; // only allowing one basecamp per submap }; std::ostream & operator<<(std::ostream &, const submap *); diff --git a/mapgen.cpp b/mapgen.cpp index d935027ee5ce2..df7aada225813 100644 --- a/mapgen.cpp +++ b/mapgen.cpp @@ -98,6 +98,7 @@ void map::generate(game *g, overmap *om, int x, int y, int turn) grid[i]->field_count = 0; grid[i]->turn_last_touched = turn; grid[i]->comp = computer(); + grid[i]->camp = basecamp(); for (int x = 0; x < SEEX; x++) { for (int y = 0; y < SEEY; y++) { grid[i]->ter[x][y] = t_null; diff --git a/npc.cpp b/npc.cpp index 4c10e89c4182b..c515cca49f558 100644 --- a/npc.cpp +++ b/npc.cpp @@ -1303,6 +1303,37 @@ int npc::player_danger(player *u) return ret; } +int npc::vehicle_danger(game *g, int radius) +{ + VehicleList vehicles = g->m.get_vehicles(posx - radius, posy - radius, posx + radius, posy + radius); + + int danger = 0; + + // TODO: check for most dangerous vehicle? + for(size_t i = 0; i < vehicles.size(); ++i) + if (vehicles[i].v->velocity > 0) + { + float facing = vehicles[i].v->face.dir(); + + int ax = vehicles[i].v->global_x(); + int ay = vehicles[i].v->global_y(); + int bx = ax + cos (facing * M_PI / 180.0) * radius; + int by = ay + sin (facing * M_PI / 180.0) * radius; + + // fake size + int last_part = vehicles[i].v->external_parts.back(); + int size = std::max(vehicles[i].v->parts[last_part].mount_dx, vehicles[i].v->parts[last_part].mount_dy); + + float normal = sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay)); + int closest = abs((posx - ax) * (by - ay) - (posy - ay) * (bx - ax)) / normal; + + if (size > closest) + danger = i; + } + + return danger; +} + bool npc::turned_hostile() { return (op_of_u.anger >= hostile_anger_level()); diff --git a/npc.h b/npc.h index 9413585fc96f5..6e71f14345a85 100644 --- a/npc.h +++ b/npc.h @@ -62,6 +62,9 @@ enum npc_mission { NPC_MISSION_MISSING, // Special; following player to finish mission NPC_MISSION_KIDNAPPED, // Special; was kidnapped, to be rescued by player + + NPC_MISSION_BASE, // Base Mission: unassigned + NUM_NPC_MISSIONS }; @@ -90,9 +93,10 @@ enum npc_action { npc_escape_item, npc_wield_melee, npc_wield_loaded_gun, npc_wield_empty_gun, npc_heal, npc_use_painkiller, npc_eat, npc_drop_items, // 5 - 12 npc_flee, npc_melee, npc_shoot, npc_shoot_burst, npc_alt_attack, // 13 - 17 - npc_look_for_player, npc_heal_player, npc_follow_player, npc_talk_to_player, - npc_mug_player, // 18 - 22 - npc_goto_destination, npc_avoid_friendly_fire, // 23, 24 + npc_look_for_player, npc_heal_player, npc_follow_player, npc_follow_embarked, + npc_talk_to_player, npc_mug_player, // 18 - 23 + npc_goto_destination, npc_avoid_friendly_fire, // 24, 25 + npc_base_idle, // 26 num_npc_actions }; @@ -255,25 +259,27 @@ struct npc_combat_rules combat_engagement engagement; bool use_guns; bool use_grenades; + bool use_silent; npc_combat_rules() { engagement = ENGAGE_ALL; use_guns = true; use_grenades = true; + use_silent = false; }; std::string save_info() { std::stringstream dump; - dump << engagement << " " << use_guns << " " << use_grenades << " "; + dump << engagement << " " << use_guns << " " << use_grenades << " " << use_silent; return dump.str(); } void load_info(std::istream &data) { int tmpen; - data >> tmpen >> use_guns >> use_grenades; + data >> tmpen >> use_guns >> use_grenades >> use_silent; engagement = combat_engagement(tmpen); } }; @@ -431,6 +437,7 @@ class npc : public player { void form_opinion(player *u); talk_topic pick_talk_topic(player *u); int player_danger(player *u); // Comparable to monsters + int vehicle_danger(game *g, int radius); bool turned_hostile(); // True if our anger is at least equal to... int hostile_anger_level(); // ... this value! void make_angry(); // Called if the player attacks us @@ -497,7 +504,7 @@ class npc : public player { // Functions which choose an action for a particular goal void choose_monster_target(game *g, int &enemy, int &danger, int &total_danger); - npc_action method_of_fleeing (game *g, int enemy); + npc_action method_of_fleeing (game *g, int target); npc_action method_of_attack (game *g, int enemy, int danger); npc_action address_needs (game *g, int danger); npc_action address_player (game *g); diff --git a/npcmove.cpp b/npcmove.cpp index 86404b3a3e23f..b8efad119758a 100644 --- a/npcmove.cpp +++ b/npcmove.cpp @@ -29,6 +29,8 @@ itype_id ALT_ATTACK_ITEMS[NUM_ALT_ATTACK_ITEMS] = { }; #endif +const int avoidance_vehicles_radius = 5; + std::string npc_action_name(npc_action action); bool thrown_item(item *used); @@ -63,7 +65,20 @@ void npc::move(game *g) } // TODO: Place player-aiding actions here, with a weight + /* NPCs are fairly suicidal so at this point we will do a quick check to see if + * something nasty is going to happen. + */ + + int vehicle = vehicle_danger(g, avoidance_vehicles_radius); + if (vehicle) { + // TODO: Think about how this actually needs to work, for now assume flee from player + target = TARGET_PLAYER; + } + + // TODO: morale breaking when surrounded by hostiles //if (!bravery_check(danger) || !bravery_check(total_danger) || + // TODO: near by active explosives spotted + if (target == TARGET_PLAYER && attitude == NPCATT_FLEE) action = method_of_fleeing(g, target); else if (danger > 0 || (target == TARGET_PLAYER && attitude == NPCATT_KILL)) @@ -86,7 +101,10 @@ void npc::move(game *g) find_item(g); if (g->debugmon) debugmsg("find_item %s", npc_action_name(action).c_str()); - if (fetching_item) // Set to true if find_item() found something + // check if in vehicle before rushing off to fetch things + if (is_following() && g->u.in_vehicle) + action = npc_follow_embarked; + else if (fetching_item) // Set to true if find_item() found something action = npc_pickup; else if (is_following()) // No items, so follow the player? action = npc_follow_player; @@ -100,9 +118,15 @@ void npc::move(game *g) /* Sometimes we'll be following the player at this point, but close enough that * "following" means standing still. If that's the case, if there are any * monsters around, we should attack them after all! + * + * If we are following a embarked player and we are in a vehicle then shoot anyway + * as we are most likely riding shotgun */ - if (action == npc_follow_player && danger > 0 && - rl_dist(posx, posy, g->u.posx, g->u.posy) <= follow_distance()) + if (danger > 0 && ( + (action == npc_follow_embarked && in_vehicle) || + (action == npc_follow_player && + rl_dist(posx, posy, g->u.posx, g->u.posy) <= follow_distance()) + )) action = method_of_attack(g, target, danger); if (g->debugmon) @@ -289,6 +313,39 @@ void npc::execute_action(game *g, npc_action action, int target) move_pause(); break; + case npc_follow_embarked: + if (in_vehicle) + move_pause(); + else { + int p1; + vehicle *veh = g->m.veh_at(g->u.posx, g->u.posy, p1); + + if (!veh) { + debugmsg("Following an embarked player with no vehicle at their location?"); + // TODO: change to wait? - for now pause + move_pause(); + } else { + int p2 = veh->free_seat(); + if (p2 < 0) { + // TODO: be angry at player, switch to wait or leave - for now pause + move_pause(); + } else { + int px = veh->global_x() + veh->parts[p2].precalc_dx[0]; + int py = veh->global_y() + veh->parts[p2].precalc_dy[0]; + update_path(g, px, py); + + // TODO: replace extra hop distance with finding the correct door + // Hop in the last few squares is mostly to avoid player clash + if (path.size() <= 2) { + g->m.board_vehicle(g, px, py, this); + move_pause(); + } else + move_to_next(g); + } + } + } + break; + case npc_talk_to_player: talk_to_u(g); moves = 0; @@ -312,6 +369,11 @@ void npc::execute_action(game *g, npc_action action, int target) avoid_friendly_fire(g, target); break; + case npc_base_idle: + // TODO: patrol or sleep or something? + move_pause(); + break; + default: debugmsg("Unknown NPC action (%d)", action); } @@ -349,7 +411,7 @@ void npc::choose_monster_target(game *g, int &enemy, int &danger, priority -= 10; monster_danger -= 10; } else - priority *= 1 + (.1 * distance); + priority *= 1 + (.1 *use_escape_item distance); */ total_danger += int(monster_danger / (distance == 0 ? 1 : distance)); @@ -421,6 +483,7 @@ npc_action npc::method_of_attack(game *g, int target, int danger) { int tarx = posx, tary = posy; bool can_use_gun = (!is_following() || combat_rules.use_guns); + bool use_silent = (is_following() && combat_rules.use_silent); if (target == TARGET_PLAYER) { tarx = g->u.posx; @@ -444,16 +507,24 @@ npc_action npc::method_of_attack(game *g, int target, int danger) return npc_reload; if (emergency(danger_assessment(g)) && alt_attack_available(g)) return npc_alt_attack; - if (weapon.is_gun() && weapon.charges > 0) { + if (weapon.is_gun() && (!use_silent || weapon.is_silent()) && weapon.charges > 0) { it_gun* gun = dynamic_cast(weapon.type); if (dist > confident_range()) { - if (can_reload() && enough_time_to_reload(g, target, weapon)) + if (can_reload() && (enough_time_to_reload(g, target, weapon) || in_vehicle)) return npc_reload; + else if (in_vehicle && dist > 1) + return npc_pause; else return npc_melee; } if (!wont_hit_friend(g, tarx, tary)) - return npc_avoid_friendly_fire; + if (in_vehicle) + if (can_reload()) + return npc_reload; + else + return npc_pause; // wait for clear shot + else + return npc_avoid_friendly_fire; else if (dist <= confident_range() / 3 && weapon.charges >= gun->burst && gun->burst > 1 && ((weapon.curammo && target_HP >= weapon.curammo->damage * 3) || emergency(danger * 2))) @@ -467,10 +538,10 @@ npc_action npc::method_of_attack(game *g, int target, int danger) bool has_empty_gun = false, has_better_melee = false; std::vector empty_guns; for (int i = 0; i < inv.size(); i++) { - if (can_use_gun && inv[i].is_gun() && inv[i].charges > 0) + bool allowed = can_use_gun && inv[i].is_gun() && (!use_silent || weapon.is_silent()); + if (allowed && inv[i].charges > 0) return npc_wield_loaded_gun; - else if (can_use_gun && inv[i].is_gun() && - enough_time_to_reload(g, target, inv[i])) { + else if (allowed && enough_time_to_reload(g, target, inv[i])) { has_empty_gun = true; empty_guns.push_back(i); } else if (inv[i].melee_value(sklevel) > weapon.melee_value(sklevel) * 1.1) @@ -491,6 +562,8 @@ npc_action npc::method_of_attack(game *g, int target, int danger) else if (has_better_melee) return npc_wield_melee; + if (in_vehicle && dist > 1) + return npc_pause; return npc_melee; } @@ -597,6 +670,8 @@ npc_action npc::long_term_goal_action(game *g) // TODO: Follow / look for player + if (mission == NPC_MISSION_BASE) + return npc_base_idle; if (!has_destination()) set_destination(g); @@ -862,6 +937,11 @@ bool npc::can_move_to(game *g, int x, int y) void npc::move_to(game *g, int x, int y) { + if (in_vehicle) { + // TODO: handle this nicely - npcs should not jump from moving vehicles + g->m.unboard_vehicle(g, posx, posy); + } + if (has_disease(DI_DOWNED)) { moves -= 100; return; @@ -1318,15 +1398,17 @@ void npc::drop_items(game *g, int weight, int volume) npc_action npc::scan_new_items(game *g, int target) { - bool can_use_gun = (!is_following() || combat_rules.use_guns); + bool can_use_gun = (!is_following() || combat_rules.use_guns); + bool use_silent = (is_following() && combat_rules.use_silent); + // Check if there's something better to wield bool has_empty_gun = false, has_better_melee = false; std::vector empty_guns; for (int i = 0; i < inv.size(); i++) { - if (can_use_gun && inv[i].is_gun() && inv[i].charges > 0) + bool allowed = can_use_gun && inv[i].is_gun() && (!use_silent || inv[i].is_silent()); + if (allowed && inv[i].charges > 0) return npc_wield_loaded_gun; - else if (can_use_gun && inv[i].is_gun() && - enough_time_to_reload(g, target, inv[i])) { + else if (allowed && enough_time_to_reload(g, target, inv[i])) { has_empty_gun = true; empty_guns.push_back(i); } else if (inv[i].melee_value(sklevel) > weapon.melee_value(sklevel) * 1.1) @@ -1987,6 +2069,7 @@ std::string npc_action_name(npc_action action) case npc_look_for_player: return "Look for player"; case npc_heal_player: return "Heal player"; case npc_follow_player: return "Follow player"; + case npc_follow_embarked: return "Follow player (embarked)"; case npc_talk_to_player: return "Talk to player"; case npc_mug_player: return "Mug player"; case npc_goto_destination: return "Go to destination"; diff --git a/npctalk.cpp b/npctalk.cpp index 41213bd8ed5da..78cef886c6233 100644 --- a/npctalk.cpp +++ b/npctalk.cpp @@ -5,6 +5,7 @@ #include "rng.h" #include "line.h" #include "keypress.h" +#include "debug.h" #include #include #include @@ -41,6 +42,8 @@ #define SUCCESS_MISSION(type) ret.back().miss = type +#define dbg(x) dout((DebugLevel)(x),D_GAME) << __FILE__ << ":" << __LINE__ << ": " + std::string dynamic_line(talk_topic topic, game *g, npc *p); std::vector gen_responses(talk_topic topic, game *g, npc *p); int topic_category(talk_topic topic); @@ -297,7 +300,8 @@ std::string dynamic_line(talk_topic topic, game *g, npc *p) case ENGAGE_ALL: status << "engaging all enemies."; break; } status << " " << (p->male ? "He" : "She") << " will " << - (p->combat_rules.use_guns ? "" : "not ") << "use firearms."; + (p->combat_rules.use_guns ? (p->combat_rules.use_silent ? "use silenced" : "use") + : "not use") << " firearms"; status << " " << (p->male ? "He" : "She") << " will " << (p->combat_rules.use_grenades ? "" : "not ") << "use grenades."; @@ -928,6 +932,11 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) RESPONSE("Let's trade items."); SUCCESS(TALK_NONE); SUCCESS_ACTION(&talk_function::start_trade); + if (p->is_following() && g->m.camp_at(g->u.posx, g->u.posy)) { + RESPONSE("Wait at this base,"); + SUCCESS(TALK_DONE); + SUCCESS_ACTION(&talk_function::assign_base); + } RESPONSE("Let's go."); SUCCESS(TALK_DONE); break; @@ -944,6 +953,15 @@ std::vector gen_responses(talk_topic topic, game *g, npc *p) SUCCESS(TALK_COMBAT_COMMANDS); SUCCESS_ACTION(&talk_function::toggle_use_guns); } + if (p->combat_rules.use_silent) { + RESPONSE("Don't worry about noise."); + SUCCESS(TALK_COMBAT_COMMANDS); + SUCCESS_ACTION(&talk_function::toggle_use_silent); + } else { + RESPONSE("Use only silent weapons."); + SUCCESS(TALK_COMBAT_COMMANDS); + SUCCESS_ACTION(&talk_function::toggle_use_silent); + } if (p->combat_rules.use_grenades) { RESPONSE("Don't use grenades anymore."); SUCCESS(TALK_COMBAT_COMMANDS); @@ -1287,6 +1305,20 @@ void talk_function::start_trade(game *g, npc *p) trade(g, p, trade_amount, "Trade"); } +void talk_function::assign_base(game *g, npc *p) +{ + // TODO: decide what to do upon assign? maybe pathing required + basecamp* camp = g->m.camp_at(g->u.posx, g->u.posy); + if(!camp) { + dbg(D_ERROR) << "talk_function::assign_base: Assigned to base but no base here."; + return; + } + + g->add_msg("%s waits at %s", p->name.c_str(), camp->camp_name().c_str()); + p->mission = NPC_MISSION_BASE; + p->attitude = NPCATT_NULL; +} + void talk_function::give_equipment(game *g, npc *p) { std::vector giving; @@ -1401,6 +1433,11 @@ void talk_function::toggle_use_guns(game *g, npc *p) p->combat_rules.use_guns = !p->combat_rules.use_guns; } +void talk_function::toggle_use_silent(game *g, npc *p) +{ + p->combat_rules.use_silent = !p->combat_rules.use_silent; +} + void talk_function::toggle_use_grenades(game *g, npc *p) { p->combat_rules.use_grenades = !p->combat_rules.use_grenades; diff --git a/vehicle.cpp b/vehicle.cpp index f1625b46c5dd1..ceeff0db910f2 100644 --- a/vehicle.cpp +++ b/vehicle.cpp @@ -661,6 +661,15 @@ std::vector vehicle::boarded_parts() return res; } +int vehicle::free_seat() +{ + for (int p = 0; p < parts.size(); p++) + if (part_flag (p, vpf_seat) && !parts[p].has_flag(vehicle_part::passenger_flag)) + return p; + + return -1; +} + player *vehicle::get_passenger (int p) { p = part_with_feature (p, vpf_seat, false); @@ -1185,11 +1194,14 @@ veh_collision vehicle::part_collision (int vx, int vy, int part, int x, int y) bool u_here = x == g->u.posx && y == g->u.posy && !g->u.in_vehicle; monster *z = mondex >= 0? &g->z[mondex] : 0; player *ph = (npcind >= 0? &g->active_npc[npcind] : (u_here? &g->u : 0)); + + if (ph && ph->in_vehicle) // if in a vehicle assume it's this one + ph = 0; + int target_part = -1; vehicle *oveh = g->m.veh_at (x, y, target_part); bool is_veh_collision = oveh && (oveh->posx != posx || oveh->posy != posy); - bool is_body_collision = (g->u.posx == x && g->u.posy == y && !g->u.in_vehicle) || - mondex >= 0 || npcind >= 0; + bool is_body_collision = ph || mondex >= 0; veh_coll_type collision_type = veh_coll_nothing; std::string obs_name = g->m.tername(x, y).c_str(); diff --git a/vehicle.h b/vehicle.h index 174b146342730..9f66b3927cef7 100644 --- a/vehicle.h +++ b/vehicle.h @@ -229,6 +229,7 @@ class vehicle // get a list of part indeces where is a passenger inside std::vector boarded_parts(); + int free_seat(); // get passenger at part p player *get_passenger (int p);