From 712042adae501d32d66f9af4eed2004467ff2149 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 1 Aug 2025 11:33:22 -0500 Subject: [PATCH 01/59] loading scen headers, skip parts of legacy load that may error --- src/fileio/fileio_scen.cpp | 9 +++++---- src/scenario/scenario.cpp | 5 ++++- src/scenario/scenario.hpp | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/fileio/fileio_scen.cpp b/src/fileio/fileio_scen.cpp index 030c8e3e7..7aad56549 100644 --- a/src/fileio/fileio_scen.cpp +++ b/src/fileio/fileio_scen.cpp @@ -266,8 +266,11 @@ bool load_scenario_v1(fs::path file_to_load, cScenario& scenario, eLoadScenario return false; } port_item_list(&item_data); - scenario.import_legacy(temp_scenario); - scenario.import_legacy(item_data); + scenario.import_legacy(temp_scenario, load_type == eLoadScenario::ONLY_HEADER); + if(load_type == eLoadScenario::FULL){ + scenario.ter_types[23].fly_over = false; + scenario.import_legacy(item_data); + } // TODO: Consider skipping the fread and assignment when len is 0 scenario.special_items.resize(50); @@ -295,8 +298,6 @@ bool load_scenario_v1(fs::path file_to_load, cScenario& scenario, eLoadScenario fclose(file_id); - scenario.ter_types[23].fly_over = false; - scenario.scen_file = file_to_load; if(load_type == eLoadScenario::ONLY_HEADER) return true; load_spec_graphics_v1(scenario.scen_file); diff --git a/src/scenario/scenario.cpp b/src/scenario/scenario.cpp index 0337292ac..5f0df0578 100644 --- a/src/scenario/scenario.cpp +++ b/src/scenario/scenario.cpp @@ -231,7 +231,7 @@ cScenario::cItemStorage::cItemStorage() : ter_type(-1), property(0) { item_odds[i] = 0; } -void cScenario::import_legacy(legacy::scenario_data_type& old){ +void cScenario::import_legacy(legacy::scenario_data_type& old, bool header_only){ is_legacy = true; // TODO eventually the absence of feature flags here will replace is_legacy altogether feature_flags = {}; @@ -267,6 +267,9 @@ void cScenario::import_legacy(legacy::scenario_data_type& old){ rating = eContentRating(old.rating); // TODO: Is this used anywhere? uses_custom_graphics = old.uses_custom_graphics; + + if(header_only) return; + boats.resize(30); horses.resize(30); for(short i = 0; i < 30; i++) { diff --git a/src/scenario/scenario.hpp b/src/scenario/scenario.hpp index d52585b2f..62180c87d 100644 --- a/src/scenario/scenario.hpp +++ b/src/scenario/scenario.hpp @@ -151,7 +151,7 @@ class cScenario { towns.back()->init_start(); } - void import_legacy(legacy::scenario_data_type& old); + void import_legacy(legacy::scenario_data_type& old, bool header_only = false); void import_legacy(legacy::scen_item_data_type& old); void writeTo(cTagFile& file) const; void readFrom(const cTagFile& file); From e600fa245d81266a928a49fc6dbf1ef00fdf081d Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 1 Aug 2025 11:53:48 -0500 Subject: [PATCH 02/59] Scenario picker handle names with first character numeric --- rsrc/dialogs/pick-scenario.xml | 1 + src/game/boe.dlgutil.cpp | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rsrc/dialogs/pick-scenario.xml b/rsrc/dialogs/pick-scenario.xml index ead9e7e95..d5ecbc9e5 100644 --- a/rsrc/dialogs/pick-scenario.xml +++ b/rsrc/dialogs/pick-scenario.xml @@ -75,6 +75,7 @@ + + Get all gold diff --git a/src/game/boe.items.cpp b/src/game/boe.items.cpp index 6e59dfe43..9c024364d 100644 --- a/src/game/boe.items.cpp +++ b/src/game/boe.items.cpp @@ -396,6 +396,16 @@ static void put_item_graphics(cDialog& me, size_t& first_item_shown, short& curr me["down"].hide(); else me["down"].show(); + me["gold"].hide(); + me["gold-label"].hide(); + for(cItem* item : item_array){ + if(item->variety == eItemType::GOLD && !item->property){ + me["gold"].show(); + me["gold-label"].show(); + break; + } + } + for(short i = 0; i < ITEMS_IN_WINDOW; i++) { std::ostringstream sout; sout << "item" << i + 1; @@ -459,7 +469,22 @@ static bool display_item_event_filter(cDialog& me, std::string id, size_t& first first_item_shown += ITEMS_IN_WINDOW; put_item_graphics(me, first_item_shown, current_getting_pc, item_array); } - } else if(id.substr(0,2) == "pc") { + } else if(id == "gold"){ + // Get all gold + for(int i = item_array.size() - 1; i >= 0; --i){ + item = *item_array[i]; + if(item.variety != eItemType::GOLD || item.property) continue; + if(item.item_level > 3000) + item.item_level = 3000; + set_item_flag(&item); + give_gold(item.item_level,false); + *item_array[i] = cItem(); + item_array.erase(item_array.begin() + i); + } + play_sound(39); // formerly force_play_sound + put_item_graphics(me, first_item_shown, current_getting_pc, item_array); + } + 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 { @@ -560,7 +585,7 @@ bool show_get_items(std::string titleText, std::vector& itemRefs, short cDialog itemDialog(*ResMgr::dialogs.get("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, {"done", "up", "down", "gold"}); itemDialog.attachClickHandlers(handler, {"pc1", "pc2", "pc3", "pc4", "pc5", "pc6"}); itemDialog.setResult(false); cTextMsg& title = dynamic_cast(itemDialog["title"]); From 1daacb9277b2e8287d094e30ae50a57e0a602720 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 6 Aug 2025 10:26:21 -0500 Subject: [PATCH 23/59] fix party disappearing in huge town --- src/game/boe.graphutil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.graphutil.cpp b/src/game/boe.graphutil.cpp index 1b432dc95..34d981055 100644 --- a/src/game/boe.graphutil.cpp +++ b/src/game/boe.graphutil.cpp @@ -448,7 +448,7 @@ void draw_party_symbol(location center) { return; if(!univ.party.is_alive()) return; - if((is_town()) && (univ.party.town_loc.x > 70)) + if((is_town()) && (univ.party.town_loc.x > (univ.scenario.is_legacy ? 70 : LOC_UNUSED))) return; if(is_town() && center != univ.party.town_loc) { From cae5739800be76f171590738bd62942b6487fb34 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 6 Aug 2025 14:14:22 -0500 Subject: [PATCH 24/59] fix place/edit special node tool --- src/scenedit/scen.actions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 91e96bd71..afe429652 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -2888,6 +2888,7 @@ void place_edit_special(location loc) { bool is_new = false; if(i == specials.size()){ specials.emplace_back(-1,-1,-1); + get_current_area()->specials.emplace_back(); is_new = true; } if(specials[i].spec < 0) { From 77991281c526c639cab48b731aa186572911346d Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 8 Aug 2025 20:39:59 -0500 Subject: [PATCH 25/59] fix loc_off_act_area for outdoors --- src/game/boe.locutils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game/boe.locutils.cpp b/src/game/boe.locutils.cpp index 259a4d3fd..c04c1a30d 100644 --- a/src/game/boe.locutils.cpp +++ b/src/game/boe.locutils.cpp @@ -138,7 +138,9 @@ bool loc_off_world(location p1) { } bool loc_off_act_area(location p1) { - if((p1.x > univ.town->in_town_rect.left) && (p1.x < univ.town->in_town_rect.right) && + if(is_out() && univ.out->is_on_map(p1)) + return false; + else if((p1.x > univ.town->in_town_rect.left) && (p1.x < univ.town->in_town_rect.right) && (p1.y > univ.town->in_town_rect.top) && (p1.y < univ.town->in_town_rect.bottom)) return false; return true; From aa1edaa7e18ada10a1023a02a34689f7746348f2 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 9 Aug 2025 13:52:52 -0500 Subject: [PATCH 26/59] fix monsters rendering backwards. fix #785 --- src/game/boe.graphutil.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/boe.graphutil.cpp b/src/game/boe.graphutil.cpp index 34d981055..619d28b08 100644 --- a/src/game/boe.graphutil.cpp +++ b/src/game/boe.graphutil.cpp @@ -134,7 +134,7 @@ void draw_monsters() { for(short k = 0; k < width * height; k++) { std::shared_ptr src_gw; graf_pos_ref(src_gw, source_rect) = spec_scen_g.find_graphic(picture_wanted % 1000 + - ((enc.direction < 4) ? 0 : (width * height)) + k); + ((enc.direction >= 4) ? 0 : (width * height)) + k); to_rect = monst_rects[(width - 1) * 2 + height - 1][k]; to_rect.offset(13 + 28 * where_draw.x,13 + 36 * where_draw.y); rect_draw_some_item(*src_gw, source_rect, terrain_screen_gworld(),to_rect, sf::BlendAlpha); @@ -142,7 +142,7 @@ void draw_monsters() { } if(picture_wanted < 1000) { for(short k = 0; k < width * height; k++) { - source_rect = get_monster_template_rect(picture_wanted,(enc.direction < 4) ? 0 : 1,k); + source_rect = get_monster_template_rect(picture_wanted,(enc.direction >= 4) ? 0 : 1,k); to_rect = monst_rects[(width - 1) * 2 + height - 1][k]; to_rect.offset(13 + 28 * where_draw.x,13 + 36 * where_draw.y); int which_sheet = m_pic_index[picture_wanted].i / 20; @@ -192,7 +192,7 @@ void draw_monsters() { Draw_Some_Item(*src_gw, source_rect, terrain_screen_gworld(), store_loc, 1, 0); } else { pic_num_t this_monst = monst.picture_num; - int pic_mode = (monst.direction) < 4 ? 0 : 1; + int pic_mode = (monst.direction) >= 4 ? 0 : 1; pic_mode += (combat_posing_monster == i + 100) ? 10 : 0; source_rect = get_monster_template_rect(this_monst, pic_mode, k); int which_sheet = (m_pic_index[this_monst].i+k) / 20; From fe509143f4358cde481c1c38414404d9b107d676 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 9 Aug 2025 15:28:36 -0500 Subject: [PATCH 27/59] feature flag keep doors easier in old scenarios --- src/game/boe.main.cpp | 3 ++- src/game/boe.party.cpp | 6 +++--- src/game/boe.town.cpp | 4 ++-- src/scenedit/scen.core.cpp | 2 ++ src/universe/universe.cpp | 4 ++++ src/universe/universe.hpp | 1 + 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index 2a5142992..a6b83bc05 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -128,7 +128,8 @@ std::map> feature_flags = { {"store-spell-target", {"fixed"}}, {"store-spell-caster", {"fixed"}}, // Game balance - {"magic-resistance", {"fixed"}} // Resist Magic used to not help with magic damage! + {"magic-resistance", {"fixed"}}, // Resist Magic used to not help with magic damage! + {"door-town-difficulty", {"fixed"}} }; struct cParseEntrance { diff --git a/src/game/boe.party.cpp b/src/game/boe.party.cpp index 7c4bf839e..aff1bc34b 100644 --- a/src/game/boe.party.cpp +++ b/src/game/boe.party.cpp @@ -1385,10 +1385,10 @@ void cast_town_spell(location where) { case eSpell::UNLOCK: // TODO: Is the unlock spell supposed to have a max range? if(univ.scenario.ter_types[ter].special == eTerSpec::UNLOCKABLE){ - if(univ.scenario.ter_types[ter].flag2 == 10) + if(univ.scenario.ter_types[ter].flag2 == 10){ r1 = 10000; - else{ - r1 = get_ran(1,1,100) - 5 * adj + 5 * univ.town->difficulty; + }else{ + r1 = get_ran(1,1,100) - 5 * adj + 5 * univ.town.door_diff_adjust(); r1 += univ.scenario.ter_types[ter].flag2 * 7; } if(r1 < (135 - combat_percent[min(19,level)])) { diff --git a/src/game/boe.town.cpp b/src/game/boe.town.cpp index 091e31331..986b7505a 100644 --- a/src/game/boe.town.cpp +++ b/src/game/boe.town.cpp @@ -1143,7 +1143,7 @@ void pick_lock(location where,short pc_num) { if(r1 < 75) will_break = true; - r1 = get_ran(1,1,100) - 5 * univ.party[pc_num].stat_adj(eSkill::DEXTERITY) + univ.town->difficulty * 7 + r1 = get_ran(1,1,100) - 5 * univ.party[pc_num].stat_adj(eSkill::DEXTERITY) + univ.town.door_diff_adjust() * 7 - 5 * univ.party[pc_num].skill(eSkill::LOCKPICKING) - which_item->abil_strength * 7; // Nimble? @@ -1176,7 +1176,7 @@ void bash_door(location where,short pc_num) { short r1,unlock_adjust; terrain = univ.town->terrain(where.x,where.y); - r1 = get_ran(1,1,100) - 15 * univ.party[pc_num].stat_adj(eSkill::STRENGTH) + univ.town->difficulty * 4; + r1 = get_ran(1,1,100) - 15 * univ.party[pc_num].stat_adj(eSkill::STRENGTH) + univ.town.door_diff_adjust() * 4; if(univ.scenario.ter_types[terrain].special != eTerSpec::UNLOCKABLE) { add_string_to_buf(" Wrong terrain type."); diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp index 641b9c662..c01d01f1c 100644 --- a/src/scenedit/scen.core.cpp +++ b/src/scenedit/scen.core.cpp @@ -3344,6 +3344,8 @@ bool build_scenario() { scenario.feature_flags = { {"scenario-meta-format", "V2"}, {"resurrection-balm", "required"}, + // Town difficulty, due to a bug, used to not be added to door difficulty + {"door-town-difficulty", "fixed"} }; fs::path basePath = progDir/"Blades of Exile Base"/(grass ? "bladbase.boes" : "cavebase.boes"); diff --git a/src/universe/universe.cpp b/src/universe/universe.cpp index 9ba324289..327df733f 100644 --- a/src/universe/universe.cpp +++ b/src/universe/universe.cpp @@ -116,6 +116,10 @@ const cTown& cCurTown::operator * () const { return *record(); } +int cCurTown::door_diff_adjust() { + return univ.scenario.has_feature_flag("door-town-difficulty") ? arena->difficulty : 0; +} + void cCurTown::place_preset_fields() { // Initialize barriers, etc. Note non-sfx gets forgotten if this is a town recently visited. fields.resize(record()->max_dim, record()->max_dim); diff --git a/src/universe/universe.hpp b/src/universe/universe.hpp index a40246766..17e9f8af7 100644 --- a/src/universe/universe.hpp +++ b/src/universe/universe.hpp @@ -52,6 +52,7 @@ class cCurTown { void import_legacy(unsigned char(& old_sfx)[64][64], unsigned char(& old_misc_i)[64][64]); void import_legacy(legacy::big_tr_type& old); + int door_diff_adjust(); cTown* operator -> (); cTown& operator * (); const cTown* operator -> () const; From e6161f9596cab139d42fd5bf534485ebbabc9b7b Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 9 Aug 2025 18:57:47 -0500 Subject: [PATCH 28/59] Draw roads with brown dots. Fix #782 --- src/game/boe.town.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.town.cpp b/src/game/boe.town.cpp index 986b7505a..46ecb3d2a 100644 --- a/src/game/boe.town.cpp +++ b/src/game/boe.town.cpp @@ -1452,7 +1452,7 @@ void draw_map(bool need_refresh) { if(is_out() ? univ.out->roads[where.x][where.y] : univ.town.is_road(where.x,where.y)) { draw_rect.inset(1,1); - rect_draw_some_item(*ResMgr::graphics.get("trim"),{8,112,12,116},map_gworld(),draw_rect); + rect_draw_some_item(*ResMgr::graphics.get("fields"),{80,28,84,32},map_gworld(),draw_rect); } } } From 996e731260e683528b23d82fcc9202625f690e61 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 9 Aug 2025 20:27:21 -0500 Subject: [PATCH 29/59] ignore uninitialized Rectangle 1-8 --- src/game/boe.text.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/game/boe.text.cpp b/src/game/boe.text.cpp index 7b0e35a6d..36269c3d7 100644 --- a/src/game/boe.text.cpp +++ b/src/game/boe.text.cpp @@ -1262,19 +1262,13 @@ std::string get_location(cUniverse* specific_univ) { std::string loc_str = ""; location loc = outdoors ? global_to_local(specific_univ->party.out_loc) : specific_univ->party.town_loc; - if(outdoors) { - loc_str = specific_univ->out->name; - for(short i = 0; i < specific_univ->out->area_desc.size(); i++) - if(loc.in(specific_univ->out->area_desc[i])) { - loc_str = specific_univ->out->area_desc[i].descr; - } - } - if(town){ - loc_str = specific_univ->town->name; - for(short i = 0; i < specific_univ->town->area_desc.size(); i++) - if(loc.in(specific_univ->town->area_desc[i])) { - loc_str = specific_univ->town->area_desc[i].descr; - } + const std::vector& area_desc = (outdoors ? specific_univ->out->area_desc : specific_univ->town->area_desc); + + loc_str = outdoors ? specific_univ->out->name : specific_univ->town->name; + for(const info_rect_t& area : area_desc){ + if(!area.empty() && loc.in(area)) { + loc_str = area.descr; + } } return loc_str; } From 4a9443f4f2820afab67bd3636c8272e4e33957c7 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 11 Aug 2025 12:15:52 -0500 Subject: [PATCH 30/59] fix grammar in special node description --- rsrc/strings/specials-text-town.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rsrc/strings/specials-text-town.txt b/rsrc/strings/specials-text-town.txt index db809e25a..b016130ca 100644 --- a/rsrc/strings/specials-text-town.txt +++ b/rsrc/strings/specials-text-town.txt @@ -353,7 +353,7 @@ Number of town to place party in Skip dialog and always change level? Trigger Limitations Special to Call in New Town -A dialog comes up text you supply (saying, perhaps, they've found a stairway). The dialog buttons are Climb and Leave. If the Leave button is pressed, the Jump To special is used, and the party is not allowed to enter the space. If the Climb button is pushed, the party is moved to another town. +A dialog comes up with text you supply (saying, perhaps, they've found a stairway). The dialog buttons are Climb and Leave. If the Leave button is pressed, the Jump To special is used, and the party is not allowed to enter the space. If the Climb button is pushed, the party is moved to another town. -------------------- Relocate Outdoors Unused From 0283811ba4c5191e52e9b3726aeed8ca4cb781e0 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 11 Aug 2025 12:33:34 -0500 Subject: [PATCH 31/59] When shifting to town entrance, show outdoor loc str Fix #693 --- rsrc/dialogs/shift-town-entrance.xml | 5 ++--- src/scenario/area.hpp | 11 +++++++++++ src/scenedit/scen.actions.cpp | 9 ++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/rsrc/dialogs/shift-town-entrance.xml b/rsrc/dialogs/shift-town-entrance.xml index c28ff69eb..65ae29df0 100644 --- a/rsrc/dialogs/shift-town-entrance.xml +++ b/rsrc/dialogs/shift-town-entrance.xml @@ -5,8 +5,7 @@ - - Shift to this town's entrance in this outdoor section? + + Shift to this town's entrance in outdoor section {sec} at {loc} ({loc_str})? - diff --git a/src/scenario/area.hpp b/src/scenario/area.hpp index 8308235d1..7d308087b 100644 --- a/src/scenario/area.hpp +++ b/src/scenario/area.hpp @@ -54,6 +54,17 @@ class cArea { bool is_on_map(location loc) const { return loc.x < max_dim && loc.y < max_dim && loc.x >= 0 && loc.y >= 0; } + + std::string loc_str(location where) { + std::string str = name; + for(info_rect_t rect : area_desc){ + if(!rect.empty() && rect.contains(where)){ + str += ": " + rect.descr; + break; + } + } + return str; + } }; #endif diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index afe429652..e4e6ae80d 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -2335,7 +2335,10 @@ void handle_editor_screen_shift(int dx, int dy) { if(town_entrances.size() == 1){ town_entrance_t only_entrance = town_entrances[0]; cChoiceDlog shift_prompt("shift-town-entrance", {"yes", "no"}); - shift_prompt->getControl("out-sec").setText(boost::lexical_cast(only_entrance.out_sec)); + cControl& text = shift_prompt->getControl("prompt"); + text.replaceText("{sec}", boost::lexical_cast(only_entrance.out_sec)); + text.replaceText("{loc}", boost::lexical_cast(only_entrance.loc)); + text.replaceText("{loc_str}", scenario.outdoors[only_entrance.out_sec.x][only_entrance.out_sec.y]->loc_str(only_entrance.loc)); if(shift_prompt.show() == "yes"){ set_current_out(only_entrance.out_sec, true); @@ -2350,9 +2353,9 @@ void handle_editor_screen_shift(int dx, int dy) { std::vector entrance_strings; for(town_entrance_t entrance : town_entrances){ std::ostringstream sstr; - sstr << "Entrance in section " << entrance.out_sec << " at " << entrance.loc; + sstr << "Entrance in section " << entrance.out_sec << " at " << entrance.loc + << " (" <loc_str(entrance.loc) << ")"; entrance_strings.push_back(sstr.str()); - } cStringChoice dlog(entrance_strings, "Shift to one of this town's entrances in the outdoors?"); size_t choice = dlog.show(-1); From 1684a483b739a7c09396b230f92bae027a336fcd Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 11 Aug 2025 13:21:23 -0500 Subject: [PATCH 32/59] queue_special() return whether special was queued --- src/game/boe.specials.cpp | 5 +++-- src/game/boe.specials.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index 62d80e8a4..5cebc7ef9 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -1987,8 +1987,8 @@ void special_increase_age(long length, bool queue) { draw_terrain(0); } -void queue_special(eSpecCtx mode, eSpecCtxType which_type, spec_num_t spec, location spec_loc) { - if(spec < 0) return; +bool queue_special(eSpecCtx mode, eSpecCtxType which_type, spec_num_t spec, location spec_loc) { + if(spec < 0) return false; pending_special_type queued_special; queued_special.spec = spec; queued_special.where = spec_loc; @@ -2000,6 +2000,7 @@ void queue_special(eSpecCtx mode, eSpecCtxType which_type, spec_num_t spec, loca run_special(queued_special, nullptr, nullptr, nullptr); else special_queue.push(queued_special); + return true; } void run_special(pending_special_type spec, short* a, short* b, bool* redraw) { diff --git a/src/game/boe.specials.hpp b/src/game/boe.specials.hpp index 32523b9f3..203f35d37 100644 --- a/src/game/boe.specials.hpp +++ b/src/game/boe.specials.hpp @@ -20,7 +20,7 @@ void teleport_party(short x,short y,short mode); bool run_stone_circle(short which); void change_level(short town_num,short x,short y); void push_things(); -void queue_special(eSpecCtx mode, eSpecCtxType which_type, spec_num_t spec, location spec_loc); +bool queue_special(eSpecCtx mode, eSpecCtxType which_type, spec_num_t spec, location spec_loc); void run_special(eSpecCtx which_mode, eSpecCtxType which_type, spec_num_t start_spec, location spec_loc, short* a = nullptr, short* b = nullptr, bool* redraw = nullptr); void run_special(pending_special_type spec, short* a, short* b, bool* redraw); From f9c0b02484084e24d524930d43c972d15444ff53 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 11 Aug 2025 13:22:41 -0500 Subject: [PATCH 33/59] town entry autosave wait for specials to run. fix #781 --- src/game/boe.specials.cpp | 6 ++++++ src/game/boe.town.cpp | 13 +++++++++---- src/game/boe.town.hpp | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index 5cebc7ef9..4f4629569 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -2010,6 +2010,8 @@ void run_special(pending_special_type spec, short* a, short* b, bool* redraw) { univ.party.age = std::max(univ.party.age, store_time); } +extern bool need_enter_town_autosave; + // This is the big painful one, the main special engine entry point // which_mode - says when it was called // which_type - where the special is stored (town, out, scenario) @@ -2167,6 +2169,10 @@ void run_special(eSpecCtx which_mode, eSpecCtxType which_type, spec_num_t start_ erase_out_specials(); else erase_town_specials(); special_in_progress = false; + if(which_mode == eSpecCtx::ENTER_TOWN && need_enter_town_autosave){ + try_auto_save("EnterTown"); + need_enter_town_autosave = false; + } // TODO: Should find a way to do this that doesn't risk stack overflow if(ctx.next_spec == -1 && !special_queue.empty()) { diff --git a/src/game/boe.town.cpp b/src/game/boe.town.cpp index 46ecb3d2a..a9b79de34 100644 --- a/src/game/boe.town.cpp +++ b/src/game/boe.town.cpp @@ -69,6 +69,8 @@ void force_town_enter(short which_town,location where_start) { town_force_loc = where_start; } +bool need_enter_town_autosave = false; + //short entry_dir; // if 9, go to forced void start_town_mode(short which_town, short entry_dir, bool debug_enter) { short town_number; @@ -334,8 +336,9 @@ void start_town_mode(short which_town, short entry_dir, bool debug_enter) { if(no_thrash.count(&monst) == 0) monst.active = eCreatureStatus::DEAD; } + bool specials_queued = false; if(!debug_enter) - handle_town_specials(town_number, (short) town_toast,(entry_dir < 9) ? univ.town->start_locs[entry_dir] : town_force_loc); + specials_queued = handle_town_specials(town_number, (short) town_toast,(entry_dir < 9) ? univ.town->start_locs[entry_dir] : town_force_loc); // Flush excess doomguards and viscous goos for(short i = 0; i < univ.town.monst.size(); i++) @@ -499,7 +502,9 @@ void start_town_mode(short which_town, short entry_dir, bool debug_enter) { // ... except it actually doesn't, because the town enter special is only queued, not run immediately. draw_terrain(1); - try_auto_save("EnterTown"); + // If special nodes still need to be called, we can't do the autosave yet. + if(specials_queued) need_enter_town_autosave = true; + else try_auto_save("EnterTown"); } @@ -626,8 +631,8 @@ location end_town_mode(bool switching_level,location destination, bool debug_lea return to_return; } -void handle_town_specials(short /*town_number*/, bool town_dead,location /*start_loc*/) { - queue_special(eSpecCtx::ENTER_TOWN, eSpecCtxType::TOWN, town_dead ? univ.town->spec_on_entry_if_dead : univ.town->spec_on_entry, univ.party.town_loc); +bool handle_town_specials(short /*town_number*/, bool town_dead,location /*start_loc*/) { + return queue_special(eSpecCtx::ENTER_TOWN, eSpecCtxType::TOWN, town_dead ? univ.town->spec_on_entry_if_dead : univ.town->spec_on_entry, univ.party.town_loc); } void handle_leave_town_specials(short /*town_number*/, short which_spec,location /*start_loc*/) { diff --git a/src/game/boe.town.hpp b/src/game/boe.town.hpp index 9467ad2c7..2c866387c 100644 --- a/src/game/boe.town.hpp +++ b/src/game/boe.town.hpp @@ -6,7 +6,7 @@ void force_town_enter(short which_town,location where_start); void start_town_mode(short which_town, short entry_dir, bool debug_enter = false); location end_town_mode(bool switching_level,location destination,bool debug_leave=false); // returns new party location void handle_leave_town_specials(short town_number, short which_spec,location start_loc) ; -void handle_town_specials(short town_number, bool town_dead,location start_loc) ; +bool handle_town_specials(short town_number, bool town_dead,location start_loc) ; bool abil_exists(eItemAbil abil); void start_town_combat(eDirection direction); From 199719a1c8a4e94ab89db82a58e3502d4926cfa2 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 11 Aug 2025 12:34:50 -0500 Subject: [PATCH 34/59] starting with stairways, show more info in edit special list --- src/scenario/special.cpp | 17 +++++++++++++++++ src/scenario/special.hpp | 2 ++ src/scenedit/scen.graphics.cpp | 6 +++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/scenario/special.cpp b/src/scenario/special.cpp index 05bb0d5af..f40bfb480 100644 --- a/src/scenario/special.cpp +++ b/src/scenario/special.cpp @@ -21,6 +21,7 @@ #include "skills_traits.hpp" #include "damage.hpp" #include "fields.hpp" +#include "scenario.hpp" bool cTimer::is_valid() const { if(time < 0) return false; @@ -529,6 +530,22 @@ void cSpecial::import_legacy(legacy::special_node_type& old){ } } +// In the editor node list, be as helpful as possible about what the specific node instance does +std::string cSpecial::editor_hint(const cScenario& scenario) const { + std::string hint = (*type).name(); + + switch(type){ + case eSpecType::TOWN_STAIR: + case eSpecType::TOWN_GENERIC_STAIR: + hint += " to "; + if(ex2a < scenario.towns.size()) hint += scenario.towns[ex2a]->loc_str(loc(ex1a, ex1b)); + else hint += "INVALID TOWN"; + break; + default: break; + } + return hint; +} + static eSpecCat getNodeCategory(eSpecType node) { for(int i = 0; i <= int(eSpecCat::OUTDOOR); i++) { eSpecCat cat = eSpecCat(i); diff --git a/src/scenario/special.hpp b/src/scenario/special.hpp index 34683ed14..6f9427a09 100644 --- a/src/scenario/special.hpp +++ b/src/scenario/special.hpp @@ -16,6 +16,7 @@ #include "dialogxml/widgets/pictypes.hpp" namespace legacy { struct special_node_type; }; +class cScenario; static const short SDF_COMPLETE = 250; @@ -108,6 +109,7 @@ class cSpecial { return true; } bool operator!=(const cSpecial& other) const { return !(*this == other); } + std::string editor_hint(const cScenario& scenario) const; }; enum class eSpecCtxType { diff --git a/src/scenedit/scen.graphics.cpp b/src/scenedit/scen.graphics.cpp index 40f5bad0c..f0d1a1a59 100644 --- a/src/scenedit/scen.graphics.cpp +++ b/src/scenedit/scen.graphics.cpp @@ -535,15 +535,15 @@ static void apply_mode_buttons() { std::ostringstream strb; switch(mode) { case 0: - strb << i << " - " << (*scenario.scen_specials[i].type).name(); + strb << i << " - " << scenario.scen_specials[i].editor_hint(scenario); set_rb(i,RB_SCEN_SPEC, i, strb.str()); break; case 1: - strb << i << " - " << (*current_terrain->specials[i].type).name(); + strb << i << " - " << current_terrain->specials[i].editor_hint(scenario); set_rb(i,RB_OUT_SPEC, i, strb.str()); break; case 2: - strb << i << " - " << (*town->specials[i].type).name(); + strb << i << " - " << town->specials[i].editor_hint(scenario); set_rb(i,RB_TOWN_SPEC, i, strb.str()); break; } From c9e54f0a628527b4fe106e498427944b8c80f490 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 14 Aug 2025 12:56:31 -0500 Subject: [PATCH 35/59] fix contradiction in comment --- src/game/boe.monster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.monster.cpp b/src/game/boe.monster.cpp index e8ff37963..8e1d0e61e 100644 --- a/src/game/boe.monster.cpp +++ b/src/game/boe.monster.cpp @@ -822,7 +822,7 @@ void monst_inflict_fields(short which_monst) { which_m = &univ.town.monst[which_monst]; bool have_radiate = which_m->abil[eMonstAbil::RADIATE].active; eFieldType which_radiate = which_m->abil[eMonstAbil::RADIATE].radiate.type; - // Judgment call: big monsters shouldn't only get damaged once per damage type if they're on + // Judgment call: big monsters should only get damaged once per damage type if they're on // multiple of the same field. (Except webs.) bool quickfire = false; bool blade_wall = false; From 83888fd087c657d8290bbb6cf48741bedd310422 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 31 Jul 2025 14:37:20 -0500 Subject: [PATCH 36/59] add donor to credits --- pkg/credits/Funding.txt | 1 + rsrc/dialogs/about-boe.xml | 9 +++++---- rsrc/graphics/startanim.png | Bin 24905 -> 25086 bytes 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/credits/Funding.txt b/pkg/credits/Funding.txt index aca5479d8..905a2baf7 100644 --- a/pkg/credits/Funding.txt +++ b/pkg/credits/Funding.txt @@ -25,6 +25,7 @@ Confirmed: - K L - Laura Nelson - Mariann Krizsan +- Maryanne Wachter - Mike Lapinsky - Nathan Rickey - Nick Chaimov diff --git a/rsrc/dialogs/about-boe.xml b/rsrc/dialogs/about-boe.xml index f290fba02..ca7bc931a 100644 --- a/rsrc/dialogs/about-boe.xml +++ b/rsrc/dialogs/about-boe.xml @@ -18,11 +18,11 @@ ORIGINAL GAME:





