From a182c61a50d5104bbe562ea67800b7caded9caa8 Mon Sep 17 00:00:00 2001 From: ALONSO Laurent Date: Sun, 23 Jan 2022 14:05:56 +0100 Subject: [PATCH] all: add an optional junk bag game: start implementing basic functions: give/drop/information start to implement the identify spell which by goal works weaker for an object in the junk bag Notes: - you can always add an item in the junk bag. - you can only give/drop an item in the junk bag if you are in a "friendly" city (or if you are in the same city where you put the item in the bag : to avoid making some impossible scenarios). - the identification spell only "works" if you are in a "friendly" city. --- rsrc/dialogs/get-items.xml | 1 + rsrc/dialogs/select-pc.xml | 6 +- src/game/boe.actions.cpp | 17 ++- src/game/boe.consts.hpp | 1 + src/game/boe.dlgutil.cpp | 10 +- src/game/boe.fileio.cpp | 1 + src/game/boe.infodlg.cpp | 4 +- src/game/boe.infodlg.hpp | 3 +- src/game/boe.items.cpp | 235 +++++++++++++++++++++++-------------- src/game/boe.items.hpp | 5 + src/game/boe.locutils.cpp | 37 ++---- src/game/boe.party.cpp | 38 ++++++ src/game/boe.specials.cpp | 17 ++- src/game/boe.specials.hpp | 8 ++ src/game/boe.text.cpp | 72 ++++++++++-- src/game/boe.text.hpp | 2 + src/universe/party.cpp | 156 +++++++++++++++++++++++- src/universe/party.hpp | 19 ++- src/universe/universe.cpp | 15 +++ 19 files changed, 500 insertions(+), 147 deletions(-) diff --git a/rsrc/dialogs/get-items.xml b/rsrc/dialogs/get-items.xml index 3eeccd36..4fdeacbc 100644 --- a/rsrc/dialogs/get-items.xml +++ b/rsrc/dialogs/get-items.xml @@ -9,6 +9,7 @@ + @@ -9,12 +9,14 @@ + + Bag Select a PC: - + diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 9410a05f..ab61765c 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -651,7 +651,7 @@ static void handle_drop_item(location destination, bool& need_redraw) { add_string_to_buf("Drop: must be adjacent."); else if(sight_obscurity(destination.x,destination.y) == 5) ASB("Drop: Space is blocked."); - else drop_item(univ.cur_pc,store_drop_item,destination); + else drop_item(stat_window == ITEM_WIN_JUNK ? 7 : univ.cur_pc,store_drop_item,destination); overall_mode = MODE_TOWN; } need_redraw = true; @@ -796,7 +796,7 @@ static void handle_give_item(short item_hit, bool& did_something, bool& need_red add_string_to_buf("Give item: Finish what you're doing first."); return; } - give_thing(stat_window, item_hit); + give_thing(stat_window!=ITEM_WIN_JUNK ? stat_window : 7, item_hit); did_something = true; need_redraw = true; take_ap(1); @@ -1367,7 +1367,7 @@ bool handle_action(const sf::Event& event) { break; } } - if(stat_window <= ITEM_WIN_QUESTS) { + if(stat_window <= ITEM_WIN_JUNK) { for(int i = 0; i < 8; i++) for(auto j : item_buttons[i].keys()) if(item_area_button_active[i][j] && point_in_area.in(item_buttons[i][j])) { @@ -1399,7 +1399,10 @@ bool handle_action(const sf::Event& event) { put_spec_item_info(spec_item_array[item_hit]); else if(stat_window == ITEM_WIN_QUESTS) put_quest_info(spec_item_array[item_hit]); - else display_pc_item(stat_window, item_hit,univ.party[stat_window].items[item_hit],0); + else if (stat_window == ITEM_WIN_JUNK) + display_pc_item(stat_window, item_hit,univ.party.get_junk_item(item_hit),0); + else + display_pc_item(stat_window, item_hit,univ.party[stat_window].items[item_hit],0); break; case ITEMBTN_SPEC: // sell? That this code was reached indicates that the item was sellable // (Based on item_area_button_active) @@ -1768,6 +1771,11 @@ bool handle_keystroke(const sf::Event& event){ case '1': case '2': case '3': case '4': case '5': case '6': handle_switch_pc(((short) chr) - 49, need_redraw, need_reprint); break; + + case '7': // Junk + if (univ.party.show_junk_bag) + set_stat_window(ITEM_WIN_JUNK); + break; case '9': // Special items set_stat_window(ITEM_WIN_SPECIAL); @@ -2708,6 +2716,7 @@ void start_new_game(bool force) { // use user's easy mode and less wandering mode univ.party.easy_mode=get_bool_pref("EasyMode", false); univ.party.less_wm=get_bool_pref("LessWanderingMonsters", false); + univ.party.show_junk_bag=get_bool_pref("ShowJunkBag", false); if(force) return; fs::path file = nav_put_party(); if(!file.empty()) save_party(file, univ); diff --git a/src/game/boe.consts.hpp b/src/game/boe.consts.hpp index 6aac9e90..6bb20a99 100644 --- a/src/game/boe.consts.hpp +++ b/src/game/boe.consts.hpp @@ -138,6 +138,7 @@ enum eItemWinMode { ITEM_WIN_PC6 = 5, ITEM_WIN_SPECIAL = 6, ITEM_WIN_QUESTS = 7, + ITEM_WIN_JUNK = 8, }; // Gobal window rects diff --git a/src/game/boe.dlgutil.cpp b/src/game/boe.dlgutil.cpp index 97ce5b75..ba66267d 100644 --- a/src/game/boe.dlgutil.cpp +++ b/src/game/boe.dlgutil.cpp @@ -1202,17 +1202,13 @@ static bool prefs_event_filter (cDialog& me, std::string id, eKeyMod) { if (overall_mode != MODE_STARTUP && party_in_memory) { univ.party.easy_mode = dynamic_cast(me["easier"]).getState() != led_off; univ.party.less_wm = dynamic_cast(me["lesswm"]).getState() != led_off; + univ.party.show_junk_bag = dynamic_cast(me["junk"]).getState() != led_off; } set_pref("DrawTerrainAnimation", dynamic_cast(me["noanim"]).getState() == led_off); set_pref("DrawTerrainShoreFrills", dynamic_cast(me["noshore"]).getState() == led_off); set_pref("ShowStartupSplash", dynamic_cast(me["skipsplash"]).getState() == led_off); + set_pref("ShowJunkBag", dynamic_cast(me["junk"]).getState() != led_off); std::string speed = dynamic_cast(me["speed"]).getSelected(); - /* TODO: Should I add these additional preferences from Windows? - party.stuff_done[SDF_NO_TARGET_LINE] = cd_get_led(1099,50); - party.stuff_done[SDF_LESS_SOUND] = cd_get_led(1099,52); - party.stuff_done[SDF_FASTER_BOOM_SPACES] = cd_get_led(1099,56); - party.stuff_done[SDF_ASK_ABOUT_TEXT_BOX] = cd_get_led(1099,60); - */ if(speed == "fast") set_pref("GameSpeed", 0); else if(speed == "med") @@ -1286,9 +1282,11 @@ void pick_preferences() { if(overall_mode == MODE_STARTUP && !party_in_memory) { dynamic_cast(prefsDlog["easier"]).setState(get_bool_pref("EasyMode") ? led_red : led_off); dynamic_cast(prefsDlog["lesswm"]).setState(get_bool_pref("LessWanderingMonsters") ? led_red : led_off); + dynamic_cast(prefsDlog["junk"]).setState(get_bool_pref("ShowJunkBag", false) ? led_red : led_off); } else { dynamic_cast(prefsDlog["easier"]).setState(univ.party.easy_mode ? led_red : led_off); dynamic_cast(prefsDlog["lesswm"]).setState(univ.party.less_wm ? led_red : led_off); + dynamic_cast(prefsDlog["junk"]).setState(univ.party.show_junk_bag ? led_red : led_off); } dynamic_cast(prefsDlog["noanim"]).setState(get_bool_pref("DrawTerrainAnimations", true) ? led_off : led_red); dynamic_cast(prefsDlog["noshore"]).setState(get_bool_pref("DrawTerrainShoreFrills", true) ? led_off : led_red); diff --git a/src/game/boe.fileio.cpp b/src/game/boe.fileio.cpp index 497a3012..82c01de1 100644 --- a/src/game/boe.fileio.cpp +++ b/src/game/boe.fileio.cpp @@ -60,6 +60,7 @@ void finish_load_party(){ // use user's easy mode and less wandering mode univ.party.easy_mode=get_bool_pref("EasyMode", false); univ.party.less_wm=get_bool_pref("LessWanderingMonsters", false); + univ.party.show_junk_bag=get_bool_pref("ShowJunkBag", false); // now if not in scen, this is it. if(!in_scen) { diff --git a/src/game/boe.infodlg.cpp b/src/game/boe.infodlg.cpp index c0a939cc..21584d4a 100644 --- a/src/game/boe.infodlg.cpp +++ b/src/game/boe.infodlg.cpp @@ -202,10 +202,10 @@ static bool display_pc_item_event_filter(cDialog& me, std::string item_hit, cIte return true; } -void display_pc_item(short pc_num,short item,cItem si,cDialog* parent) { +void display_pc_item(short pc_num,short item,cItem const &si,cDialog* parent) { using namespace std::placeholders; cItem store_i; - if(pc_num == 6) + if(pc_num == 6 || pc_num == ITEM_WIN_JUNK) store_i = si; else store_i = univ.party[pc_num].items[item]; set_cursor(sword_curs); diff --git a/src/game/boe.infodlg.hpp b/src/game/boe.infodlg.hpp index 933b80fe..d410ff37 100644 --- a/src/game/boe.infodlg.hpp +++ b/src/game/boe.infodlg.hpp @@ -4,13 +4,14 @@ #include "item.hpp" #include "monster.hpp" +#include "party.hpp" #include "pc.hpp" #include "creature.hpp" class cDialog; void display_spells(eSkill mode,short force_spell,cDialog* parent); void display_skills(eSkill force_skill,cDialog* parent); -void display_pc_item(short pc_num,short item,class cItem si,cDialog* parent); +void display_pc_item(short pc_num,short item,class cItem const &si,cDialog* parent); void display_monst(short array_pos,cCreature *which_m,short mode); void display_alchemy(); void display_traits_graphics(); diff --git a/src/game/boe.items.cpp b/src/game/boe.items.cpp index a8713940..f7709863 100644 --- a/src/game/boe.items.cpp +++ b/src/game/boe.items.cpp @@ -102,59 +102,59 @@ void equip_item(short pc_num,short item_num) { void drop_item(short pc_num,short item_num,location where_drop) { - short s1, s2; - int spec = -1; - std::string choice; - short how_many = 1; - cItem item_store; - bool take_given_item = true, need_redraw = false; - location loc; - - item_store = univ.party[pc_num].items[item_num]; - if(item_store.ability == eItemAbil::DROP_CALL_SPECIAL) - spec = item_store.abil_data[0]; - - if(univ.party[pc_num].equip[item_num] && univ.party[pc_num].items[item_num].cursed) - add_string_to_buf("Drop: Item is cursed."); - else switch(overall_mode) { - case MODE_OUTDOORS: - choice = cChoiceDlog("drop-item-confirm",{"okay","cancel"}).show(); - if(choice == "cancel") - return; - add_string_to_buf("Drop: OK"); - if((item_store.type_flag > 0) && (item_store.charges > 1)) { - how_many = get_num_of_items(item_store.charges); - if(how_many == item_store.charges) - univ.party[pc_num].take_item(item_num); - else univ.party[pc_num].items[item_num].charges -= how_many; - } - else univ.party[pc_num].take_item(item_num); - break; - - case MODE_DROP_TOWN: case MODE_DROP_COMBAT: - loc = where_drop; - if((item_store.type_flag > 0) && (item_store.charges > 1)) { - how_many = get_num_of_items(item_store.charges); - if(how_many <= 0) - return; - if(how_many < item_store.charges) - take_given_item = false; - item_store.charges = how_many; - } - if(place_item(item_store,loc,true)) { - add_string_to_buf("Drop: Item put away"); - spec = -1; // Don't call drop specials if it was put away - } else add_string_to_buf("Drop: OK"); - univ.party[pc_num].items[item_num].charges -= how_many; - if(take_given_item) - univ.party[pc_num].take_item(item_num); - break; - default: //should never be reached - break; + if (pc_num>7) { + add_string_to_buf("Drop: Unexpected pc."); + return; } - if(spec >= 0) - while(how_many--) - run_special(eSpecCtx::DROP_ITEM, eSpecCtxType::SCEN, spec, where_drop, &s1, &s2, &need_redraw); + + cItem &item_to_drop = pc_num<=6 ? univ.party[pc_num].items[item_num] : univ.party.get_junk_item(item_num); + if(pc_num<6 && univ.party[pc_num].equip[item_num] && item_to_drop.cursed) { + add_string_to_buf("Drop: Item is cursed."); + return; + } + if (overall_mode== MODE_OUTDOORS) { + std::string choice = cChoiceDlog("drop-item-confirm",{"okay","cancel"}).show(); + if(choice == "cancel") + return; + add_string_to_buf("Drop: OK"); + } + + cItem item_store = item_to_drop; + int spec = -1; + if(item_to_drop.ability == eItemAbil::DROP_CALL_SPECIAL) + spec = item_to_drop.abil_data[0]; + + short how_many = 1; + bool take_given_item = true; + if(item_to_drop.type_flag > 0 && item_to_drop.charges > 1) { + how_many = get_num_of_items(item_to_drop.charges); + if(how_many <= 0) + return; + if(how_many < item_to_drop.charges) + take_given_item = false; + item_store.charges = how_many; + item_to_drop.charges -= how_many; + } + if (overall_mode==MODE_DROP_TOWN || overall_mode==MODE_DROP_COMBAT) { + if (place_item(item_store,where_drop,true)) { + add_string_to_buf("Drop: Item put away"); + spec = -1; // Don't call drop specials if it was put away + } + else + add_string_to_buf("Drop: OK"); + } + if(take_given_item) { + if (pc_num<=6) + univ.party[pc_num].take_item(item_num); + else + univ.party.take_junk_item(item_num); + } + if(spec < 0) return; + + bool need_redraw = false; + short s1, s2; + while(how_many--) + run_special(eSpecCtx::DROP_ITEM, eSpecCtxType::SCEN, spec, where_drop, &s1, &s2, &need_redraw); if(need_redraw) draw_terrain(0); } @@ -183,41 +183,56 @@ bool place_item(cItem item,location where,bool contained) { } void give_thing(short pc_num, short item_num) { + if (pc_num>7) { + add_string_to_buf("Give: Unexpected pc."); + return; + } short who_to,how_many = 0; cItem item_store; bool take_given_item = true; - if(univ.party[pc_num].equip[item_num] && univ.party[pc_num].items[item_num].cursed) + cItem &item_to_give=pc_num==7 ? univ.party.get_junk_item(item_num) : univ.party[pc_num].items[item_num]; + if(pc_num<=6 && univ.party[pc_num].equip[item_num] && item_to_give.cursed) add_string_to_buf("Give: Item is cursed."); else { - item_store = univ.party[pc_num].items[item_num]; - who_to = char_select_pc(3,"Give item to who?"); - if((overall_mode == MODE_COMBAT) && !adjacent(univ.party[pc_num].combat_pos,univ.party[who_to].combat_pos)) { + item_store = item_to_give; + if (univ.party.show_junk_bag && pc_num!=7) + who_to = char_select_pc(4, "Give item to who? (type '7' for Bag)"); + else + who_to = char_select_pc(3, "Give item to who?"); + if(overall_mode == MODE_COMBAT && who_to < 6 && !adjacent(univ.party[pc_num].combat_pos,univ.party[who_to].combat_pos)) { add_string_to_buf("Give: Must be adjacent."); who_to = 6; } - - if((who_to < 6) && (who_to != pc_num) - && ((overall_mode != MODE_COMBAT) || (adjacent(univ.party[pc_num].combat_pos,univ.party[who_to].combat_pos)))) { - if((item_store.type_flag > 0) && (item_store.charges > 1)) { - how_many = get_num_of_items(item_store.charges); + else if ((who_to < 6 || who_to == 7) && who_to != pc_num) { + if((item_to_give.type_flag > 0) && (item_to_give.charges > 1)) { + how_many = get_num_of_items(item_to_give.charges); if(how_many == 0) return; - if(how_many < item_store.charges) + if(how_many < item_to_give.charges) take_given_item = false; - univ.party[pc_num].items[item_num].charges -= how_many; + item_to_give.charges -= how_many; item_store.charges = how_many; } - if(univ.party[who_to].give_item(item_store,0)) { - if(take_given_item) - univ.party[pc_num].take_item(item_num); - } + if ((who_to==7 && univ.party.give_junk_item(item_store, is_town() ? univ.party.town_num : 200)) || + (who_to<6 && univ.party[who_to].give_item(item_store,0))) + ; // ok else { - if(!univ.party[who_to].has_space()) + if(who_to<6 && !univ.party[who_to].has_space()) ASB("Can't give: PC has max. # of items."); - else ASB("Can't give: PC carrying too much."); + else if(who_to<6) + ASB("Can't give: PC carrying too much."); + else + ASB("Can't give: unknown problem."); if(how_many > 0) - univ.party[pc_num].items[item_num].charges += how_many; + item_to_give.charges += how_many; + take_given_item = false; + } + if(take_given_item) { + if (pc_num<=6) + univ.party[pc_num].take_item(item_num); + else + univ.party.take_junk_item(item_num); } } } @@ -293,6 +308,24 @@ short get_item(location place,short pc_num,bool check_container) { } +bool is_town_hostile() +{ + if (!is_town()) + return true; + if (univ.town.monst.hostile) + return true; + int numFriendly=0, numHostile=0; + for (auto const &monster : univ.town.monst) { + if (!monster.active || monster.summon_time>0) + continue; + if (monster.is_friendly()) + ++numFriendly; + else + ++numHostile; + } + return numHostile>=numFriendly; +} + void make_town_hostile() { set_town_attitude(0, -1, eAttitude::HOSTILE_A); return; @@ -364,15 +397,16 @@ static void put_item_graphics(cDialog& me, size_t& first_item_shown, short& curr if(current_getting_pc < 6 && (univ.party[current_getting_pc].main_status != eMainStatus::ALIVE || !univ.party[current_getting_pc].has_space())) { current_getting_pc = 6; - } + else if (!univ.party.show_junk_bag && current_getting_pc==7) + current_getting_pc = 6; for(short i = 0; i < 6; i++) { std::ostringstream sout; sout << "pc" << i + 1; std::string id = sout.str(); if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].has_space() - && ((!is_combat()) || (univ.cur_pc == i))) { + && (!is_combat() || univ.cur_pc == i)) { if(current_getting_pc == 6) current_getting_pc = i; me[id].show(); @@ -381,11 +415,10 @@ static void put_item_graphics(cDialog& me, size_t& first_item_shown, short& curr sout << "-g"; me[sout.str()].hide(); } - if(current_getting_pc == i) - me.addLabelFor(id, "* ", LABEL_LEFT, 7, true); - else me.addLabelFor(id," ", LABEL_LEFT, 7, true); + me.addLabelFor(id, current_getting_pc == i ? "* " : " ", LABEL_LEFT, 7, true); } - + if (univ.party.show_junk_bag) + me.addLabelFor("junk", current_getting_pc == 7 ? "* " : " ", LABEL_LEFT, 7, true); if (first_item_shown >= item_array.size()) first_item_shown=std::max(size_t(8),item_array.size())-8; // darken arrows, as appropriate @@ -446,9 +479,9 @@ static void put_item_graphics(cDialog& me, size_t& first_item_shown, short& curr static bool display_item_event_filter(cDialog& me, std::string id, size_t& first_item_shown, short& current_getting_pc, std::vector& item_array, bool allow_overload) { cItem item; - if(id == "done") { + if(id == "done") me.toast(true); - } else if(id == "up") { + else if(id == "up") { if(first_item_shown > 0) { first_item_shown -= 8; put_item_graphics(me, first_item_shown, current_getting_pc, item_array); @@ -461,7 +494,11 @@ static bool display_item_event_filter(cDialog& me, std::string id, size_t& first } else if(id.substr(0,2) == "pc") { current_getting_pc = id[2] - '1'; put_item_graphics(me, first_item_shown, current_getting_pc, item_array); - } else if(id.substr(0,4) == "item" && id.length()==10 && id.substr(5,5)=="-info") { + } else if (id == "junk") { + current_getting_pc = 7; + put_item_graphics(me, first_item_shown, current_getting_pc, item_array); + } + else if(id.substr(0,4) == "item" && id.length()==10 && id.substr(5,5)=="-info") { size_t item_hit; item_hit = id[4] - '1'; item_hit += first_item_shown; @@ -507,7 +544,7 @@ static bool display_item_event_filter(cDialog& me, std::string id, size_t& first univ.party.active_quests[item.item_level].source = -1; set_item_flag(&item); } else { - if(!allow_overload && item.item_weight() > univ.party[current_getting_pc].free_weight()) { + if(current_getting_pc<6 && !allow_overload && item.item_weight() > univ.party[current_getting_pc].free_weight()) { beep(); // TODO: This is a game event, so it should have a game sound, not a system alert. me["prompt"].setText("It's too heavy to carry."); give_help(38,0,me); @@ -516,10 +553,14 @@ static bool display_item_event_filter(cDialog& me, std::string id, size_t& first set_item_flag(&item); play_sound(0); // formerly force_play_sound - int flags = GIVE_DO_PRINT; - if(allow_overload) - flags |= GIVE_ALLOW_OVERLOAD; - univ.party[current_getting_pc].give_item(item, flags); + if (current_getting_pc<6) { + int flags = GIVE_DO_PRINT; + if(allow_overload) + flags |= GIVE_ALLOW_OVERLOAD; + univ.party[current_getting_pc].give_item(item, flags); + } + else + univ.party.give_junk_item(item, is_combat() ? -1 : is_town() ? univ.party.town_num : 200); } *item_array[item_hit] = cItem(); item_array.erase(item_array.begin() + item_hit); @@ -557,7 +598,7 @@ bool display_item(location from_loc,short /*pc_num*/,short mode, bool check_cont else if(mode == 0) stole_something = show_get_items("Getting all adjacent items:", item_array, current_getting_pc); else stole_something = show_get_items("Getting all nearby items:", item_array, current_getting_pc); - + if (stat_window==ITEM_WIN_JUNK) set_stat_window(stat_window); // FIXME MUST NO BE HERE put_item_screen(stat_window); put_pc_screen(); @@ -571,7 +612,7 @@ bool show_get_items(std::string titleText, std::vector& itemRefs, short cDialog itemDialog("get-items"); auto handler = std::bind(display_item_event_filter, _1, _2, std::ref(first_item), std::ref(pc_getting), std::ref(itemRefs), overload); itemDialog.attachClickHandlers(handler, {"done", "up", "down"}); - itemDialog.attachClickHandlers(handler, {"pc1", "pc2", "pc3", "pc4", "pc5", "pc6"}); + itemDialog.attachClickHandlers(handler, {"pc1", "pc2", "pc3", "pc4", "pc5", "pc6", "junk"}); itemDialog.setResult(false); cTextMsg& title = dynamic_cast(itemDialog["title"]); @@ -586,6 +627,10 @@ bool show_get_items(std::string titleText, std::vector& itemRefs, short sout << "item" << i << "-info"; itemDialog[sout.str()].attachClickHandler(handler); } + if (univ.party.show_junk_bag) + itemDialog["junk"].show(); + else + itemDialog["junk"].hide(); put_item_graphics(itemDialog, first_item, pc_getting, itemRefs); void (*give_help)(short,short,cDialog&) = ::give_help; @@ -881,7 +926,9 @@ short get_num_response(short min, short max, std::string prompt) { static bool select_pc_event_filter (cDialog& me, std::string item_hit, eKeyMod) { me.toast(true); - if(item_hit != "cancel") { + if (item_hit == "junk") + me.setResult(7); + else if(item_hit != "cancel") { short which_pc = item_hit[item_hit.length() - 1] - '1'; me.setResult(which_pc); } else me.setResult(6); @@ -889,14 +936,14 @@ static bool select_pc_event_filter (cDialog& me, std::string item_hit, eKeyMod) } // mode determines which PCs can be picked -// 0 - only living pcs, 1 - any pc, 2 - only dead pcs, 3 - only living pcs with inventory space +// 0 - only living pcs, 1 - any pc, 2 - only dead pcs, 3 - only living pcs with inventory space, 4 - only living pcs with inventory space or junk bag short char_select_pc(short mode,const char *title) { short item_hit; set_cursor(sword_curs); cDialog selectPc("select-pc"); - selectPc.attachClickHandlers(select_pc_event_filter, {"cancel", "pick1", "pick2", "pick3", "pick4", "pick5", "pick6"}); + selectPc.attachClickHandlers(select_pc_event_filter, {"cancel", "pick1", "pick2", "pick3", "pick4", "pick5", "pick6", "junk"}); selectPc["title"].setText(title); @@ -907,6 +954,7 @@ short char_select_pc(short mode,const char *title) { can_pick = false; else switch(mode) { case 3: + case 4: if(!univ.party[i].has_space()) can_pick = false; // Fallthrough intentional @@ -926,7 +974,14 @@ short char_select_pc(short mode,const char *title) { selectPc["pc" + n].setText(univ.party[i].name); } } - + if (mode==4) { + selectPc["junk"].show(); + selectPc["junk-text"].show(); + } + else { + selectPc["junk"].hide(); + selectPc["junk-text"].hide(); + } selectPc.run(); item_hit = selectPc.getResult(); diff --git a/src/game/boe.items.hpp b/src/game/boe.items.hpp index 6ee0de5f..2e622dde 100644 --- a/src/game/boe.items.hpp +++ b/src/game/boe.items.hpp @@ -1,7 +1,11 @@ #include "dialog.hpp" +#include "item.hpp" +#include "monster.hpp" #include "pict.hpp" +enum class eSpecCtxType; + bool GTP(short item_num); bool silent_GTP(short item_num); void give_gold(short amount,bool print_result); @@ -16,6 +20,7 @@ short dist_from_party(location where); void set_item_flag(cItem *item); short get_item(location place,short pc_num,bool check_container); +bool is_town_hostile(); void make_town_hostile(); void set_town_attitude(short lo,short hi,eAttitude att); bool show_get_items(std::string titleText, std::vector& itemRefs, short pc_getting, bool overload = false); diff --git a/src/game/boe.locutils.cpp b/src/game/boe.locutils.cpp index 1821b6f3..bcd5129f 100644 --- a/src/game/boe.locutils.cpp +++ b/src/game/boe.locutils.cpp @@ -47,36 +47,21 @@ void make_explored(short i,short j, short val) { } } +static bool is_out(eGameMode overall_mode) { + return overall_mode == MODE_OUTDOORS || overall_mode == MODE_LOOK_OUTDOORS; +} bool is_out() { - if((overall_mode == MODE_OUTDOORS) || (overall_mode == MODE_LOOK_OUTDOORS)) - return true; - else if(overall_mode == MODE_SHOPPING) { - std::swap(overall_mode, store_pre_shop_mode); - bool ret = is_out(); - std::swap(overall_mode, store_pre_shop_mode); - return ret; - } else if(overall_mode == MODE_TALKING) { - std::swap(overall_mode, store_pre_talk_mode); - bool ret = is_out(); - std::swap(overall_mode, store_pre_talk_mode); - return ret; - } else return false; + return is_out(overall_mode) || (overall_mode == MODE_SHOPPING && is_out(store_pre_shop_mode)) || + (overall_mode == MODE_TALKING && is_out(store_pre_talk_mode)); } +static bool is_town(eGameMode overall_mode) { + return (overall_mode > MODE_OUTDOORS && overall_mode < MODE_COMBAT) || overall_mode == MODE_LOOK_TOWN; +} bool is_town() { - if((overall_mode > MODE_OUTDOORS && overall_mode < MODE_COMBAT) || overall_mode == MODE_LOOK_TOWN || cartoon_happening) - return true; - else if(overall_mode == MODE_SHOPPING) { - std::swap(overall_mode, store_pre_shop_mode); - bool ret = is_town(); - std::swap(overall_mode, store_pre_shop_mode); - return ret; - } else if(overall_mode == MODE_TALKING) { - std::swap(overall_mode, store_pre_talk_mode); - bool ret = is_town(); - std::swap(overall_mode, store_pre_talk_mode); - return ret; - } else return false; + return is_town(overall_mode) || cartoon_happening || + (overall_mode == MODE_SHOPPING && is_town(store_pre_shop_mode)) || + (overall_mode == MODE_TALKING && is_town(store_pre_talk_mode)); } bool is_combat() { diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index f815a7fe..58e551fc 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -142,6 +142,20 @@ void put_party_in_scen(std::string scen_name) { else thisItem.special_class = 0; } + for (size_t i = univ.party.junk_items.size(); i>0; i--) { + cItem & thisItem = univ.party.junk_items[i-1].first; + univ.party.junk_items[i-1].second.clear(); + if (thisItem.variety == eItemType::NO_ITEM) + continue; + if (is_item_specific_to_scenario(thisItem)) { + if (i!=univ.party.junk_items.size()) + std::swap(univ.party.junk_items[i-1], univ.party.junk_items.back()); + univ.party.junk_items.pop_back(); + item_took = true; + } + else + thisItem.special_class = 0; + } if(item_took) cChoiceDlog("removed-special-items").show(); @@ -596,6 +610,30 @@ void do_mage_spell(short pc_num,eSpell spell_num,bool freebie) { for(cPlayer& pc : univ.party) for(cItem& item : pc.items) item.ident = true; + if (univ.party.show_junk_bag && is_town() && !is_combat() && !is_town_hostile()) { + // now try to identify items in the junk bag, + // the junk bag is clearly a magic item so + // it must interfer with this spell + short numDone=0, numFailed=0; + for (auto &itemSet : univ.party.junk_items) { + if (itemSet.first.ident) continue; + if (get_ran(1, 0, 1)) { + ++numDone; + itemSet.first.ident=true; + } + else + ++numFailed; + } + if (numDone || numFailed) { + ASB(std::string("Junk Bag: ")+std::to_string(numDone)+'/'+std::to_string(numDone+numFailed) + +" items identified."); + } + if (numDone) { + univ.party.combine_junk_items(); + if (stat_window==ITEM_WIN_JUNK) + set_stat_window(ITEM_WIN_JUNK); + } + } break; case eSpell::TRUE_SIGHT: diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index f114ba4c..197def32 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -2223,9 +2223,19 @@ void general_spec(const runtime_state& ctx) { ctx.next_spec = spec.ex1b; break; case eSpecType::BUY_ITEMS_OF_TYPE: - for(short i = 0; i < 144; i++) - if(univ.party.check_class(spec.ex1a,true)) + for(short i = 0; i < 144; i++) { + if(!univ.party.check_class(spec.ex1a,true)) + break; + store_val++; + } + if (univ.party.show_junk_bag && is_town() && !is_combat()) { + int check_town_id = is_town_hostile() ? univ.party.town_num : -1; + for(short i = 0; i < 144; i++) { + if(store_val>=144 || !univ.party.check_junk_class(spec.ex1a,true,check_town_id)) + break; store_val++; + } + } if(store_val == 0) { if(spec.ex1b >= 0) ctx.next_spec = spec.ex1b; @@ -3351,6 +3361,9 @@ void ifthen_spec(const runtime_state& ctx) { case eSpecType::IF_HAVE_ITEM_CLASS: if(univ.party.check_class(spec.ex1a,spec.ex2a > 0)) ctx.next_spec = spec.ex1b; + else if (univ.party.show_junk_bag && is_town() && !is_combat() && + univ.party.check_junk_class(spec.ex1a,spec.ex2a > 0, is_town_hostile() ? univ.party.town_num : -1)) + ctx.next_spec = spec.ex1b; break; case eSpecType::IF_EQUIP_ITEM_CLASS: for(cPlayer& pc : univ.party) diff --git a/src/game/boe.specials.hpp b/src/game/boe.specials.hpp index a68a4347..64696aa3 100644 --- a/src/game/boe.specials.hpp +++ b/src/game/boe.specials.hpp @@ -1,6 +1,12 @@ +#ifndef BOE_GAME_SPECIALS_H +#define BOE_GAME_SPECIALS_H #include "creature.hpp" +struct pending_special_type; +enum class eSpecCtx; +enum class eSpecCtxType; + bool handle_wandering_specials(short mode); bool check_special_terrain(location where_check,eSpecCtx mode,cPlayer& which_pc,bool *forced); void check_fields(location where_check,eSpecCtx mode,cPlayer& which_pc); @@ -25,3 +31,5 @@ void run_special(pending_special_type spec, short* a, short* b, bool* redraw); void get_strs(std::string& str1, std::string& str2,eSpecCtxType cur_type,short which_str1,short which_str2); void set_campaign_flag(short sdf_a, short sdf_b, short cpf_a, short cpf_b, short str, bool get_send); + +#endif diff --git a/src/game/boe.text.cpp b/src/game/boe.text.cpp index 7d6a59e2..9dcf1b8c 100644 --- a/src/game/boe.text.cpp +++ b/src/game/boe.text.cpp @@ -7,21 +7,23 @@ const int TEXT_BUF_LEN = 70; #include "boe.global.hpp" #include "boe.graphutil.hpp" -#include "universe.hpp" +#include "boe.items.hpp" #include "boe.text.hpp" #include "boe.locutils.hpp" #include "boe.infodlg.hpp" + +#include "enum_map.hpp" #include "mathutil.hpp" #include "render_text.hpp" #include "render_image.hpp" #include "render_shapes.hpp" -#include "tiling.hpp" -#include "utility.hpp" -#include "scrollbar.hpp" -#include "res_image.hpp" #include "res_font.hpp" +#include "res_image.hpp" +#include "scrollbar.hpp" #include "spell.hpp" -#include "enum_map.hpp" +#include "tiling.hpp" +#include "universe.hpp" +#include "utility.hpp" typedef struct { char line[50]; @@ -243,7 +245,9 @@ void put_item_screen(eItemWinMode screen_num) { case ITEM_WIN_QUESTS: win_draw_string(item_stats_gworld,upper_frame_rect,"Quests/Jobs:",eTextMode::WRAP,style); break; - + case ITEM_WIN_JUNK: + win_draw_string(item_stats_gworld,upper_frame_rect,"Junk Bag:",eTextMode::WRAP,style); + break; default: // on an items page pc = screen_num; sout.str("");; @@ -291,7 +295,58 @@ void put_item_screen(eItemWinMode screen_num) { } } break; + case ITEM_WIN_JUNK: + style.colour = Colours::BLACK; + for(short i = 0; i < 8; i++) { + i_num = i + item_offset; + sout.str(""); + sout << i_num + 1 << '.'; + win_draw_string(item_stats_gworld,item_buttons[i][ITEMBTN_NAME],sout.str(),eTextMode::WRAP,style); + + dest_rect = item_buttons[i][ITEMBTN_NAME]; + dest_rect.left += 36; + dest_rect.top -= 2; + + const cItem& item = univ.party.get_junk_item(i_num); + if(item.variety != eItemType::NO_ITEM) { + style.font = FONT_PLAIN; + style.colour = Colours::BLACK; + + sout.str(""); + if(!item.ident) + sout << item.name << " "; + else { /// Don't place # of charges when Sell button up and space tight + sout << item.full_name << ' '; + if(item.charges > 0 && item.ability != eItemAbil::MESSAGE && (stat_screen_mode == MODE_INVEN || stat_screen_mode == MODE_SHOP)) + sout << '(' << int(item.charges) << ')'; + } + dest_rect.left -= 2; + win_draw_string(item_stats_gworld,dest_rect,sout.str(),eTextMode::WRAP,style); + style.italic = false; + style.colour = Colours::BLACK; + + place_item_graphic(i,item.graphic_num); + item_area_button_active[i][ITEMBTN_NAME] = item_area_button_active[i][ITEMBTN_ICON] = false; + place_item_button(3,i,ITEMBTN_INFO); // info button + + if(stat_screen_mode == MODE_INVEN && !is_combat()) { + // check if we need to add give and drop + int townId=is_town() ? univ.party.town_num : 200; + if (univ.party.is_junk_item_compatible_with_town(i_num, townId) || + (is_town() && !is_town_hostile())) { + place_item_button(1,i,ITEMBTN_GIVE); + if (is_town()) place_item_button(2,i,ITEMBTN_DROP); + } + } +#if 0 + if(stat_screen_mode != MODE_INVEN && stat_screen_mode != MODE_SHOP) { + place_buy_button(i,pc,i_num); + } +#endif + } // end of if item is there + } // end of for(short i = 0; i < 8; i++) + break; default: // on an items page style.colour = Colours::BLACK; @@ -569,6 +624,9 @@ void set_stat_window(eItemWinMode new_stat) { array_pos = max(0,array_pos - 8); item_sbar->setMaximum(array_pos); break; + case ITEM_WIN_JUNK: + item_sbar->setMaximum(max(8,univ.party.junk_items.size())-8); + break; default: item_sbar->setMaximum(16); break; diff --git a/src/game/boe.text.hpp b/src/game/boe.text.hpp index 5f030ff3..1b7ce1c9 100644 --- a/src/game/boe.text.hpp +++ b/src/game/boe.text.hpp @@ -1,5 +1,7 @@ #include +#include "outdoors.hpp" + class cVehicle; struct Texture; diff --git a/src/universe/party.cpp b/src/universe/party.cpp index e374401c..91959dd4 100644 --- a/src/universe/party.cpp +++ b/src/universe/party.cpp @@ -63,6 +63,7 @@ cParty::cParty(const cParty& other) , hostiles_present(other.hostiles_present) , easy_mode(other.easy_mode) , less_wm(other.less_wm) + , show_junk_bag(other.show_junk_bag) , magic_ptrs(other.magic_ptrs) , light_level(other.light_level) , outdoor_corner(other.outdoor_corner) @@ -106,6 +107,7 @@ cParty::cParty(const cParty& other) , scen_won(other.scen_won) , scen_played(other.scen_played) , campaign_flags(other.campaign_flags) + , junk_items(other.junk_items) , pointers(other.pointers) { memcpy(stuff_done, other.stuff_done, sizeof(stuff_done)); @@ -133,6 +135,7 @@ void cParty::swap(cParty& other) { std::swap(hostiles_present, other.hostiles_present); std::swap(easy_mode, other.easy_mode); std::swap(less_wm, other.less_wm); + std::swap(show_junk_bag, other.show_junk_bag); std::swap(magic_ptrs, other.magic_ptrs); std::swap(light_level, other.light_level); std::swap(outdoor_corner, other.outdoor_corner); @@ -177,6 +180,7 @@ void cParty::swap(cParty& other) { std::swap(scen_won, other.scen_won); std::swap(scen_played, other.scen_played); std::swap(campaign_flags, other.campaign_flags); + std::swap(junk_items, other.junk_items); std::swap(pointers, other.pointers); std::swap(stuff_done, other.stuff_done); std::swap(setup, other.setup); @@ -230,6 +234,104 @@ cMonster &cParty::get_summon(mon_num_t id) { return bad_monster; } +cItem const &cParty::get_junk_item(item_num_t id) const +{ + if (id0; + return false; +} + +static bool combine_items(cItem &item1, cItem const &item2) +{ + if (item1.variety != eItemType::NO_ITEM && item1.ident && item2.ident && + item2.variety != eItemType::NO_ITEM && item1.type_flag == item2.type_flag && + item1.name == item2.name && item1.special_class == item2.special_class) { + short test = item1.charges + item2.charges; + if(test > 125) { + item1.charges = 125; + if(cParty::print_result) + cParty::print_result("(Can have at most 125 of any item."); + } + else item1.charges += item2.charges; + return true; + } + return false; +} + +bool cParty::give_junk_item(cItem const &item, int townId) { + if(item.variety == eItemType::NO_ITEM) + return true; + if(item.variety == eItemType::GOLD || item.variety == eItemType::FOOD || item.variety == eItemType::QUEST || item.variety == eItemType::SPECIAL) { + if(print_result) + print_result("Trying to add unexpected items in junk bag."); + return false; + } + cItem fItem(item); + fItem.property = false; + fItem.contained = false; + fItem.held = false; + if (fItem.charges<0) fItem.charges=1; + if(fItem.type_flag > 0 && fItem.ident) { + for(auto &jItemSet : junk_items) { + if(combine_items(jItemSet.first, fItem)) { + if (townId>=0) jItemSet.second.insert(townId); + return true; + } + } + } + std::set listTowns; + if (townId>=0) listTowns.insert(townId); + junk_items.push_back(std::make_pair(fItem,listTowns)); + return true; +} + +void cParty::combine_junk_items() +{ + // FIXME: probably better to first construct a multimap of name => id, items for identify id + // then for each item which shares the same names check if we combine them and keep a set + // of item id to remove, then we can remove the item + for (size_t i=junk_items.size(); i>0; --i) { + auto const &item=junk_items[i-1].first; + if (!item.ident) continue; + for (size_t j=i-1; j>0; --j) { + if (!combine_items(junk_items[j-1].first, item)) + continue; + if (i!=j) + junk_items[j-1].second.insert(junk_items[i-1].second.begin(), junk_items[i-1].second.end()); + std::swap(junk_items[i-1],junk_items.back()); + junk_items.pop_back(); + break; + } + } +} + +void cParty::take_junk_item(item_num_t id) +{ + if (id>=junk_items.size()) { + if(print_result) + print_result("Trying to remove unexistant item in junk bag."); + return; + } + junk_items.erase(junk_items.begin()+id); +} + void cParty::import_legacy(legacy::party_record_type const & old, cUniverse& univ){ scen_name = old.scen_name; age = old.age; @@ -774,6 +876,26 @@ bool cParty::check_class(unsigned int item_class,bool take) { return false; } +bool cParty::check_junk_class(unsigned int item_class,bool take,int townId) { + if(!show_junk_bag || item_class == 0) + return false; + for (size_t i=0; i=0 && itemSet.second.count(townId)==0) + continue; + if (take) { + if(itemSet.first.charges > 1) + --itemSet.first.charges; + else + take_junk_item(i); + return true; + } + } + return false; +} + bool cParty::start_timer(short time, spec_num_t node, eSpecCtxType type){ if(party_event_timers.size() == party_event_timers.max_size()) return false; // Shouldn't be reached cTimer t; @@ -793,6 +915,7 @@ void cParty::writeTo(std::ostream& file) const { file << "HOSTILES " << int(hostiles_present) << '\n'; file << "EASY " << int(easy_mode) << '\n'; file << "LESSWM " << int(less_wm) << '\n'; + file << "JUNKBAG" << int(show_junk_bag) << "\n"; for(int i = 0; i < 310; i++) for(int j = 0; j < 50; j++) if(stuff_done[i][j] > 0) @@ -845,8 +968,8 @@ void cParty::writeTo(std::ostream& file) const { file << "PLAYED " << scen_played << '\n'; for(auto p : active_quests) file << "QUEST " << p.first << ' ' << p.second.status << ' ' << p.second.start << ' ' << p.second.source << '\n'; - for(auto p : store_limited_stock) { - for(auto p2 : p.second) { + for(auto const &p : store_limited_stock) { + for(auto const &p2 : p.second) { file << "SHOPSTOCK " << p.first << ' ' << p2.first << ' ' << p2.second << '\n'; } } @@ -876,6 +999,17 @@ void cParty::writeTo(std::ostream& file) const { } } file << '\f'; + for (size_t i=0; i> n; less_wm = n; + } else if(cur == "JUNKBAG") { + int n; + sin >> n; + show_junk_bag = n; } else if(cur == "CREATEVERSION") { unsigned long long version; sin >> std::hex >> version >> std::dec; @@ -1118,6 +1256,17 @@ void cParty::readFrom(std::istream& file){ int i,j; bin >> i >> j; magic_store_items[i][j].readFrom(bin); + } else if (cur == "JUNKITEM") { + junk_items.emplace_back(); + int n; + bin >> n; + for (int i=0; i> town; + if (town<0) break; + junk_items.back().second.insert(town); + } + junk_items.back().first.readFrom(bin); } else if(cur == "ENCOUNTER") { int i; bin >> i; @@ -1230,7 +1379,8 @@ void cParty::readFrom(std::istream& file){ } cPlayer& cParty::operator[](unsigned short n){ - if(n > 6) throw std::out_of_range("Attempt to access a player that doesn't exist."); + if(n > 6) + throw std::out_of_range("Attempt to access a player that doesn't exist."); else if(n == 6) return *adven[0]; // TODO: PC #6 should never be accessed, but bounds checking is rarely done, so this is a quick fix. return *adven[n]; diff --git a/src/universe/party.hpp b/src/universe/party.hpp index 6d7d2d73..2a987908 100644 --- a/src/universe/party.hpp +++ b/src/universe/party.hpp @@ -9,11 +9,12 @@ #ifndef BOE_DATA_PARTY_H #define BOE_DATA_PARTY_H -#include -#include #include #include #include +#include +#include +#include #include @@ -84,7 +85,7 @@ public: unsigned char stuff_done[350][50]; // These used to be stored as magic SDFs unsigned char hostiles_present; - bool easy_mode = false, less_wm = false; + bool easy_mode = false, less_wm = false, show_junk_bag=false; // End former magic SDFs std::array magic_ptrs; short light_level; @@ -130,6 +131,7 @@ public: std::vector summons; // an array of monsters which can be summoned by the party's items yet don't originate from this scenario unsigned short scen_won, scen_played; // numbers of scenarios won and played respectively by this party std::map campaign_flags; + std::vector>> junk_items; private: std::map> pointers; using sd_array = decltype(stuff_done); @@ -198,7 +200,8 @@ public: bool has_abil(eItemAbil abil, short dat = -1); bool take_abil(eItemAbil abil, short dat = -1); bool check_class(unsigned int item_class,bool take); - + bool check_junk_class(unsigned int item_class,bool take,int townId=-1); + bool start_split(short x, short y, snd_num_t noise, short who); bool end_split(snd_num_t noise); bool is_split() const; @@ -228,6 +231,14 @@ public: cVehicle const &get_horse(int id) const; cMonster &get_summon(mon_num_t id); cMonster const &get_summon(mon_num_t id) const; + + // junk item + cItem const &get_junk_item(item_num_t id) const; + cItem &get_junk_item(item_num_t id); + bool is_junk_item_compatible_with_town(item_num_t id, int townId) const; + bool give_junk_item(cItem const &item, int townId); + void take_junk_item(item_num_t id); + void combine_junk_items(); cParty(ePartyPreset party_preset = PARTY_DEFAULT); ~cParty(); diff --git a/src/universe/universe.cpp b/src/universe/universe.cpp index fdf6cd93..512ae6ee 100644 --- a/src/universe/universe.cpp +++ b/src/universe/universe.cpp @@ -1054,6 +1054,8 @@ void cUniverse::exportGraphics() { for (auto &items : party.stored_items) for (auto &item : items) state.check_item(*this, item); + for (auto &junk : party.junk_items) + state.check_item(*this, junk.first); for(mon_num_t monst : party.imprisoned_monst) { if(monst > 0 && monst < scenario.scen_monsters.size()) state.check_monst(*this, scenario.scen_monsters[monst]); @@ -1125,6 +1127,19 @@ void cUniverse::exportSummons() { } } } + for (auto &itemSet : party.junk_items) { + auto &item=itemSet.first; + if(item.variety == eItemType::NO_ITEM) continue; + if(item.ability == eItemAbil::SUMMONING||item.ability == eItemAbil::MASS_SUMMONING) { + mon_num_t monst = item.abil_data[1]; + if(monst >= 10000) + used_monsters.insert(monst - 10000); + else { + need_monsters.insert(monst); + update_items[monst].insert(&item); + } + } + } for(mon_num_t monst : party.imprisoned_monst) { if(monst == 0) continue; if(monst >= 10000)