- OPEN SOURCE CREDITS:











































































+ OPEN SOURCE CREDITS:












































































SCENARIO FIXES AND UPDATES:

- +
Concept, Design, Programming:
Graphics:
@@ -34,11 +34,11 @@ Graphics:






Consulting:



Testing and Troubleshooting:

- Funding:













































+ Funding:
















































Bandit Busywork:
- +
Jeff Vogel
Andrew Hunter
@@ -99,6 +99,7 @@ K L
Laura Nelson
Mariann Krizsan
+ Maryanne Wachter
Mike Lapinsky
Nathan Rickey
Nick Chaimov
diff --git a/rsrc/graphics/startanim.png b/rsrc/graphics/startanim.png index e4443d9a8181159680d516f2b316fcbfd71c81df..b10cb955fbc59aabd068fd33ea258189d1864f3f 100644 GIT binary patch delta 2874 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 delta 2681 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 From 2e858ddd611a1997f1439d2ea8591b40ca96be7a Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 15 Aug 2025 09:43:36 -0500 Subject: [PATCH 37/59] add name to funding list --- pkg/credits/Funding.txt | 1 + rsrc/dialogs/about-boe.xml | 9 +++++---- rsrc/graphics/startanim.png | Bin 25086 -> 25115 bytes 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/credits/Funding.txt b/pkg/credits/Funding.txt index 905a2baf7..a5ef8089d 100644 --- a/pkg/credits/Funding.txt +++ b/pkg/credits/Funding.txt @@ -10,6 +10,7 @@ Confirmed: - Bret Rodabaugh - Dylan Nugent - Evan Mulrooney +- Glen Chudley - Jake Harrelson - Jared Forcinito - Jeff Potter diff --git a/rsrc/dialogs/about-boe.xml b/rsrc/dialogs/about-boe.xml index ca7bc931a..b703a0de1 100644 --- a/rsrc/dialogs/about-boe.xml +++ b/rsrc/dialogs/about-boe.xml @@ -18,11 +18,11 @@ ORIGINAL GAME:





- OPEN SOURCE CREDITS:












































































+ OPEN SOURCE CREDITS:













































































SCENARIO FIXES AND UPDATES:

- +
Concept, Design, Programming:
Graphics:
@@ -34,11 +34,11 @@ Graphics:






Consulting:



Testing and Troubleshooting:

- Funding:














































+ Funding:

















































Bandit Busywork:
- +
Jeff Vogel
Andrew Hunter
@@ -84,6 +84,7 @@ Bret Rodabaugh
Dylan Nugent
Evan Mulrooney
+ Glen Chudley
Jake Harrelson
Jared Forcinito
Jeff Potter
diff --git a/rsrc/graphics/startanim.png b/rsrc/graphics/startanim.png index b10cb955fbc59aabd068fd33ea258189d1864f3f..247de61ebbe305e7d7e4c27320eacbf7b42d4343 100644 GIT binary patch delta 21679 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 From d770e60c39b31646e052f541aab1e6dc8b30519f Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 16 Aug 2025 14:12:29 -0500 Subject: [PATCH 38/59] has_class require_charges allow item w/ 0 max charges --- src/universe/pc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/universe/pc.cpp b/src/universe/pc.cpp index c490c81ba..3c4d3ac63 100644 --- a/src/universe/pc.cpp +++ b/src/universe/pc.cpp @@ -850,7 +850,7 @@ const cInvenSlot cPlayer::has_class_equip(unsigned int item_class) const { cInvenSlot cPlayer::has_class(unsigned int item_class, bool require_charges) { return find_item_matching([item_class, require_charges](int, const cItem& item) { - return item.special_class == item_class && (!require_charges || item.charges > 0); + return item.special_class == item_class && (!require_charges || item.charges > 0 || item.max_charges == 0); }); } From 83e56612092217721bb5920de0f5be14481e2343 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 18 Aug 2025 11:45:12 -0500 Subject: [PATCH 39/59] Fix rendering UTF-8 text --- src/game/boe.text.cpp | 2 +- src/gfx/render_text.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/game/boe.text.cpp b/src/game/boe.text.cpp index 36269c3d7..a1ea6e268 100644 --- a/src/game/boe.text.cpp +++ b/src/game/boe.text.cpp @@ -1130,7 +1130,7 @@ void print_buf () { line_style.applyTo(text, get_ui_scale()); // A spacing factor of 1.0 within multiline messages doesn't actually line up with other single buffer lines text.setLineSpacing(0.85); - text.setString(message); + text.setString(sf::String::fromUtf8(message.begin(), message.end())); text.setPosition(moveTo); draw_scale_aware_text(text_area_gworld(), text); } diff --git a/src/gfx/render_text.cpp b/src/gfx/render_text.cpp index 611325b3a..a86861c63 100644 --- a/src/gfx/render_text.cpp +++ b/src/gfx/render_text.cpp @@ -125,7 +125,7 @@ break_info_t calculate_line_wrapping(rectangle dest_rect, std::string str, TextS short str_len = str.length(); unsigned short last_line_break = 0,last_word_break = 0; - str_to_draw.setString(str); + str_to_draw.setString(sf::String::fromUtf8(str.begin(), str.end())); // Even if the text is only one line, break_info is required for calculating word boundaries. // So we can't skip the rest of this. @@ -263,7 +263,7 @@ static void win_draw_string(sf::RenderTarget& dest_window,rectangle dest_rect,st for(auto it : substitutions){ boost::replace_all(str, it.first, it.second); } - str_to_draw.setString(str); + str_to_draw.setString(sf::String::fromUtf8(str.begin(), str.end())); short total_width = str_to_draw.getLocalBounds().width; options.style.applyTo(str_to_draw, get_ui_scale()); @@ -337,7 +337,7 @@ static void win_draw_string(sf::RenderTarget& dest_window,rectangle dest_rect,st } for(auto& snippet : options.snippets) { - str_to_draw.setString(snippet.text); + str_to_draw.setString(sf::String::fromUtf8(snippet.text.begin(), snippet.text.end())); str_to_draw.setPosition(snippet.at); if(snippet.hilited) { rectangle bounds = str_to_draw.getGlobalBounds(); @@ -409,7 +409,7 @@ size_t string_length(std::string str, const TextStyle& style, short* height){ sf::Text text; style.applyTo(text); - text.setString(str); + text.setString(sf::String::fromUtf8(str.begin(), str.end())); total_width = text.getLocalBounds().width; if(strings_to_cache.count(str)){ location measurement; From 92fe397348860eac8f7b2cfa6396de3164931a02 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 18 Aug 2025 14:04:12 -0500 Subject: [PATCH 40/59] try allowing implicit fallthrough in xcode --- proj/xc12/BoE.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proj/xc12/BoE.xcodeproj/project.pbxproj b/proj/xc12/BoE.xcodeproj/project.pbxproj index c8a6baba7..6573d4b63 100755 --- a/proj/xc12/BoE.xcodeproj/project.pbxproj +++ b/proj/xc12/BoE.xcodeproj/project.pbxproj @@ -2516,7 +2516,7 @@ "-Wno-quoted-include-in-framework-header", "-Wno-shorten-64-to-32", "-Wno-comma", - "-Werror=implicit-fallthrough", + "-Wimplicit-fallthrough", ); }; name = Debug; @@ -2631,7 +2631,7 @@ "-Wno-quoted-include-in-framework-header", "-Wno-shorten-64-to-32", "-Wno-comma", - "-Werror=implicit-fallthrough", + "-Wimplicit-fallthrough", ); }; name = Release; From e470575701607f7c7e46aca150d71b864f693bf9 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 18 Aug 2025 17:45:55 -0500 Subject: [PATCH 41/59] Clear saved monsters when exiting scenario. Fix #786 --- src/game/boe.actions.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 8bb52761f..12854267a 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -1409,6 +1409,11 @@ void handle_victory(bool force, bool record) { univ.exportGraphics(); univ.exportSummons(); univ.clear_stored_pcs(); + // Saved monsters are not valid now + for(auto& pop : univ.party.creature_save){ + pop.which_town = 200; + pop.clear(); + } reload_startup(); overall_mode = MODE_STARTUP; draw_startup(0); From 80ffda130d9774cab2f571de83a4545fc63abdd0 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 18 Aug 2025 17:48:02 -0500 Subject: [PATCH 42/59] PC editor also clear creature save --- src/pcedit/pc.fileio.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pcedit/pc.fileio.cpp b/src/pcedit/pc.fileio.cpp index caa33faac..e4d8ee222 100644 --- a/src/pcedit/pc.fileio.cpp +++ b/src/pcedit/pc.fileio.cpp @@ -26,6 +26,11 @@ void remove_party_from_scen() { univ.exportGraphics(); univ.exportSummons(); univ.party.scen_name = ""; + // Saved monsters are not valid now + for(auto& pop : univ.party.creature_save){ + pop.which_town = 200; + pop.clear(); + } party_in_scen = false; load_base_item_defs(); } From 7c2115e402a8bbad501ab704c5f0a0d127010ae2 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 24 Aug 2025 19:14:59 -0500 Subject: [PATCH 43/59] Fix another bug in creating special encounters --- src/scenedit/scen.actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index e4e6ae80d..738e49aaf 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -2895,7 +2895,7 @@ void place_edit_special(location loc) { is_new = true; } if(specials[i].spec < 0) { - if(edit_spec_enc(i, editing_town ? 2: 1, nullptr, is_new)) { + if(edit_spec_enc(get_current_area()->specials.size()-1, editing_town ? 2: 1, nullptr, is_new)) { specials[i] = loc; specials[i].spec = i; undo_list.add(action_ptr(new aPlaceEraseSpecial("Place Special Encounter", true, specials[i]))); From 6e13a9eae0751d45d5ad6ef3d1db372879a38447 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 24 Aug 2025 19:23:56 -0500 Subject: [PATCH 44/59] add an is_new check --- src/scenedit/scen.keydlgs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenedit/scen.keydlgs.cpp b/src/scenedit/scen.keydlgs.cpp index 96bf2ac8b..37da1f11c 100644 --- a/src/scenedit/scen.keydlgs.cpp +++ b/src/scenedit/scen.keydlgs.cpp @@ -967,7 +967,7 @@ static bool discard_spec_enc(cDialog& me, node_stack_t& edit_stack) { } auto cur = edit_stack.back(); auto& list = cur.mode == 0 ? scenario.scen_specials : (cur.mode == 1 ? current_terrain->specials : town->specials); - if(cur.which == list.size() - 1 && list[cur.which].type == eSpecType::NONE && list[cur.which].jumpto == -1) + if(cur.is_new && cur.which == list.size() - 1 && list[cur.which].type == eSpecType::NONE && list[cur.which].jumpto == -1) list.pop_back(); edit_stack.pop_back(); if(action == "cancel") { From 591387c3dc0783b8b9d842104ed8aa006249ce37 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 24 Aug 2025 20:15:57 -0500 Subject: [PATCH 45/59] make get_sdf_name() const --- src/scenario/scenario.cpp | 6 +++--- src/scenario/scenario.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scenario/scenario.cpp b/src/scenario/scenario.cpp index 5f0df0578..aa12e8fc7 100644 --- a/src/scenario/scenario.cpp +++ b/src/scenario/scenario.cpp @@ -643,15 +643,15 @@ std::string cScenario::get_feature_flag(std::string flag) { return iter->second; } -std::string cScenario::get_sdf_name(int row, int col) { +std::string cScenario::get_sdf_name(int row, int col) const { if(row < 0 || row >= SDF_ROWS || col < 0 || col >= SDF_COLUMNS){ throw "Tried to access SDF name for out-of-bounds flag (" + std::to_string(row) + ", " + std::to_string(col) + ")"; } if(sdf_names.find(row) == sdf_names.end()) return ""; - if(sdf_names[row].find(col) == sdf_names[row].end()) + if(sdf_names.at(row).find(col) == sdf_names.at(row).end()) return ""; - return sdf_names[row][col]; + return sdf_names.at(row).at(col); } bool cScenario::cItemStorage::operator==(const cScenario::cItemStorage& other) const { diff --git a/src/scenario/scenario.hpp b/src/scenario/scenario.hpp index 62180c87d..ea6f1e17f 100644 --- a/src/scenario/scenario.hpp +++ b/src/scenario/scenario.hpp @@ -139,7 +139,7 @@ class cScenario { std::vector ic_names; std::vector itf_names; std::map> sdf_names; - std::string get_sdf_name(int row, int col); + std::string get_sdf_name(int row, int col) const; bool adjust_diff; bool is_legacy; From 095a5a793f75c1a1008955738302a6a10f3c8674 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 18 Aug 2025 11:48:32 -0500 Subject: [PATCH 46/59] substitutions no longer needed --- src/game/boe.newgraph.cpp | 6 ------ src/gfx/render_text.cpp | 11 ----------- 2 files changed, 17 deletions(-) diff --git a/src/game/boe.newgraph.cpp b/src/game/boe.newgraph.cpp index 3a82e33f6..5db0708a2 100644 --- a/src/game/boe.newgraph.cpp +++ b/src/game/boe.newgraph.cpp @@ -1013,12 +1013,6 @@ void place_talk_str(std::string str_to_place,std::string str_to_place2,short col // The added spaces ensure that end-of-word boundaries are found std::string str = str_to_place + " |" + str_to_place2 + " "; - // TODO use a font where we don't need this - extern std::map substitutions; - for(auto it : substitutions){ - boost::replace_all(str, it.first, it.second); - } - std::vector hilites; std::vector nodes; int wordStart = 0, wordEnd = 0; diff --git a/src/gfx/render_text.cpp b/src/gfx/render_text.cpp index a86861c63..23745d48c 100644 --- a/src/gfx/render_text.cpp +++ b/src/gfx/render_text.cpp @@ -108,18 +108,10 @@ static void push_snippets(size_t start, size_t end, text_params_t& options, size } while(start < upper_bound); } -std::map substitutions = { - {"–", "--"} -}; - break_info_t calculate_line_wrapping(rectangle dest_rect, std::string str, TextStyle style) { break_info_t break_info; if(str.empty()) return break_info; // Nothing to do! - for(auto it : substitutions){ - boost::replace_all(str, it.first, it.second); - } - sf::Text str_to_draw; style.applyTo(str_to_draw); short str_len = str.length(); @@ -260,9 +252,6 @@ static void win_draw_string(sf::RenderTarget& dest_window,rectangle dest_rect,st // TODO: Why the heck are we drawing a whole line higher than requested!? adjust_y -= str_to_draw.getLocalBounds().height; - for(auto it : substitutions){ - boost::replace_all(str, it.first, it.second); - } str_to_draw.setString(sf::String::fromUtf8(str.begin(), str.end())); short total_width = str_to_draw.getLocalBounds().width; From 7d9f5d5ea2f750c1e7c64567cbb591f848bc2676 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 25 Aug 2025 11:48:02 -0500 Subject: [PATCH 47/59] cScenario copy constructor copy sdf_names --- src/scenario/scenario.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenario/scenario.cpp b/src/scenario/scenario.cpp index aa12e8fc7..2e10063d5 100644 --- a/src/scenario/scenario.cpp +++ b/src/scenario/scenario.cpp @@ -119,6 +119,7 @@ cScenario::cScenario(const cScenario& other) , intro_strs(other.intro_strs) , journal_strs(other.journal_strs) , spec_strs(other.spec_strs) + , sdf_names(other.sdf_names) , snd_names(other.snd_names) , adjust_diff(other.adjust_diff) , is_legacy(other.is_legacy) From af604358d00c15689ab01eaa0b4c29b013a19be9 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Tue, 26 Aug 2025 16:11:10 -0500 Subject: [PATCH 48/59] update search highlights when paging left/right. fix #791 --- src/dialogxml/dialogs/strchoice.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dialogxml/dialogs/strchoice.cpp b/src/dialogxml/dialogs/strchoice.cpp index be546087b..aa4159165 100644 --- a/src/dialogxml/dialogs/strchoice.cpp +++ b/src/dialogxml/dialogs/strchoice.cpp @@ -143,6 +143,8 @@ bool cStringChoice::onLeft(){ if(page == 0) page = lastPage(); else page--; fillPage(); + clearHighlights(); + highlightSearch(); return true; } @@ -154,6 +156,8 @@ bool cStringChoice::onRight(){ if(page == lastPage()) page = 0; else page++; fillPage(); + clearHighlights(); + highlightSearch(); return true; } From 168ff836374264c5ae3c47e2cf0519ef0795e915 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 28 Aug 2025 11:35:30 -0500 Subject: [PATCH 49/59] fix draw zoomed-out teranim in scenedit --- src/scenedit/scen.graphics.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/scenedit/scen.graphics.cpp b/src/scenedit/scen.graphics.cpp index f0d1a1a59..c7ebf7c3d 100644 --- a/src/scenedit/scen.graphics.cpp +++ b/src/scenedit/scen.graphics.cpp @@ -1484,7 +1484,6 @@ void draw_one_tiny_terrain_spot (short i,short j,ter_num_t terrain_to_draw,short } else { sf::Texture& small_ter_gworld = *ResMgr::graphics.get("termap"); if(picture_wanted >= 960) { - picture_wanted -= 960; from_rect.offset(12 * 20, (picture_wanted - 960) * 12); rect_draw_some_item(small_ter_gworld, from_rect, mainPtr(), dest_rect); } else { From 851238978f70b9bfe10fc9e63d487adf547d30ca Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 28 Aug 2025 12:09:20 -0500 Subject: [PATCH 50/59] remove Spiderweb Software copyright & concact info from docs --- doc/editor/Contents.html | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/doc/editor/Contents.html b/doc/editor/Contents.html index 47502503f..3a9cec7a2 100644 --- a/doc/editor/Contents.html +++ b/doc/editor/Contents.html @@ -6,7 +6,6 @@

Blades Scenario Editor Instructions

-

Copyright 1998, Spiderweb Software, Inc.

This document contains all the information you need to play use the Blades of Exile Scenario Editor and make new scenarios for Blades of Exile!

This menu can also take you to the different sections of this document. To find the thing you're looking for, try the table of contents...

Table of Contents

@@ -98,15 +97,5 @@

Technical support

  • Blades of Exile Forum at Spiderweb Software
  • Blades of Exile IRC channel
  • -

    Spiderweb Software

    - From a320da89fa369ceaa761e4460d39f721ac896bef Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 29 Aug 2025 14:28:37 -0500 Subject: [PATCH 51/59] fix crash showing change site at edge of world --- src/scenedit/scen.undo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scenedit/scen.undo.cpp b/src/scenedit/scen.undo.cpp index 7f6bffcf1..3d0ec7ec8 100644 --- a/src/scenedit/scen.undo.cpp +++ b/src/scenedit/scen.undo.cpp @@ -65,6 +65,9 @@ void cTerrainAction::showChangeSite() { if(!((abs((short) (cen_x - area.where.x)) <=4) && (abs((short) (cen_y - area.where.y)) <= 4))){ cen_x = area.where.x; cen_y = area.where.y; + size_t max_dim = area.is_town ? town->max_dim : current_terrain->max_dim; + cen_x = minmax(4, max_dim - 5, cen_x); + cen_y = minmax(4, max_dim - 5, cen_y); } redraw_screen(); } From bf51eefdbcc03948c56cbf40f7ae156f64662380 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Fri, 29 Aug 2025 14:51:49 -0500 Subject: [PATCH 52/59] show better changesite for floodfill/terrain frill/item place --- src/scenedit/scen.undo.cpp | 18 +++++++++++++++++- src/scenedit/scen.undo.hpp | 5 ++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/scenedit/scen.undo.cpp b/src/scenedit/scen.undo.cpp index 3d0ec7ec8..261d9ae4d 100644 --- a/src/scenedit/scen.undo.cpp +++ b/src/scenedit/scen.undo.cpp @@ -291,7 +291,7 @@ aPlaceEraseItem::aPlaceEraseItem(std::string name, bool place, size_t index, cTo {} aPlaceEraseItem::aPlaceEraseItem(std::string name, bool place, item_changes_t items) - : cTerrainAction(name, items.begin()->second.loc, !place) + : cTerrainAction(name, closest_to_view(items), !place) , items(items) {} @@ -1249,3 +1249,19 @@ bool aCreateAreaRect::redo_me() { area->area_desc[which] = rect; return true; } + +location closest_to_view(stroke_ter_changes_t changes) { + std::vector locs; + for(auto& it : changes){ + locs.push_back(it.first); + } + return closest_point(locs, loc(cen_x, cen_y)); +} + +location closest_to_view(item_changes_t changes) { + std::vector locs; + for(auto& it : changes){ + locs.push_back(it.second.loc); + } + return closest_point(locs, loc(cen_x, cen_y)); +} \ No newline at end of file diff --git a/src/scenedit/scen.undo.hpp b/src/scenedit/scen.undo.hpp index fbf792141..0f21d2842 100644 --- a/src/scenedit/scen.undo.hpp +++ b/src/scenedit/scen.undo.hpp @@ -45,6 +45,9 @@ typedef std::map outdoor_sections_t; typedef std::map,loc_compare> clear_field_stroke_t; typedef std::set field_stroke_t; +location closest_to_view(stroke_ter_changes_t changes); +location closest_to_view(item_changes_t changes); + // Action that modified something in town or outdoor terrain, so we should show the modified area when undoing or redoing class cTerrainAction : public cAction { public: @@ -100,7 +103,7 @@ class aSetSpecial : public cTerrainAction { class aDrawTerrain : public cTerrainAction { public: aDrawTerrain(std::string name, stroke_ter_changes_t stroke_changes) : - cTerrainAction(name, stroke_changes.begin()->first), // Use arbitrary changed tile as site of change + cTerrainAction(name, closest_to_view(stroke_changes)), changes(stroke_changes) {} bool undo_me() override; bool redo_me() override; From 6333c667ea8e8a11a33624daadbc582597ce3396 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 31 Aug 2025 15:05:32 -0500 Subject: [PATCH 53/59] fix change cursor when hovering field --- src/dialogxml/dialogs/dialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dialogxml/dialogs/dialog.cpp b/src/dialogxml/dialogs/dialog.cpp index 728f3368e..c671becc2 100644 --- a/src/dialogxml/dialogs/dialog.cpp +++ b/src/dialogxml/dialogs/dialog.cpp @@ -795,6 +795,8 @@ void cDialog::handle_one_event(const sf::Event& currentEvent, cFramerateLimiter& } BOOST_FALLTHROUGH; case sf::Event::MouseMoved:{ + int x = currentEvent.mouseMove.x / get_ui_scale(); + int y = currentEvent.mouseMove.y / get_ui_scale(); // Did the window move, potentially dirtying the canvas below it? if(check_window_moved(win, winLastX, winLastY)) if (redraw_everything != NULL) @@ -802,7 +804,7 @@ void cDialog::handle_one_event(const sf::Event& currentEvent, cFramerateLimiter& bool inField = false; for(auto& ctrl : controls) { - if(ctrl.second->getType() == CTRL_FIELD && ctrl.second->getBounds().contains(currentEvent.mouseMove.x, currentEvent.mouseMove.y)) { + if(ctrl.second->getType() == CTRL_FIELD && ctrl.second->getBounds().contains(x, y)) { set_cursor(text_curs); inField = true; break; From 7666e0bfed265bf483b0c35f411d4f6ab8a287ca Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 1 Sep 2025 12:03:36 -0500 Subject: [PATCH 54/59] fix highlighting everything on strchoice page left/right --- src/dialogxml/dialogs/strchoice.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dialogxml/dialogs/strchoice.cpp b/src/dialogxml/dialogs/strchoice.cpp index aa4159165..3cbb7d029 100644 --- a/src/dialogxml/dialogs/strchoice.cpp +++ b/src/dialogxml/dialogs/strchoice.cpp @@ -248,6 +248,7 @@ void cStringChoice::clearHighlights() { bool cStringChoice::highlightSearch() { bool match_on_page = false; + if(search_str.empty()) return false; for(int offset = 0; offset < per_page; ++offset){ std::string led_id = "led" + std::to_string(offset + 1); From 6f50ae3fdd028b9be51744011c204736f64a26c7 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Mon, 1 Sep 2025 12:08:35 -0500 Subject: [PATCH 55/59] fix 'frill up terrain' prompting town entrance/sign text change --- src/scenedit/scen.actions.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 738e49aaf..8d2a55c12 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -2466,15 +2466,18 @@ void frill_up_terrain() { for(short i = 0; i < cur_area->max_dim; i++) for(short j = 0; j < cur_area->max_dim; j++) { terrain_type = cur_area->terrain(i,j); + bool changed = false; for(size_t k = 0; k < scenario.ter_types.size(); k++) { if(terrain_type == k) continue; cTerrain& ter = scenario.ter_types[k]; - if(terrain_type == ter.frill_for && get_ran(1,1,100) < ter.frill_chance) + if(terrain_type == ter.frill_for && get_ran(1,1,100) < ter.frill_chance){ terrain_type = k; + changed = true; + } } - set_terrain(loc(i, j), terrain_type, changes); + if(changed) set_terrain(loc(i, j), terrain_type, changes); } undo_list.add(action_ptr(new aDrawTerrain("Frill Up Terrain", changes))); update_edit_menu(); From f83374722a3863c3fd1e73f9c6f41f9776ca1fe0 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 3 Sep 2025 19:24:56 -0500 Subject: [PATCH 56/59] Town and scenario timers are supposed to repeat --- src/game/boe.specials.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index 4f4629569..22078abdc 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -1937,7 +1937,6 @@ void special_increase_age(long length, bool queue) { univ.party.age = j; queue_special(eSpecCtx::TOWN_TIMER, eSpecCtxType::TOWN, univ.town->timers[i].node, trigger_loc); } else run_special(eSpecCtx::TOWN_TIMER, eSpecCtxType::TOWN, univ.town->timers[i].node, trigger_loc, nullptr, nullptr, &need_redraw); - univ.town->timers[i].time = 0; } stat_area = true; if(need_redraw) @@ -1955,7 +1954,6 @@ void special_increase_age(long length, bool queue) { univ.party.age = j; queue_special(eSpecCtx::SCEN_TIMER, eSpecCtxType::SCEN, univ.scenario.scenario_timers[i].node, trigger_loc); } else run_special(eSpecCtx::SCEN_TIMER, eSpecCtxType::SCEN, univ.scenario.scenario_timers[i].node, trigger_loc, nullptr, nullptr,&need_redraw); - univ.scenario.scenario_timers[i].time = 0; } stat_area = true; if(need_redraw) @@ -1971,6 +1969,7 @@ void special_increase_age(long length, bool queue) { if(queue) queue_special(eSpecCtx::PARTY_TIMER, which_type, party_timers[i].node, trigger_loc); else run_special(eSpecCtx::PARTY_TIMER, which_type, party_timers[i].node, trigger_loc, nullptr, nullptr, &need_redraw); + // Party timers are triggered by special nodes and only run once! univ.party.party_event_timers[i].time = 0; univ.party.party_event_timers[i].node = -1; stat_area = true; From 435d65ff65c48c59bfc39e59b1090bca1a44db0f Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 4 Sep 2025 11:07:14 -0500 Subject: [PATCH 57/59] fix bug undoing draw monster --- src/scenedit/scen.actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 8d2a55c12..74096acc1 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -809,7 +809,7 @@ void commit_stroke() { }else if(!current_creatures_placed.empty()){ undo_list.add(action_ptr(new aPlaceEraseCreature(current_creatures_placed.size() > 1 ? "Place Creatures" : "Place Creature", true, current_creatures_placed))); update_edit_menu(); - current_items_placed.clear(); + current_creatures_placed.clear(); }else if(!current_fields_cleared.empty()){ undo_list.add(action_ptr(new aClearFields(current_fields_cleared))); update_edit_menu(); From 80b8668b519d1fa7c2f8e98e6e8d608d77f24010 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 4 Sep 2025 11:43:22 -0500 Subject: [PATCH 58/59] fix crash with up/down key on empty field --- src/dialogxml/widgets/field.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dialogxml/widgets/field.cpp b/src/dialogxml/widgets/field.cpp index 6f18e3bf9..70a7315db 100644 --- a/src/dialogxml/widgets/field.cpp +++ b/src/dialogxml/widgets/field.cpp @@ -560,6 +560,7 @@ void cTextField::handleInput(cKey key, bool record) { if(current_action) history.add(current_action), current_action.reset(); if(haveSelection && !select) selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint); + if(snippets.empty()) break; if(snippets[ip_row].at.y == snippets[0].at.y) { key.k = key_top; if(select) key.mod += mod_shift; @@ -574,6 +575,7 @@ void cTextField::handleInput(cKey key, bool record) { if(current_action) history.add(current_action), current_action.reset(); if(haveSelection && !select) selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint); + if(snippets.empty()) break; if(snippets[ip_row].at.y == snippets.back().at.y) { key.k = key_bottom; if(select) key.mod += mod_shift; From f250cd4aecb97bd4cf44ce1791639adaff3f244a Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Thu, 4 Sep 2025 13:07:55 -0500 Subject: [PATCH 59/59] fix clicking 'ok' in edit item dialog --- src/scenedit/scen.core.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp index c01d01f1c..9cb0c24c8 100644 --- a/src/scenedit/scen.core.cpp +++ b/src/scenedit/scen.core.cpp @@ -1897,6 +1897,7 @@ static bool edit_item_type_event_filter(cDialog& me, std::string hit, cItem& ite } if(commit_changes){ + temp_item = item; // We actually can't make the action undoable while the dialog is still open if(is_new){ undo_list.add(action_ptr(new aCreateDeleteItem(true, temp_item)));