diff --git a/osx/boe.actions.cpp b/osx/boe.actions.cpp index b8ad4608..4b6d68b0 100644 --- a/osx/boe.actions.cpp +++ b/osx/boe.actions.cpp @@ -1,5 +1,6 @@ #include +#include //#include "item.h" @@ -151,7 +152,7 @@ short monst_place_count = 0; // 1 - standard place 2 - place last // 0 - whole area, 1 - active area 2 - graphic 3 - item name // 4 - item cost 5 - item extra str 6 - item help button RECT shopping_rects[8][7]; -pending_special_type special_queue[20]; +std::queue special_queue; bool end_scenario = false; void init_screen_locs() //// @@ -325,8 +326,10 @@ bool handle_action(sf::Event event) the_point = location(event.mouseButton.x, event.mouseButton.y); the_point.x -= ul.x; the_point.y -= ul.y; - for (i = 0; i < 20; i++) // TODO: Does this cause problems by leaving some specials uncalled? - special_queue[i].spec = -1; + if(!special_queue.empty()) + printf("Note: %ld queued specials have been flushed without running!", special_queue.size()); + while(!special_queue.empty()) // TODO: Does this cause problems by leaving some specials uncalled? + special_queue.pop(); end_scenario = false; // Now split off the extra stuff, like talking and shopping. @@ -1291,17 +1294,11 @@ bool handle_action(sf::Event event) } // MARK: At this point, see if any specials have been queued up, and deal with them - // TODO: Use an std::queue for this. - for (i = 0; i < 20; i++) - if (special_queue[i].spec >= 0) { - long long store_time = univ.party.age; - univ.party.age = special_queue[i].trigger_time; + // Note: We just check once here instead of looping because run_special also pulls from the queue. + if(!special_queue.empty()) { s3 = 0; - run_special(special_queue[i].mode,special_queue[i].type,special_queue[i].spec, - special_queue[i].where,&s1,&s2,&s3); - special_queue[i].spec = -1; - long long change_time = univ.party.age - special_queue[i].trigger_time; - univ.party.age = store_time + change_time; + run_special(special_queue.front(), &s1, &s2, &s3); + special_queue.pop(); if (s3 > 0) draw_terrain(); } @@ -2220,9 +2217,9 @@ void increase_age()//// move_to_zero(PSD[SDF_PARTY_FLIGHT]); - if ((overall_mode > MODE_OUTDOORS) && (univ.town->lighting_type == 2)) { - univ.party.light_level = max (0,univ.party.light_level - 9); - if (univ.town->lighting_type == 3) { + if(overall_mode > MODE_OUTDOORS && univ.town->lighting_type >= LIGHT_DRAINS) { + increase_light(-9); + if(univ.town->lighting_type == LIGHT_NONE) { if (univ.party.light_level > 0) ASB("Your light is drained."); univ.party.light_level = 0; @@ -2658,7 +2655,7 @@ static void run_waterfalls(short mode){ // mode 0 - town, 1 - outdoors } draw_terrain(); print_buf(); - if ((cave_lore_present() > 0) && (get_ran(1,0,1) == 0)) + if ((wilderness_lore_present() > 0) && (get_ran(1,0,1) == 0)) add_string_to_buf(" (No supplies lost.)"); else if (univ.party.food > 1800){ add_string_to_buf(" (Many supplies lost.)"); @@ -3088,11 +3085,17 @@ bool is_sign(ter_num_t ter) bool check_for_interrupt(){ using kb = sf::Keyboard; + bool interrupt = false; #ifdef __APPLE__ if((kb::isKeyPressed(kb::LSystem) || kb::isKeyPressed(kb::RSystem)) && kb::isKeyPressed(kb::Period)) - return true; + interrupt = true; #endif if((kb::isKeyPressed(kb::LControl) || kb::isKeyPressed(kb::RControl)) && kb::isKeyPressed(kb::C)) - return true; + interrupt = true; + if(interrupt) { + // TODO: A customized dialog with a more appropriate message + cChoiceDlog confirm("quit-confirm-nosave.xml", {"quit","cancel"}); + if(confirm.show() == "quit") return true; + } return false; } diff --git a/osx/boe.combat.cpp b/osx/boe.combat.cpp index eb5b0387..9582e8a5 100644 --- a/osx/boe.combat.cpp +++ b/osx/boe.combat.cpp @@ -1787,6 +1787,7 @@ void do_monster_turn() { bool acted_yet, had_monst = false,printed_poison = false,printed_disease = false,printed_acid = false; bool redraw_not_yet_done = true; + bool special_called = false; location targ_space,move_targ,l; short i,j,k,num_monst, target,r1,move_target; cCreature *cur_monst; @@ -2169,6 +2170,12 @@ void do_monster_turn() cur_monst->cur_loc,130,cur_monst->attitude) == true) {monst_spell_note(cur_monst->number,33); play_sound(61);} } + if ((cur_monst->radiate_1 == 14) && !special_called && party_can_see_monst(i)) { + short s1, s2, s3; + special_called = true; + take_m_ap(1,cur_monst); + run_special(eSpecCtx::MONST_SPEC_ABIL,0,cur_monst->radiate_2,cur_monst->cur_loc,&s1,&s2,&s3); + } } combat_posing_monster = current_working_monster = -1; @@ -2369,7 +2376,7 @@ void monster_attack_pc(short who_att,short target) && (get_ran(1,0,2) < 2)) { add_string_to_buf(" Causes disease! "); print_buf(); - disease_pc(target,(attacker->spec_skill == 25) ? 6 : 2); + disease_pc(target,6); } // Petrification touch @@ -2381,6 +2388,29 @@ void monster_attack_pc(short who_att,short target) print_buf(); kill_pc(target,eMainStatus::STONE); // petrified, duh! } +#if 0 // TODO: This is *i's version of the petrification touch ability. + // It seems better in some ways, like printing a message when you resist, + // but its calculation is very different, so I'm not sure what to with it. + // Note, his version has also been incorporated into monster_attack_monster. + + // Petrify target + if (attacker->spec_skill == 30) { + add_string_to_buf(" Petrification touch! "); + r1 = max(0,(get_ran(1,0,100) - univ.party[target].level + 0.5*attacker->level)); + // Equip petrify protection? + if (pc_has_abil_equip(target,49) < 24) + r1 = 0; + // Check if petrified. + if (r1 > 60) { + kill_pc(target,eMainStatus::STONE); + add_string_to_buf(" Turned to stone! "); + play_sound(43); + } + else { + add_string_to_buf(" Resists! "); + } + } +#endif // Undead xp drain if (((attacker->spec_skill == 16) || (attacker->spec_skill == 17)) @@ -2543,11 +2573,34 @@ void monster_attack_monster(short who_att,short attackee) add_string_to_buf(" Dumbfounds! "); dumbfound_monst(target,2); } + // Disease target + if (((attacker->spec_skill == 25)) + && (get_ran(1,0,2) < 2)) { + add_string_to_buf(" Causes disease! "); + print_buf(); + disease_monst(target,6); + } // Paralyze target if (attacker->spec_skill == 29) { add_string_to_buf(" Paralysis touch! "); charm_monst(target,-5,eStatus::PARALYZED,500); } + // Petrify target + if (attacker->spec_skill == 30) { + add_string_to_buf(" Petrification touch! "); + r1 = max(0,(get_ran(1,0,100) - target->level + 0.5*attacker->level)); + // Check if petrified. + if ((r1 < 60) || (target->immunities & 2)) { + add_string_to_buf(" Resists! "); + } + else { + kill_monst(target,7); + add_string_to_buf(" Turned to stone! "); + play_sound(43); + + } + } + // Acid touch if (attacker->spec_skill == 31) { add_string_to_buf(" Acid touch! "); diff --git a/osx/boe.consts.h b/osx/boe.consts.h index e5312b92..9d4838c0 100644 --- a/osx/boe.consts.h +++ b/osx/boe.consts.h @@ -32,6 +32,8 @@ #define SFX_RUBBLE 128 /* stuff done flags */ +#define SDF_SPEC_LOC_X 301][0 // For special nodes to access the trigger location +#define SDF_SPEC_LOC_Y 301][1 //#define SDF_IS_PARTY_SPLIT 304][0 //#define SDF_PARTY_SPLIT_X 304][1 //#define SDF_PARTY_SPLIT_Y 304][2 diff --git a/osx/boe.dlgutil.cpp b/osx/boe.dlgutil.cpp index bf6df7ba..7ebf5644 100644 --- a/osx/boe.dlgutil.cpp +++ b/osx/boe.dlgutil.cpp @@ -486,7 +486,7 @@ void set_up_shop_array() if (i == minmax(0,31,i)) { store_i = store_mage_spells(i); store_shop_costs[shop_pos] = store_i.value; - store_shop_items[shop_pos] = 800 + i + 30; + store_shop_items[shop_pos] = 800 + i; shop_pos++; } break; @@ -495,7 +495,7 @@ void set_up_shop_array() if (i == minmax(0,31,i)) { store_i = store_priest_spells(i); store_shop_costs[shop_pos] = store_i.value; - store_shop_items[shop_pos] = 900 + i + 30; + store_shop_items[shop_pos] = 900 + i; shop_pos++; } break; diff --git a/osx/boe.graphutil.cpp b/osx/boe.graphutil.cpp index 77cc4a4b..f3f46fca 100644 --- a/osx/boe.graphutil.cpp +++ b/osx/boe.graphutil.cpp @@ -2,6 +2,7 @@ #include #include +#include //#include "item.h" @@ -53,7 +54,7 @@ extern sf::Texture fields_gworld,anim_gworld,vehicle_gworld,terrain_gworld[NUM_T //extern short wish_list[STORED_GRAPHICS]; //extern short storage_status[STORED_GRAPHICS]; // 0 - empty 1 - in use 2 - there, not in use extern short terrain_there[9][9]; -extern pending_special_type special_queue[20]; +extern std::queue special_queue; extern location ul; extern location pc_pos[6],center; @@ -214,12 +215,10 @@ void draw_monsters() //// for (i = 0; i < univ.town->max_monst(); i++) if ((univ.town.monst[i].active != 0) && (univ.town.monst[i].spec_skill != 11)) if (party_can_see_monst(i)) { - check_if_monst_seen(univ.town.monst[i].number); + check_if_monst_seen(univ.town.monst[i].number, univ.town.monst[i].cur_loc); where_draw.x = univ.town.monst[i].cur_loc.x - center.x + 4; where_draw.y = univ.town.monst[i].cur_loc.y - center.y + 4; get_monst_dims(univ.town.monst[i].number,&width,&height); - if (point_onscreen(center,univ.town.monst[i].cur_loc) == true) - play_see_monster_str(univ.town.monst[i].number); // TODO: This also gets called by check_if_monst_seen! for (k = 0; k < width * height; k++) { store_loc = where_draw; @@ -261,7 +260,7 @@ void draw_monsters() //// for (i = 0; i < univ.town->max_monst(); i++) if ((univ.town.monst[i].active != 0) && (univ.town.monst[i].spec_skill != 11)) if (point_onscreen(center,univ.town.monst[i].cur_loc) || party_can_see_monst(i)) { - check_if_monst_seen(univ.town.monst[i].number); + check_if_monst_seen(univ.town.monst[i].number,univ.town.monst[i].cur_loc); where_draw.x = univ.town.monst[i].cur_loc.x - center.x + 4; where_draw.y = univ.town.monst[i].cur_loc.y - center.y + 4; get_monst_dims(univ.town.monst[i].number,&width,&height); @@ -304,7 +303,7 @@ void draw_monsters() //// } } -void play_see_monster_str(unsigned short m){ +void play_see_monster_str(unsigned short m, location monst_loc) { short str1, str2, pic, snd, spec; ePicType type; str1 = scenario.scen_monsters[m].see_str1; @@ -325,16 +324,7 @@ void play_see_monster_str(unsigned short m){ } // Then run the special, if any if(spec > -1){ - for(int i = 2; i < 20; i++){ - if(special_queue[i].spec == -1){ - special_queue[i].spec = spec; - special_queue[i].mode = eSpecCtx::SEE_MONST; - special_queue[i].type = 0; - special_queue[i].trigger_time = univ.party.age; - special_queue[i].where = loc(); // TODO: Maybe a different location should be passed? - break; - } - } + queue_special(eSpecCtx::SEE_MONST, 0, spec, monst_loc); } } @@ -772,11 +762,11 @@ char get_fluid_trim(location where,ter_num_t ter_type) } // Sees if party has seen a monster of this sort, gives special messages as necessary -void check_if_monst_seen(unsigned short m_num) { +void check_if_monst_seen(unsigned short m_num, location at) { // Give special messages if necessary if (!univ.party.m_seen[m_num]) { univ.party.m_seen[m_num] = true; - play_see_monster_str(m_num); + play_see_monster_str(m_num, at); } // Make the monster vocalize if applicable snd_num_t sound = scenario.scen_monsters[m_num].ambient_sound; diff --git a/osx/boe.graphutil.h b/osx/boe.graphutil.h index cc886b52..ccd5d2f8 100644 --- a/osx/boe.graphutil.h +++ b/osx/boe.graphutil.h @@ -8,7 +8,7 @@ void draw_one_terrain_spot (short i,short j,short terrain_to_draw); void draw_monsters(); -void play_see_monster_str(unsigned short m); +void play_see_monster_str(unsigned short m, location monst_loc); void draw_pcs(location center,short mode); void draw_outd_boats(location center); void draw_town_boat(location center) ; @@ -21,7 +21,7 @@ bool is_shore(ter_num_t ter_type); bool is_wall(ter_num_t ter_type); bool is_ground(ter_num_t ter_type); char get_fluid_trim(location where,ter_num_t ter_type); -void check_if_monst_seen(unsigned short m_num); +void check_if_monst_seen(unsigned short m_num, location monst_loc); void play_ambient_sound(); void draw_items(location where); diff --git a/osx/boe.items.cpp b/osx/boe.items.cpp index 0057a027..6060b5c0 100644 --- a/osx/boe.items.cpp +++ b/osx/boe.items.cpp @@ -11,6 +11,7 @@ #include "boe.graphics.h" #include "boe.text.h" #include "boe.items.h" +#include "boe.specials.h" #include "boe.party.h" #include "boe.fields.h" #include "boe.locutils.h" @@ -835,40 +836,57 @@ short get_item(location place,short pc_num,bool check_container) void make_town_hostile() { + set_town_attitude(0, -1, 1); + return; +} + +// Set Attitude node adapted from *i, meant to replace make_town_hostile node +// att is any valid monster attitude (so, 0..3) +void set_town_attitude(short lo,short hi,short att) { short i,num; - bool fry_party = false; + short a[3] = {}; // Dummy values to pass to run_special. if (which_combat_type == 0) return; give_help(53,0); univ.town.monst.friendly = 1; - //// - for (i = 0; i < univ.town->max_monst(); i++) + + // Nice smart indexing, like Python :D + if(lo <= -univ.town->max_monst()) + lo = 0; + if(lo < 0) + lo = univ.town->max_monst() + lo; + if(hi <= -univ.town->max_monst()) + hi = 0; + if(hi < 0) + hi = univ.town->max_monst() + hi; + if(hi < lo) + std::swap(lo, hi); + + for (i = lo; i <= hi; i++) { if ((univ.town.monst[i].active > 0) && (univ.town.monst[i].summoned == 0)){ - univ.town.monst[i].attitude = 1; + univ.town.monst[i].attitude = att; num = univ.town.monst[i].number; + // If made hostile, make mobile + if (att == 1 || att == 3) { + univ.town.monst[i].mobility = 1; + // If a "guard", give a power boost if (scenario.scen_monsters[num].spec_skill == 37) { univ.town.monst[i].active = 2; - - // If a town, give pwoer boost univ.town.monst[i].health *= 3; univ.town.monst[i].status[eStatus::HASTE_SLOW] = 8; univ.town.monst[i].status[eStatus::BLESS_CURSE] = 8; } + + } } + } // In some towns, doin' this'll getcha' killed. - //// wedge in special - // TODO: Resupport this! - - if (fry_party == true) { - for (i = 0; i < 6; i++) - if(univ.party[i].main_status > eMainStatus::ABSENT) - univ.party[i].main_status = eMainStatus::ABSENT; - stat_window = 6; - boom_anim_active = false; - } + // (Or something else! Killing the party would be the responsibility of whatever special node is called.) + if((att == 1 || att == 3) && univ.town->spec_on_hostile >= 0) + run_special(eSpecCtx::TOWN_HOSTILE, 2, univ.town->spec_on_hostile, univ.party.p_loc, &a[0], &a[1], &a[2]); } diff --git a/osx/boe.items.h b/osx/boe.items.h index 2231b18d..b32871ac 100644 --- a/osx/boe.items.h +++ b/osx/boe.items.h @@ -35,6 +35,7 @@ short get_item(location place,short pc_num,bool check_container); short get_prot_level(short pc_num,short abil); void make_town_hostile(); +void set_town_attitude(short lo,short hi,short att); bool display_item(location from_loc,short pc_num,short mode, bool check_container); short custom_choice_dialog(std::array& strs,short pic_num,ePicType pic_type,std::array& buttons) ; //short fancy_choice_dialog(short which_dlog,short parent); diff --git a/osx/boe.main.cpp b/osx/boe.main.cpp index 6f25da04..8aff1433 100644 --- a/osx/boe.main.cpp +++ b/osx/boe.main.cpp @@ -846,6 +846,7 @@ void pause(short length) sf::sleep(time_in_ticks(len)); } +// TODO: I think this should be in a better place, maybe in cParty? // stuff done legit, i.e. flags are within proper ranges for stuff done flag bool sd_legit(short a, short b) { diff --git a/osx/boe.monster.cpp b/osx/boe.monster.cpp index cecc89a9..4d1db66b 100644 --- a/osx/boe.monster.cpp +++ b/osx/boe.monster.cpp @@ -1153,50 +1153,71 @@ void poison_monst(cCreature *which_m,short how_much) return; } which_m->status[eStatus::POISON] = min(8, which_m->status[eStatus::POISON] + how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 4); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 4); + else + monst_spell_note(which_m->number,34); } void acid_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::ACID] = minmax(-8,8, which_m->status[eStatus::ACID] + how_much); - monst_spell_note(which_m->number,31); - + if(how_much >= 0) + monst_spell_note(which_m->number,31); + else + monst_spell_note(which_m->number,48); } void slow_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::HASTE_SLOW] = minmax(-8,8, which_m->status[eStatus::HASTE_SLOW] - how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 2); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 2); + else + monst_spell_note(which_m->number,35); } void curse_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::BLESS_CURSE] = minmax(-8,8, which_m->status[eStatus::BLESS_CURSE] - how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 5); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 5); + else + monst_spell_note(which_m->number,36); } void web_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::WEBS] = minmax(-8,8, which_m->status[eStatus::WEBS] + how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 19); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 19); + else + monst_spell_note(which_m->number,37); } void scare_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->morale = which_m->morale - how_much; - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 1); + // TODO: I don't think there's currently any way to increase monster morale at the moment - add one! + if(how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 1); + else + monst_spell_note(which_m->number,47); } void disease_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::DISEASE] = minmax(-8,8, which_m->status[eStatus::DISEASE] + how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 25); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 25); + else + monst_spell_note(which_m->number,38); } @@ -1204,7 +1225,10 @@ void dumbfound_monst(cCreature *which_m,short how_much) { magic_adjust(which_m,&how_much); which_m->status[eStatus::DUMB] = minmax(-8,8, which_m->status[eStatus::DUMB] + how_much); - monst_spell_note(which_m->number,(how_much == 0) ? 10 : 22); + if (how_much >= 0) + monst_spell_note(which_m->number,(how_much == 0) ? 10 : 22); + else + monst_spell_note(which_m->number,39); } @@ -1242,10 +1266,12 @@ void charm_monst(cCreature *which_m,short penalty,eStatus which_status,short amo } else { which_m->status[which_status] = amount; - if (which_status == eStatus::ASLEEP) + if (which_status == eStatus::ASLEEP && (amount >= 0)) monst_spell_note(which_m->number,28); - if (which_status == eStatus::PARALYZED) + if (which_status == eStatus::PARALYZED && (amount >= 0)) monst_spell_note(which_m->number,30); + if (amount < 0) + monst_spell_note(which_m->number,40); } //one_sound(53); } diff --git a/osx/boe.party.cpp b/osx/boe.party.cpp index c354e151..5c10125a 100644 --- a/osx/boe.party.cpp +++ b/osx/boe.party.cpp @@ -177,8 +177,10 @@ void put_pick_spell_graphics(); //mode; // 0 - prefab 1 - regular 2 - debug +// Note: mode 1 is never used void init_party(short mode) { + // TODO: Remove in favour of cParty constructor. short i,j,k,l; cVehicle null_boat;// = {{0,0},{0,0},{0,0},200,false}; @@ -252,6 +254,10 @@ void init_party(short mode) for (j = 0; j < 8; j++) univ.party.item_taken[i][j] = 0; + // Zero out campaign flags and pointers + univ.party.campaign_flags.clear(); + univ.party.pointers.clear(); + refresh_store_items(); @@ -814,6 +820,8 @@ void increase_light(short amt) location where; univ.party.light_level += amt; + if(univ.party.light_level < 0) + univ.party.light_level = 0; if (is_combat()) { for (i = 0; i < 6; i++) if(univ.party[i].main_status == eMainStatus::ALIVE) { @@ -941,13 +949,23 @@ void drain_pc(short which_pc,short how_much) } } -short mage_lore_total() -{ - short total = 0,i; +// mode: 0 = total, 1 = mean, 2 = min, 3 = max +short check_party_stat(short which_stat, short mode) { + short total = mode == 2 ? std::numeric_limits::max() : 0, num_pcs = 0; - for (i = 0; i < 6; i++) - if(univ.party[i].main_status == eMainStatus::ALIVE) - total += univ.party[i].skills[11]; + for(short i = 0; i < 6; i++) + if(univ.party[i].main_status == eMainStatus::ALIVE) { + num_pcs++; + if(mode < 2) + total += univ.party[i].skills[which_stat]; + else if(mode == 2) + total = max(univ.party[i].skills[which_stat], total); + else if(mode == 3) + total = min(univ.party[i].skills[which_stat], total); + } + + if(mode == 1 && num_pcs > 0) + total /= num_pcs; return total; } @@ -3122,20 +3140,38 @@ void take_ap(short num) univ.party[current_pc].ap = max(0,univ.party[current_pc].ap - num); } -short cave_lore_present() -{ +// TODO: Enumify +// TODO: Use this to check cave lore and woodsman for the purpose of gaining food +// (It replaces cave_lore_present() and woodsman_present(), but the latter was never used, +// and the former was only used to help you lose less food when going over a waterfall. +short trait_present(short which_trait) { short i,ret = 0; for (i = 0; i < 6; i++) - if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[4] > 0) + if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[which_trait] > 0) ret += 1; return ret; } -short woodsman_present() -{ - short i,ret = 0; - for (i = 0; i < 6; i++) - if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[5] > 0) - ret += 1; - return ret; +short wilderness_lore_present() { + // TODO: Add contional statement to choose between these + // (Probably requires something added to terrain types to specify that it's cave/surface wilderness.) + return trait_present(4); // Cave Lore + return trait_present(5); // Woodsman +} + +short party_size(bool only_living) { + short num_pcs = 0; + for (short i = 0; i < 6; i++) { + if (!only_living) { + if (univ.party[i].main_status != eMainStatus::ABSENT) + num_pcs++; + } + else { + if (univ.party[i].main_status == eMainStatus::ALIVE) + num_pcs++; + } + } + + return num_pcs; + } diff --git a/osx/boe.party.h b/osx/boe.party.h index 9569843c..c1a8550a 100644 --- a/osx/boe.party.h +++ b/osx/boe.party.h @@ -20,7 +20,7 @@ void restore_sp_party(short amt); void award_party_xp(short amt); void award_xp(short pc_num,short amt); void drain_pc(short which_pc,short how_much); -short mage_lore_total(); +short check_party_stat(short which_stat, short mode); bool poison_weapon( short pc_num, short how_much,short safe); bool is_weapon(short pc_num,short item); void cast_spell(short type); @@ -55,10 +55,11 @@ bool damage_pc(short which_pc,short how_much,eDamageType damage_type,eRace type_ void kill_pc(short which_pc,eMainStatus type); void set_pc_moves(); void take_ap(short num); -short cave_lore_present(); -short woodsman_present(); +short trait_present(short which_trait); +short wilderness_lore_present(); void print_spell_cast(short spell_num,short which); void put_party_in_scen(std::string scen_name); +short party_size(bool only_living); // This is defined in pc.editors.cpp since it is also used by the character editor bool spend_xp(short pc_num, short mode, cDialog* parent); diff --git a/osx/boe.specials.cpp b/osx/boe.specials.cpp index cf22c2e4..eb5f7b36 100644 --- a/osx/boe.specials.cpp +++ b/osx/boe.specials.cpp @@ -1,6 +1,7 @@ #include #include +#include //#include "item.h" @@ -54,6 +55,7 @@ extern bool fast_bang,end_scenario; extern short town_type; extern cScenario scenario; extern cUniverse univ; +extern std::queue special_queue; //extern piles_of_stuff_dumping_type *data_store; bool can_draw_pcs = true; @@ -124,20 +126,22 @@ bool handle_wandering_specials (short /*which*/,short mode) // wanderin spec 99 -> generic spec { - // TODO: Should a better location be passed to these specials? + // TODO: Is loc_in_sec the correct location to pass here? + // (I'm pretty sure it is, but I should verify it somehow.) + // (It's either that or univ.party.p_loc.) short s1 = 0,s2 = 0,s3 = 0; if ((mode == 0) && (store_wandering_special.spec_on_meet >= 0)) { // When encountering - run_special(eSpecCtx::OUTDOOR_ENC,1,store_wandering_special.spec_on_meet,loc(),&s1,&s2,&s3); + run_special(eSpecCtx::OUTDOOR_ENC,1,store_wandering_special.spec_on_meet,univ.party.loc_in_sec,&s1,&s2,&s3); if (s1 > 0) return false; } if ((mode == 1) && (store_wandering_special.spec_on_win >= 0)) {// After defeating - run_special(eSpecCtx::WIN_ENCOUNTER,1,store_wandering_special.spec_on_win,loc(),&s1,&s2,&s3); + run_special(eSpecCtx::WIN_ENCOUNTER,1,store_wandering_special.spec_on_win,univ.party.loc_in_sec,&s1,&s2,&s3); } if ((mode == 2) && (store_wandering_special.spec_on_flee >= 0)) {// After fleeing like a buncha girly men - run_special(eSpecCtx::FLEE_ENCOUNTER,1,store_wandering_special.spec_on_flee,loc(),&s1,&s2,&s3); + run_special(eSpecCtx::FLEE_ENCOUNTER,1,store_wandering_special.spec_on_flee,univ.party.loc_in_sec,&s1,&s2,&s3); } return true; } @@ -595,7 +599,7 @@ void use_spec_item(short item) short i,j,k; location null_loc; - run_special(eSpecCtx::USE_SPEC_ITEM,0,scenario.special_items[item].special,loc(),&i,&j,&k); + run_special(eSpecCtx::USE_SPEC_ITEM,0,scenario.special_items[item].special,univ.party.p_loc,&i,&j,&k); } @@ -604,6 +608,7 @@ void use_item(short pc,short item) { bool take_charge = true,inept_ok = false; short abil,level,i,j,item_use_code,str,type,r1; + short sp[3] = {}; // Dummy values to pass to run_special; not actually used eStatus which_stat; char to_draw[60]; location user_loc; @@ -1045,6 +1050,11 @@ void use_item(short pc,short item) break; } break; + case ITEM_CALL_SPECIAL: + // TODO: Should this have its own separate eSpecCtx? + run_special(eSpecCtx::USE_SPEC_ITEM,0,str,user_loc,&sp[0],&sp[1],&sp[2]); + break; + // spell effects case ITEM_SPELL_FLAME: @@ -1671,7 +1681,8 @@ void kill_monst(cCreature *which_m,short who_killed) if (sd_legit(which_m->spec1,which_m->spec2) == true) PSD[which_m->spec1][which_m->spec2] = 1; - run_special(eSpecCtx::KILL_MONST,2,which_m->special_on_kill,which_m->cur_loc,&s1,&s2,&s3); + if (which_m->special_on_kill >= 0) + run_special(eSpecCtx::KILL_MONST,2,which_m->special_on_kill,which_m->cur_loc,&s1,&s2,&s3); if (which_m->radiate_1 == 15) run_special(eSpecCtx::KILL_MONST,0,which_m->radiate_2,which_m->cur_loc,&s1,&s2,&s3); @@ -1863,7 +1874,7 @@ void special_increase_age() unsigned short i; short s1,s2,s3; bool redraw = false,stat_area = false; - location null_loc; + location null_loc; // TODO: Should we pass the party's location here? It doesn't quite make sense to me though... if(is_town()) { for(i = 0; i < 8; i++) @@ -1903,6 +1914,21 @@ void special_increase_age() } +void queue_special(eSpecCtx mode, short which_type, short spec, location spec_loc) { + if(spec < 0) return; + pending_special_type queued_special; + queued_special.spec = spec; + queued_special.where = spec_loc; + queued_special.type = which_type; + queued_special.mode = mode; +// queued_special.trigger_time = univ.party.age; // Don't think this is needed after all. + special_queue.push(queued_special); +} + +void run_special(pending_special_type spec, short* a, short* b, short* redraw) { + run_special(spec.mode, spec.type, spec.spec, spec.where, a, b, redraw); +} + // This is the big painful one, the main special engine // which_mode - says when it was called // 0 - out moving (a - 1 if blocked) @@ -1932,10 +1958,11 @@ void run_special(eSpecCtx which_mode,short which_type,short start_spec,location { short cur_spec,cur_spec_type,next_spec,next_spec_type; cSpecial cur_node; - short num_nodes = 0; + int num_nodes = 0; - if (special_in_progress == true) { - giveError("The scenario called a special node while processing another special encounter. The second special will be ignored."); + // Modify this to put a value in the special node queue instead of raising an error + if(special_in_progress && start_spec >= 0) { + queue_special(which_mode, which_type, start_spec, spec_loc); return; } special_in_progress = true; @@ -1955,6 +1982,25 @@ void run_special(eSpecCtx which_mode,short which_type,short start_spec,location next_spec = -1; cur_node = get_node(cur_spec,cur_spec_type); + // Store the special's location in reserved pointers + univ.party.force_ptr(10, 301, 0); + univ.party.force_ptr(11, 301, 1); + // And put the location there + PSD[SDF_SPEC_LOC_X] = spec_loc.x; + PSD[SDF_SPEC_LOC_Y] = spec_loc.y; + // (We do this here instead of before the loop, in case a queued special has a different location.) + + // Convert pointer values to reference values + if(cur_node.sd1 < -1) cur_node.sd1 = univ.party.get_ptr(-cur_node.sd1); + if(cur_node.sd2 < -1) cur_node.sd2 = univ.party.get_ptr(-cur_node.sd2); + if(cur_node.ex1a < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex1a); + if(cur_node.ex1b < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex1b); + if(cur_node.ex1c < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex1c); + if(cur_node.ex2a < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex2a); + if(cur_node.ex2b < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex2b); + if(cur_node.ex2c < -1) cur_node.ex1a = univ.party.get_ptr(-cur_node.ex2c); + // TODO: Should pointers be allowed in message, pict, or jumpto as well? + //print_nums(1111,cur_spec_type,cur_node.type); if(cur_node.type == eSpecType::ERROR) { @@ -1991,8 +2037,17 @@ void run_special(eSpecCtx which_mode,short which_type,short start_spec,location num_nodes++; + if(next_spec == -1 && !special_queue.empty()) { + pending_special_type pending = special_queue.front(); + which_mode = pending.mode; + which_type = pending.type; + next_spec = pending.spec; + spec_loc = pending.where; + special_queue.pop(); + } + if(check_for_interrupt()){ - giveError("The special encounter was interrupted. The scenario may be in an unexpected state; it is recommended that you reload from a saved game."); + add_string_to_buf("The special encounter was interrupted. The scenario may be in an unexpected state; it is recommended that you reload from a saved game.", 3); next_spec = -1; } } @@ -2062,9 +2117,9 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, get_strs(str1,str2, cur_spec_type,cur_node.m1 + mess_adj[cur_spec_type], cur_node.m2 + mess_adj[cur_spec_type]); if (cur_node.m1 >= 0) - ASB(str1.c_str()); + ASB(str1.c_str(), 4); if (cur_node.m2 >= 0) - ASB(str2.c_str()); + ASB(str2.c_str(), 4); break; case eSpecType::FLIP_SDF: setsd(cur_node.sd1,cur_node.sd2, @@ -2105,7 +2160,7 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, check_mess = true; if (spec.ex1a != minmax(0,scenario.num_towns - 1,spec.ex1a)) giveError("Town out of range."); - else univ.party.can_find_town[spec.ex1a] = (spec.ex1b == 0) ? 0 : 1; + else univ.party.can_find_town[spec.ex1a] = spec.ex2a; *redraw = true; break; case eSpecType::MAJOR_EVENT_OCCURRED: @@ -2160,6 +2215,24 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, case eSpecType::END_SCENARIO: end_scenario = true; break; + case eSpecType::SET_POINTER: + if(spec.ex1a < 0) + giveError("Attempted to assign a pointer out of range (100..199)"); + else try { + if(spec.sd1 < 0 && spec.sd2 < 0) + univ.party.clear_ptr(spec.ex1a); + else univ.party.set_ptr(spec.sd1,spec.sd2,spec.ex1a); + } catch(std::range_error x) { + giveError(x.what()); + } + break; + case eSpecType::SET_CAMP_FLAG: + if(!sd_legit(spec.sd1,spec.sd2)) + giveError("Stuff Done flag out of range (x - 0..299, y - 0..49)."); + else { + set_campaign_flag(spec.sd1,spec.sd2,spec.ex1a,spec.ex1b,spec.m1,spec.ex2a); + } + break; } if (check_mess == true) { handle_message(which_mode,cur_spec_type,cur_node.m1,cur_node.m2,a,b); @@ -2388,6 +2461,11 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, switch (cur_node.type) { case eSpecType::SELECT_PC: check_mess = false; + // If this <= 0, pick PC normally + // TODO: I think this is for compatibility with old scenarios? If so, remove it and just convert data on load. + // (Actually, I think the only compatibility thing is that it's <= instead of ==) + if (spec.ex2a <= 0) { + if (spec.ex1a == 2) current_pc_picked_in_spec_enc = -1; else if (spec.ex1a == 1) { @@ -2402,6 +2480,32 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } if (i == 6)// && (spec.ex1b >= 0)) *next_spec = spec.ex1b; + + } + else if(spec.ex2a > 10 || spec.ex2a <= 16) { + // Select a specific PC + short pc = spec.ex2a - 11; + // Honour the request for alive PCs only. + if(spec.ex1a == 1 || univ.party[pc].main_status == eMainStatus::ALIVE) + current_pc_picked_in_spec_enc = pc; + } else { + // Pick random PC (from *i) + // TODO: What if spec.ex1a == 2? + + if (spec.ex1a == 0) { + short pc_alive = 0; + while (pc_alive == 0) { + i = get_ran(1,0,5); + if (univ.party[i].main_status == eMainStatus::ALIVE) + pc_alive = 1; + } + current_pc_picked_in_spec_enc = i; + } + else { + i = get_ran(1,0,5); + current_pc_picked_in_spec_enc = i; + } + } break; case eSpecType::DAMAGE: { @@ -2416,16 +2520,36 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, break; } case eSpecType::AFFECT_HP: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) univ.party[i].cur_health = minmax(0,univ.party[i].max_health, univ.party[i].cur_health + spec.ex1a * (spec.ex1b ? -1: 1)); + } + else { + univ.town.monst[spec.ex2a].health = minmax(0, univ.town.monst[spec.ex2a].m_health, + univ.town.monst[spec.ex2a].health + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + if (spec.ex1b == 0) + monst_spell_note(univ.town.monst[spec.ex2a].number,41); + else + monst_spell_note(univ.town.monst[spec.ex2a].number,42); + } break; case eSpecType::AFFECT_SP: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) univ.party[i].cur_sp = minmax(0, univ.party[i].max_sp, univ.party[i].cur_sp + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + } + else { + univ.town.monst[spec.ex2a].mp = minmax(0, univ.town.monst[spec.ex2a].max_mp, + univ.town.monst[spec.ex2a].mp + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + if (spec.ex1b == 0) + monst_spell_note(univ.town.monst[spec.ex2a].number,43); + else + monst_spell_note(univ.town.monst[spec.ex2a].number,44); + } break; case eSpecType::AFFECT_XP: for (i = 0; i < 6; i++) @@ -2440,12 +2564,14 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, univ.party[i].skill_pts + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); break; case eSpecType::AFFECT_DEADNESS: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) { if (spec.ex1b == 0) { if ((univ.party[i].main_status > eMainStatus::ABSENT) && (univ.party[i].main_status < eMainStatus::SPLIT)) univ.party[i].main_status = eMainStatus::ALIVE; } + else if (univ.party[i].main_status == eMainStatus::ABSENT); else switch(spec.ex1a){ // When passed to kill_pc, the SPLIT party status actually means "no saving throw". case 0: @@ -2457,8 +2583,26 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } } *redraw = 1; + } + else { + // Kill monster + if ((univ.town.monst[spec.ex2a].active > 0) && (spec.ex1b > 0)) { + // If dead/dust actually kill, if stone just erase + if (spec.ex1a < 2) { + kill_monst(&univ.town.monst[spec.ex2a],7); + monst_spell_note(univ.town.monst[spec.ex2a].number,46); + } + univ.town.monst[spec.ex2a].active = 0; + } + // Bring back to life + if ((univ.town.monst[spec.ex2a].active == 0) && (spec.ex1b == 0)) { + univ.town.monst[spec.ex2a].active = 1; + monst_spell_note(univ.town.monst[spec.ex2a].number,45); + } + } break; case eSpecType::AFFECT_POISON: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) { if (spec.ex1b == 0) { @@ -2466,8 +2610,18 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } else poison_pc(i,spec.ex1a); } + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + poison_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_SPEED: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) { if (spec.ex1b == 0) { @@ -2475,6 +2629,15 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } else slow_pc(i,spec.ex1a); } + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + slow_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_INVULN: for (i = 0; i < 6; i++) @@ -2487,14 +2650,34 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, affect_pc(i,eStatus::MAGIC_RESISTANCE,spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); break; case eSpecType::AFFECT_WEBS: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) affect_pc(i,eStatus::WEBS,spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + web_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_DISEASE: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) affect_pc(i,eStatus::DISEASE,spec.ex1a * ((spec.ex1b != 0) ? 1: -1)); + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + disease_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_SANCTUARY: for (i = 0; i < 6; i++) @@ -2502,16 +2685,37 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, affect_pc(i,eStatus::INVISIBLE,spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); break; case eSpecType::AFFECT_CURSE_BLESS: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) affect_pc(i,eStatus::BLESS_CURSE,spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + curse_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_DUMBFOUND: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) affect_pc(i,eStatus::DUMB,spec.ex1a * ((spec.ex1b == 0) ? -1: 1)); + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + dumbfound_monst(&univ.town.monst[spec.ex2a],alvl); + } + } break; case eSpecType::AFFECT_SLEEP: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) { if (spec.ex1b == 0) { @@ -2519,8 +2723,18 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } else sleep_pc(i,spec.ex1a,eStatus::ASLEEP,10); } + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + charm_monst(&univ.town.monst[spec.ex2a],0,eStatus::ASLEEP,alvl); + } + } break; case eSpecType::AFFECT_PARALYSIS: + if (spec.ex2a < 0) { for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) { if (spec.ex1b == 0) { @@ -2528,6 +2742,15 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, } else sleep_pc(i,spec.ex1a,eStatus::PARALYZED,10); } + } + else { + if (univ.town.monst[spec.ex2a].active > 0) { + short alvl = spec.ex1a; + if (spec.ex1b == 0) + alvl = -1*alvl; + charm_monst(&univ.town.monst[spec.ex2a],0,eStatus::PARALYZED,alvl); + } + } break; case eSpecType::AFFECT_STAT: if (spec.ex2a != minmax(0,18,spec.ex2a)) { @@ -2540,22 +2763,22 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, univ.party[i].skills[spec.ex2a] + spec.ex1a * ((spec.ex1b != 0) ? -1: 1)); break; case eSpecType::AFFECT_MAGE_SPELL: - if (spec.ex1a != minmax(0,31,spec.ex1a)) { - giveError("Mage spell is out of range (0 - 31). See docs."); + if (spec.ex1a != minmax(0,61,spec.ex1a)) { + giveError("Mage spell is out of range (0 - 61). See docs."); break; } for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) - univ.party[i].mage_spells[spec.ex1a + 30] = true; + univ.party[i].mage_spells[spec.ex1a] = spec.ex1b; break; case eSpecType::AFFECT_PRIEST_SPELL: - if (spec.ex1a != minmax(0,31,spec.ex1a)) { - giveError("Priest spell is out of range (0 - 31). See docs."); + if (spec.ex1a != minmax(0,61,spec.ex1a)) { + giveError("Priest spell is out of range (0 - 61). See docs."); break; } for (i = 0; i < 6; i++) if ((pc < 0) || (pc == i)) - univ.party[i].priest_spells[spec.ex1a + 30] = true; + univ.party[i].priest_spells[spec.ex1a] = spec.ex1b; break; case eSpecType::AFFECT_GOLD: if (spec.ex1b == 0) @@ -2731,34 +2954,74 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, if (calc_day() >= spec.ex1a) *next_spec = spec.ex1b; break; - case eSpecType::IF_BARRELS: + case eSpecType::IF_OBJECTS: + if(spec.ex1a == 0) { for (j = 0; j < univ.town->max_dim(); j++) for (k = 0; k < univ.town->max_dim(); k++) if (univ.town.is_barrel(j,k)) *next_spec = spec.ex1b; - break; - case eSpecType::IF_CRATES: + } else if(spec.ex1a == 1) { for (j = 0; j < univ.town->max_dim(); j++) for (k = 0; k < univ.town->max_dim(); k++) if (univ.town.is_crate(j,k)) *next_spec = spec.ex1b; + } + // TODO: Are there other object types to account for? + // TODO: Allow restricting to a specific rect + break; + case eSpecType::IF_PARTY_SIZE: + if (spec.ex2a < 1) { + if (party_size(spec.ex2b) == spec.ex1a) + *next_spec = spec.ex1b; + } + else { + if (party_size(spec.ex2b) >= spec.ex1a) + *next_spec = spec.ex1b; + } break; case eSpecType::IF_EVENT_OCCURRED: if (day_reached(spec.ex1a,spec.ex1b) == true) *next_spec = spec.ex2b; break; - case eSpecType::IF_HAS_CAVE_LORE: - for (i = 0; i < 6; i++) - if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[4] > 0) - *next_spec = spec.ex1b; + case eSpecType::IF_SPECIES: + if(spec.ex1a < 0 || spec.ex1a > 2) break; // TODO: Should we allow monster races too? + i = 0; + j = min(spec.ex2a,party_size(0)); + if (j < 1) + j = 1; + for (i = 0; i < 6; i++) { + if ((univ.party[i].main_status == eMainStatus::ALIVE) && (univ.party[i].race == eRace(spec.ex1a))) + i++; + } + if (i >= j) + *next_spec = spec.ex1b; break; - case eSpecType::IF_HAS_WOODSMAN: - for (i = 0; i < 6; i++) - if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[5] > 0) - *next_spec = spec.ex1b; + case eSpecType::IF_TRAIT: + j = min(spec.ex2a,party_size(0)); + if (j < 1) + j = 1; + for (i = 0; i < 6; i++) { + if(univ.party[i].main_status == eMainStatus::ALIVE && univ.party[i].traits[spec.ex1a] > 0) + i++; + } + if (trait_present(spec.ex1a) >= j) + *next_spec = spec.ex1b; break; - case eSpecType::IF_ENOUGH_MAGE_LORE: - if (mage_lore_total() >= spec.ex1a) + case eSpecType::IF_STATISTIC: + if(spec.ex2b == -1) { + // Check specific PC's stat (uses the active PC from Select PC node) + short pc; + if(univ.party.is_split()) + pc = univ.party.pc_present(); + if(pc == 6 && univ.party.pc_present(current_pc_picked_in_spec_enc)) + pc = current_pc_picked_in_spec_enc; + if(pc != 6) { + if(univ.party[pc].skills[spec.ex2a] >= spec.ex1a) + *next_spec = spec.ex1b; + break; + } + } + if(check_party_stat(spec.ex2a, spec.ex2b) >= spec.ex1a) *next_spec = spec.ex1b; break; case eSpecType::IF_TEXT_RESPONSE: @@ -2925,7 +3188,7 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, return; switch (cur_node.type) { case eSpecType::MAKE_TOWN_HOSTILE: - make_town_hostile(); + set_town_attitude(spec.ex1a,spec.ex1b,spec.ex2a); break; case eSpecType::TOWN_CHANGE_TER: set_terrain(l,spec.ex2a); @@ -3242,7 +3505,19 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, case eSpecType::TOWN_TIMER_START: univ.party.start_timer(spec.ex1a, spec.ex1b, 1); break; - } + // OBoE: Change town lighting + case eSpecType::TOWN_CHANGE_LIGHTING: + // Change bulk town lighting + if ((spec.ex1a >= 0) && (spec.ex1a <= 3)) + univ.town->lighting_type = (eLighting) spec.ex1a; + // Change party light level + if (spec.ex2a > 0) { + if (spec.ex2b == 0) + increase_light(spec.ex2a); + else increase_light(-spec.ex2a); + } + break; +} if (check_mess == true) { handle_message(which_mode,cur_spec_type,cur_node.m1,cur_node.m2,a,b); } @@ -3552,3 +3827,25 @@ void get_strs(std::string& str1,std::string& str2,short cur_type,short which_str } } + +// This function sets/retrieves values to/from campaign flags +void set_campaign_flag(short sdf_a, short sdf_b, short cpf_a, short cpf_b, short str, bool get_send) { + // get_send = false: Send value in SDF to Campaign Flag + // get_send = true: Retrieve value from Campaign Flag and put in SDF + try { + if(str >= 0) { + std::string cp_id = scenario.scen_strs(str); + if(get_send) + univ.party.stuff_done[sdf_a][sdf_b] = univ.party.cpn_flag(cpf_a, cpf_b, cp_id); + else + univ.party.cpn_flag(cpf_a, cpf_b, cp_id) = univ.party.stuff_done[sdf_a][sdf_b]; + } else { + if(get_send) + univ.party.stuff_done[sdf_a][sdf_b] = univ.party.cpn_flag(cpf_a, cpf_b); + else + univ.party.cpn_flag(cpf_a, cpf_b) = univ.party.stuff_done[sdf_a][sdf_b]; + } + } catch(std::range_error x) { + giveError(x.what()); + } +} diff --git a/osx/boe.specials.h b/osx/boe.specials.h index 6eeff50a..08f6ba95 100644 --- a/osx/boe.specials.h +++ b/osx/boe.specials.h @@ -18,7 +18,9 @@ void fade_party(); void change_level(short town_num,short x,short y); void push_things(); void special_increase_age(); +void queue_special(eSpecCtx mode, short which_type, short spec, location spec_loc); void run_special(eSpecCtx which_mode,short which_type,short start_spec,location spec_loc,short *a,short *b,short *redraw); +void run_special(pending_special_type spec, short* a, short* b, short* redraw); cSpecial get_node(short cur_spec,short cur_spec_type); void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, short *next_spec,short *next_spec_type,short *a,short *b,short *redraw); @@ -37,3 +39,5 @@ void rect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, short *next_spec,short *next_spec_type,short *a,short *b,short *redraw); void outdoor_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type, short *next_spec,short *next_spec_type,short *a,short *b,short *redraw); + +void set_campaign_flag(short sdf_a, short sdf_b, short cpf_a, short cpf_b, short str, bool get_send); diff --git a/osx/boe.text.cpp b/osx/boe.text.cpp index 2bc43090..220d95c3 100644 --- a/osx/boe.text.cpp +++ b/osx/boe.text.cpp @@ -1182,6 +1182,51 @@ void monst_spell_note(m_num_t number,short which_mess) case 33: msg = " " + msg + " summons aid. "; break; + case 34: + msg = " " + msg + " is cured."; + break; + case 35: + msg = " " + msg + " is hasted."; + break; + case 36: + msg = " " + msg + " is blessed."; + break; + case 37: + msg = " " + msg + " cleans webs."; + break; + case 38: + msg = " " + msg + " feels better."; + break; + case 39: + msg = " " + msg + " mind cleared."; + break; + case 40: + msg = " " + msg + " feels alert."; + break; + case 41: + msg = " " + msg + " is healed."; + break; + case 42: + msg = " " + msg + " drained of health."; + break; + case 43: + msg = " " + msg + " magic recharged."; + break; + case 44: + msg = " " + msg + " drained of magic."; + break; + case 45: + msg = " " + msg + " returns to life!"; + break; + case 46: + msg = " " + msg + " dies."; + break; + case 47: + msg = " " + msg + " rallies its courage."; + break; + case 48: + msg = " " + msg + " cleans off acid."; + break; } if (which_mess > 0) @@ -1255,13 +1300,28 @@ short print_terrain(location space) } -void add_string_to_buf(std::string str) +void add_string_to_buf(std::string str, unsigned short indent) { if(overall_mode == MODE_STARTUP) return; if(str == "") return; + if(indent && str.find_last_not_of(' ') > 48) { + if(indent > 20) indent = 20; + size_t split = str.find_last_of(' ', 49); + add_string_to_buf(str.substr(0,split)); + str = str.substr(split); + while(str.find_last_not_of(' ') > 48 - indent) { + std::string wrap(indent, ' '); + split = str.find_last_of(' ', 49 - indent); + wrap += str.substr(0,split); + str = str.substr(split); + add_string_to_buf(wrap); + } + return; + } + text_sbar->setPosition(58); // TODO: This seems oddly specific if (buf_pointer == mark_where_printing_long) { printing_long = true; diff --git a/osx/boe.text.h b/osx/boe.text.h index e99344d2..3e9554d1 100644 --- a/osx/boe.text.h +++ b/osx/boe.text.h @@ -33,7 +33,7 @@ void monst_damaged_mes(short which_m,short how_much,short how_much_spec); void monst_killed_mes(short which_m); void print_nums(short a,short b,short c); short print_terrain(location space); -void add_string_to_buf(std::string str); +void add_string_to_buf(std::string str, unsigned short indent = 0); // Set second paramater to nonzero to auto-split the line if it's too long void init_buf(); void print_buf () ; void restart_printing(); diff --git a/osx/boe.town.cpp b/osx/boe.town.cpp index 2441df29..a277df99 100644 --- a/osx/boe.town.cpp +++ b/osx/boe.town.cpp @@ -1,5 +1,6 @@ #include +#include //#include "item.h" @@ -45,7 +46,7 @@ extern short store_current_pc,current_ground; //extern pascal bool cd_event_filter(); extern eGameMode store_pre_shop_mode,store_pre_talk_mode; //extern location monster_targs[60]; -extern pending_special_type special_queue[20]; +extern std::queue special_queue; extern bool map_visible,diff_depth_ok,belt_present; extern sf::RenderWindow mini_map; @@ -715,29 +716,12 @@ location end_town_mode(short switching_level,location destination) // returns n return to_return; } -// actually, entry_dir is non zero is town is dead - kludge! -void handle_town_specials(short /*town_number*/, short entry_dir,location /*start_loc*/) { - - //if (entry_dir > 0) - // run_special(5,2,univ.town.town.spec_on_entry_if_dead,start_loc,&s1,&s2,&s3); - //else run_special(5,2,univ.town.town.spec_on_entry,start_loc,&s1,&s2,&s3); - if (entry_dir > 0) - special_queue[0].spec = univ.town->spec_on_entry_if_dead; - else special_queue[0].spec = univ.town->spec_on_entry; - special_queue[0].where = univ.town.p_loc; - special_queue[0].type = 2; - special_queue[0].mode = eSpecCtx::ENTER_TOWN; - special_queue[0].trigger_time = univ.party.age; // TODO: Simply pushing into slot 0 seems like a bad idea +void handle_town_specials(short /*town_number*/, bool town_dead,location /*start_loc*/) { + queue_special(eSpecCtx::ENTER_TOWN, 2, town_dead ? univ.town->spec_on_entry_if_dead : univ.town->spec_on_entry, univ.town.p_loc); } void handle_leave_town_specials(short /*town_number*/, short which_spec,location /*start_loc*/) { - - //run_special(6,2,which_spec,start_loc,&s1,&s2,&s3); - special_queue[1].spec = which_spec; - special_queue[1].where = univ.party.p_loc; - special_queue[1].type = 2; - special_queue[1].mode = eSpecCtx::LEAVE_TOWN; - special_queue[1].trigger_time = univ.party.age; // TODO: Simply pushing into slot 1 seems like a bad idea + queue_special(eSpecCtx::LEAVE_TOWN, 2, which_spec, univ.party.p_loc); } bool abil_exists(short abil) // use when univ.out.outdoors diff --git a/osx/boe.town.h b/osx/boe.town.h index c7b6ef18..43c274b5 100644 --- a/osx/boe.town.h +++ b/osx/boe.town.h @@ -6,7 +6,7 @@ void start_town_mode(short which_town, short entry_dir); void terrain_under_rentar(); location end_town_mode(short switching_level,location destination); // 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, short entry_dir,location start_loc) ; +void handle_town_specials(short town_number, bool town_dead,location start_loc) ; bool abil_exists(short abil) ; void start_town_combat(short direction); diff --git a/osx/classes/party.cpp b/osx/classes/party.cpp index b1b7dd8c..ef6c94d7 100644 --- a/osx/classes/party.cpp +++ b/osx/classes/party.cpp @@ -12,6 +12,7 @@ #include #include +#include "dlogutil.h" #include "classes.h" #include "oldstructs.h" #include "fileio.h" @@ -208,6 +209,7 @@ bool cParty::start_timer(short time, short node, short type){ } void cParty::writeTo(std::ostream& file){ + file << "CREATEVERSION" << OBOE_CURRENT_VERSION << '\n'; file << "AGE " << age << '\n'; file << "GOLD " << gold << '\n'; file << "FOOD " << food << '\n'; @@ -215,7 +217,7 @@ void cParty::writeTo(std::ostream& file){ for(int j = 0; j < 50; j++) if(stuff_done[i][j] > 0) file << "SDF " << i << ' ' << j << ' ' << unsigned(stuff_done[i][j]) << '\n'; - for(ptrIter iter = pointers.begin(); iter != pointers.end(); iter++) + for(auto iter = pointers.begin(); iter != pointers.end(); iter++) file << "POINTER " << iter->first << ' ' << iter->second.first << ' ' << iter->second.second << '\n'; for(int i = 0; i < 200; i++) if(item_taken[i][0] > 0 || item_taken[i][1] > 0 || item_taken[i][2] > 0 || item_taken[i][3] > 0 || @@ -270,10 +272,43 @@ void cParty::writeTo(std::ostream& file){ for(unsigned int i = 0; i < 250; i++) if(graphicUsed[i]) file << "GRAPHIC " << i << '\n'; - for(campIter iter = campaign_flags.begin(); iter != campaign_flags.end(); iter++){ - for(unsigned int i = 0; i < iter->second.size(); i++) - if(iter->second[i] > 0) - file << "CAMPAIGN \"" << iter->first << "\" " << i << ' ' << iter->second[i] << '\n'; + for(auto iter = campaign_flags.begin(); iter != campaign_flags.end(); iter++){ + std::string campaign_id = iter->first; + if(campaign_id.find_first_of(' ') != std::string::npos || campaign_id[0] == '"' || campaign_id[0] == '\'') { + // The string contains spaces or starts with a quote, so quote it. + // We may have to escape quotes or backslashes. + int apos = 0, quot = 0, bslash = 0; + std::for_each(campaign_id.begin(), campaign_id.end(), [&apos,",&bslash](char c) { + if(c == '\'') apos++; + if(c == '"') quot++; + if(c == '\\') bslash++; + }); + char quote_c; + // Surround it in whichever quote character appears fewer times. + if(quot < apos) quote_c = '"'; + else quote_c = '\''; + // Let's create this string to initially have the required size. + std::string temp; + size_t quoted_len = campaign_id.length() + std::min(quot,apos) + bslash + 2; + temp.reserve(quoted_len); + temp += quote_c; + for(size_t i = 0; i < campaign_id.length(); i++) { + if(campaign_id[i] == quote_c) { + temp += '\\'; + temp += quote_c; + } else if(campaign_id[i] == '\\') + temp += "\\\\"; + else temp += campaign_id[i]; + } + temp += quote_c; + campaign_id.swap(temp); + } + // Okay, we have the campaign ID in a state such that reading it back in will restore the original ID. + // Now output any flags that are set for this campaign. + for(unsigned int i = 0; i < 25; i++) + for(unsigned int j = 0; j < 20; j++) + if(iter->second.idx[i][j] > 0) + file << "CAMPAIGN " << campaign_id << ' ' << i << ' ' << j << ' ' << unsigned(iter->second.idx[i][j]) << '\n'; } file << '\f'; for(int i = 0; i < 30; i++){ @@ -393,6 +428,12 @@ void cParty::readFrom(std::istream& file){ unsigned int n; sin >> i >> j >> n; stuff_done[i][j] = n; + } else if(cur == "CREATEVERSION") { + unsigned long long version; + sin >> version; + if(version > OBOE_CURRENT_VERSION) { + giveError("Warning: this game appears to have been created with a newer version of Blades of Exile than you are running. Exile will do its best to load the saved game anyway, but there may be loss of information."); + } } else if(cur == "POINTER") { int i,j,k; sin >> i >> j >> k; @@ -517,13 +558,12 @@ void cParty::readFrom(std::istream& file){ out_c[i].what_monst.readFrom(bin); out_c[i].exists = true; }else if(cur == "CAMPAIGN") { - unsigned int i; - int j; + unsigned int i, j; + int val; cur = read_maybe_quoted_string(bin); - bin >> i >> j; - // TODO: value_type of campaign_flags is a vector, but maybe a map would be better? - while(campaign_flags[cur].size() < i) campaign_flags[cur].push_back(0); - campaign_flags[cur][i] = j; + bin >> i >> j >> val; + if(i < 25 && j < 25) + campaign_flags[cur].idx[i][j] = val; } else if(cur == "TIMER") { int i; bin >> i; @@ -583,25 +623,40 @@ cPlayer& cParty::operator[](unsigned short n){ return adven[n]; } -void cParty::set_ptr(short p, unsigned short sdfx, unsigned short sdfy){ // This function is not used for setting the reserved pointers - if(p >= -199 && p <= -100){ // must be a mutable pointer +// Note that the pointer functions take the pointer with its negative sign stripped off! +void cParty::set_ptr(unsigned short p, unsigned short sdfx, unsigned short sdfy){ + // This function is not used for setting the reserved pointers + if(p >= 100 && p <= 199){ // must be a mutable pointer if(sdfx >= 300) throw std::range_error("SDF x-coordinate out of range (0..299)"); if(sdfy >= 50) throw std::range_error("SDF y-coordinate out of range (0..49)"); pointers[p] = std::make_pair(sdfx,sdfy); } - else throw std::range_error("Pointer out of range (-199 to -100)"); + else throw std::range_error("Attempted to assign a pointer out of range (100..199)"); } -void cParty::force_ptr(short p, unsigned short sdfx, unsigned short sdfy){ +void cParty::clear_ptr(unsigned short p) { + if(p >= 100 && p <= 199) { + pointers[p] = std::make_pair(-1,-1); + } else throw std::range_error("Attempted to assign a pointer out of range (100 to 199)"); +} + +void cParty::force_ptr(unsigned short p, unsigned short sdfx, unsigned short sdfy){ pointers[p] = std::make_pair(sdfx,sdfy); } -unsigned char cParty::get_ptr(short p){ - ptrIter iter = pointers.find(p); +unsigned char cParty::get_ptr(unsigned short p){ + auto iter = pointers.find(p); if(iter == pointers.end()) return 0; return stuff_done[iter->second.first][iter->second.second]; } +unsigned char& cParty::cpn_flag(unsigned int x, unsigned int y, std::string id) { + if(id.empty()) id = scenario.campaign_id; + if(id.empty()) id = scenario.scen_name; + if(x >= 25 || y >= 25) throw std::range_error("Attempted to access a campaign flag out of range (0..25)"); + return campaign_flags[id].idx[x][y]; +} + bool cParty::is_split(){ bool ret = false; for(int i = 0; i < 6; i++) diff --git a/osx/classes/party.h b/osx/classes/party.h index e15931f3..98bf77af 100644 --- a/osx/classes/party.h +++ b/osx/classes/party.h @@ -30,6 +30,10 @@ namespace legacy { struct setup_save_type; }; +struct campaign_flag_type{ + unsigned char idx[25][25]; +}; + class cParty { public: class cConvers { // conversation; formerly talk_save_type @@ -68,6 +72,7 @@ public: short light_level; location outdoor_corner; location i_w_c; + // TODO: Does this duplicate cCurTown::p_loc? If not, why not? location p_loc; location loc_in_sec; cVehicle boats[30]; @@ -106,12 +111,17 @@ public: std::vector summons; // an array of monsters which can be summoned by the party's items yet don't originate from this scenario bool graphicUsed[250]; // whether each custom graphics slot on the party's sheet is actually used; needed to place new custom graphics on the sheet. unsigned short scen_won, scen_played; // numbers of scenarios won and played respectively by this party - std::map > campaign_flags; - std::map > pointers; +private: + std::map campaign_flags; + std::map> pointers; +public: - void set_ptr(short p, unsigned short sdfx, unsigned short sdfy); - void force_ptr(short p, unsigned short sdfx, unsigned short sdfy); - unsigned char get_ptr(short p); + void set_ptr(unsigned short p, unsigned short sdfx, unsigned short sdfy); + void force_ptr(unsigned short p, unsigned short sdfx, unsigned short sdfy); + void clear_ptr(unsigned short p); + unsigned char get_ptr(unsigned short p); + + unsigned char& cpn_flag(unsigned int x, unsigned int y, std::string id = ""); cParty& operator = (legacy::party_record_type& old); void append(legacy::big_tr_type& old); @@ -142,8 +152,8 @@ public: typedef std::vector::iterator journalIter; typedef std::vector::iterator talkIter; typedef std::vector::iterator timerIter; - typedef std::map >::iterator campIter; - typedef std::map >::iterator ptrIter; + // TODO: Remove this in favour of cParty constructor + friend void init_party(short); }; bool operator==(const cParty::cConvers& one, const cParty::cConvers& two); diff --git a/osx/classes/scenario.h b/osx/classes/scenario.h index d74c29bc..91c89b8b 100644 --- a/osx/classes/scenario.h +++ b/osx/classes/scenario.h @@ -81,6 +81,7 @@ public: location last_out_edited; short last_town_edited; scenario_header_flags format; + std::string campaign_id; // A hopefully unique identifier to specify the campaign this scenario is a part of. // scen_item_data_type scen_item_list { cItemRec scen_items[400]; //char monst_names[256][20]; @@ -107,4 +108,7 @@ public: void writeTo(std::ostream& file); }; +// OBoE Current Version +const unsigned long long OBOE_CURRENT_VERSION = 0x010000; // MMmmff; M - major, m - minor, f - bugfix + #endif \ No newline at end of file diff --git a/osx/classes/simpletypes.h b/osx/classes/simpletypes.h index db2f317b..71a11f6d 100644 --- a/osx/classes/simpletypes.h +++ b/osx/classes/simpletypes.h @@ -373,6 +373,7 @@ enum eItemAbil { ITEM_FIREWALK = 92, ITEM_FLYING = 93, ITEM_MAJOR_HEALING = 94, + ITEM_CALL_SPECIAL = 95, // Spell Usable ITEM_SPELL_FLAME = 110, ITEM_SPELL_FIREBALL = 111, @@ -539,6 +540,8 @@ enum class eSpecCtx { TARGET = 16, USE_SPACE = 17, SEE_MONST = 18, + MONST_SPEC_ABIL = 19, + TOWN_HOSTILE = 20, }; enum class eSpecType { @@ -571,6 +574,8 @@ enum class eSpecType { REST = 25, WANDERING_WILL_FIGHT = 26, END_SCENARIO = 27, + SET_POINTER = 28, + SET_CAMP_FLAG = 29, ONCE_GIVE_ITEM = 50, ONCE_GIVE_SPEC_ITEM = 51, ONCE_NULL = 52, @@ -630,12 +635,12 @@ enum class eSpecType { IF_HAVE_ITEM_CLASS_AND_TAKE = 145, IF_EQUIP_ITEM_CLASS_AND_TAKE = 146, IF_DAY_REACHED = 147, - IF_BARRELS = 148, - IF_CRATES = 149, + IF_OBJECTS = 148, + IF_PARTY_SIZE = 149, IF_EVENT_OCCURRED = 150, - IF_HAS_CAVE_LORE = 151, - IF_HAS_WOODSMAN = 152, - IF_ENOUGH_MAGE_LORE = 153, + IF_SPECIES = 151, + IF_TRAIT = 152, + IF_STATISTIC = 153, IF_TEXT_RESPONSE = 154, IF_SDF_EQ = 155, IF_CONTEXT = 156, @@ -665,6 +670,7 @@ enum class eSpecType { TOWN_SPLIT_PARTY = 193, TOWN_REUNITE_PARTY = 194, TOWN_TIMER_START = 195, + TOWN_CHANGE_LIGHTING = 196, RECT_PLACE_FIRE = 200, RECT_PLACE_FORCE = 201, RECT_PLACE_ICE = 202, @@ -698,7 +704,7 @@ enum class eSpecCat { inline eSpecCat getNodeCategory(eSpecType node) { int code = (int) node; - if(code >= 0 && code <= 27) + if(code >= 0 && code <= 29) return eSpecCat::GENERAL; if(code >= 50 && code <= 63) return eSpecCat::ONCE; @@ -706,7 +712,7 @@ inline eSpecCat getNodeCategory(eSpecType node) { return eSpecCat::AFFECT; if(code >= 130 && code <= 156) return eSpecCat::IF_THEN; - if(code >= 170 && code <= 195) + if(code >= 170 && code <= 196) return eSpecCat::TOWN; if(code >= 200 && code <= 218) return eSpecCat::RECT; diff --git a/osx/classes/special.cpp b/osx/classes/special.cpp index 3f017fdb..53db2ef0 100644 --- a/osx/classes/special.cpp +++ b/osx/classes/special.cpp @@ -66,6 +66,27 @@ cSpecial& cSpecial::operator = (legacy::special_node_type& old){ ex1a = (int) eSpecCtx::TARGET; ex1b = 108; // Spell ID for ritual of sanctification, as seen in cast_town_spell() break; + case 99: case 100: // Add mage/priest spell TODO: Merge these by adding 100 if it's a priest spell + ex1a += 30; + ex1b = 1; // Meaning give spell, not take + break; + case 148: case 149: // if barrels or crates + type = eSpecType::IF_OBJECTS; + ex1a = old.type - 148; + break; + case 151: case 152: // if has cave lore or woodsman + type = eSpecType::IF_TRAIT; + ex1a = old.type - 147; + break; + case 153: // if enough mage lore + type = eSpecType::IF_STATISTIC; + ex2a = 11; + ex2b = 0; + break; + case 229: // Outdoor store - fix spell IDs + if(ex1b == 1 || ex1b == 2) + ex1a += 30; + break; } return *this; } @@ -208,12 +229,11 @@ const std::map allNodeProps = { {eSpecType::IF_HAVE_ITEM_CLASS_AND_TAKE, {ex1b_ch = true,jmp_lbl = 3}}, {eSpecType::IF_EQUIP_ITEM_CLASS_AND_TAKE, {ex1b_ch = true,jmp_lbl = 3}}, {eSpecType::IF_DAY_REACHED, {ex1b_ch = true,jmp_lbl = 3}}, - {eSpecType::IF_BARRELS, {ex1b_ch = true,jmp_lbl = 3}}, - {eSpecType::IF_CRATES, {ex1b_ch = true,jmp_lbl = 3}}, + {eSpecType::IF_OBJECTS, {ex1b_ch = true,jmp_lbl = 3}}, {eSpecType::IF_EVENT_OCCURRED, {ex1b_ch = true,jmp_lbl = 3}}, - {eSpecType::IF_HAS_CAVE_LORE, {ex1b_ch = true,jmp_lbl = 3}}, - {eSpecType::IF_HAS_WOODSMAN, {ex1b_ch = true,jmp_lbl = 3}}, - {eSpecType::IF_ENOUGH_MAGE_LORE, {ex1b_ch = true,jmp_lbl = 3}}, + {eSpecType::IF_SPECIES, {ex1b_ch = true,jmp_lbl = 3}}, + {eSpecType::IF_TRAIT, {ex1b_ch = true,jmp_lbl = 3}}, + {eSpecType::IF_STATISTIC, {ex1b_ch = true,jmp_lbl = 3}}, {eSpecType::IF_TEXT_RESPONSE, {ex1b_ch = true,ex2b_ch = true,pic_lbl = 5,jmp_lbl = 3}}, {eSpecType::IF_SDF_EQ, {ex1b_ch = true,sdf_lbl = 1,jmp_lbl = 3}}, {eSpecType::IF_CONTEXT, {}}, diff --git a/osx/classes/special.h b/osx/classes/special.h index 1d98291d..0fc84b27 100644 --- a/osx/classes/special.h +++ b/osx/classes/special.h @@ -43,7 +43,6 @@ struct pending_special_type { eSpecCtx mode; unsigned char type; // 0 - scen, 1 - out, 2 - town location where; - long long trigger_time; }; struct node_properties_t { diff --git a/osx/classes/talking.cpp b/osx/classes/talking.cpp index adf68260..1e6249a6 100644 --- a/osx/classes/talking.cpp +++ b/osx/classes/talking.cpp @@ -26,6 +26,12 @@ cSpeech& cSpeech::operator = (legacy::talking_record_type& old){ talk_nodes[i].link2[j] = old.talk_nodes[i].link2[j]; talk_nodes[i].extras[j] = old.talk_nodes[i].extras[j]; } + // Now, convert data if necessary + switch(old.talk_nodes[i].type) { + case 9: case 10: // Spell shops TODO: Merge these by adding 100 if it's priest spells + talk_nodes[i].extras[1] += 30; + break; + } } return *this; } diff --git a/osx/classes/town.cpp b/osx/classes/town.cpp index cb07db58..c3a37b94 100644 --- a/osx/classes/town.cpp +++ b/osx/classes/town.cpp @@ -47,7 +47,7 @@ cTown& cTown::operator = (legacy::town_record_type& old){ sign_locs[i].x = old.sign_locs[i].x; sign_locs[i].y = old.sign_locs[i].y; } - lighting_type = old.lighting; + lighting_type = (eLighting) old.lighting; in_town_rect.top = old.in_town_rect.top; in_town_rect.left = old.in_town_rect.left; in_town_rect.bottom = old.in_town_rect.bottom; @@ -98,7 +98,7 @@ cTown::cTown(short){ special_locs[i] = d_loc; spec_id[i] = 0; } - lighting_type = 0; + lighting_type = LIGHT_NORMAL; for (i = 0; i < 4; i++) { start_locs[i] = d_loc; exit_specs[i] = -1; diff --git a/osx/classes/town.h b/osx/classes/town.h index f909290e..2bcc4583 100644 --- a/osx/classes/town.h +++ b/osx/classes/town.h @@ -28,6 +28,13 @@ namespace legacy { struct preset_field_type; }; +enum eLighting { + LIGHT_NORMAL = 0, + LIGHT_DARK = 1, + LIGHT_DRAINS = 2, + LIGHT_NONE = 3, +}; + class cTown { // formerly town_record_type public: // class cCreature { // formerly creature_start_type @@ -73,7 +80,7 @@ public: location special_locs[50]; unsigned short spec_id[50]; location sign_locs[15]; - short lighting_type; + eLighting lighting_type; location start_locs[4]; location exit_locs[4]; short exit_specs[4]; @@ -82,6 +89,7 @@ public: short max_num_monst; std::vector preset_fields; short spec_on_entry,spec_on_entry_if_dead; + short spec_on_hostile; short timer_spec_times[8]; short timer_specs[8]; unsigned char strlens[180]; diff --git a/osx/tools/fileio.cpp b/osx/tools/fileio.cpp index 895a66b5..32680e54 100644 --- a/osx/tools/fileio.cpp +++ b/osx/tools/fileio.cpp @@ -1360,7 +1360,7 @@ bool save_party(fs::path dest_file) static_cast(in_town ? 1342 : 5790), // is the party in town? static_cast(in_scen ? 100 : 200), // is the party in a scenario? static_cast(save_maps ? 5567 : 3422), // is the save maps feature enabled? - 0x0100, // current version number, major and minor revisions only + OBOE_CURRENT_VERSION >> 8, // current version number, major and minor revisions only // Version 1 indicates a beta format that may not be supported in the final release }; if(!mac_is_intel) // must flip all the flags to little-endian diff --git a/osx/tools/specials_parse.cpp b/osx/tools/specials_parse.cpp index 0f8b61da..1834b31a 100644 --- a/osx/tools/specials_parse.cpp +++ b/osx/tools/specials_parse.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "special.h" @@ -126,12 +127,12 @@ struct initer { ("if-item-class", eSpecType::IF_HAVE_ITEM_CLASS_AND_TAKE) ("if-item-class-equip", eSpecType::IF_EQUIP_ITEM_CLASS_AND_TAKE) ("if-day", eSpecType::IF_DAY_REACHED) - ("if-field", eSpecType::IF_BARRELS) - ("if-object", eSpecType::IF_CRATES) +// ("if-field", eSpecType::IF_BARRELS) + ("if-object", eSpecType::IF_OBJECTS) ("if-event", eSpecType::IF_EVENT_OCCURRED) - ("if-cave-lore", eSpecType::IF_HAS_CAVE_LORE) - ("if-woodsman", eSpecType::IF_HAS_WOODSMAN) - ("if-mage-lore", eSpecType::IF_ENOUGH_MAGE_LORE) + ("if-trait", eSpecType::IF_TRAIT) + ("if-species", eSpecType::IF_SPECIES) + ("if-statistic", eSpecType::IF_STATISTIC) ("if-response", eSpecType::IF_TEXT_RESPONSE) ("if-sdf-eq", eSpecType::IF_SDF_EQ) ("town-attitude", eSpecType::MAKE_TOWN_HOSTILE) @@ -174,6 +175,24 @@ struct initer { ("make-out-monst", eSpecType::OUT_PLACE_ENCOUNTER) ("start-shop", eSpecType::OUT_STORE) ; + // A check for missing types. + using underlying = std::underlying_type::type; + struct node_less : std::binary_function { + bool operator()(const eSpecType& x, const eSpecType& y) const {return underlying(x) < underlying(y);} + }; + std::set allNodes; + for(underlying i = 0; i < std::numeric_limits::max(); i++) { + eSpecType check = (eSpecType) i; + eSpecCat category = getNodeCategory(check); + if(category == eSpecCat::INVALID) continue; + allNodes.insert(check); + } + opcode.for_each([&allNodes](const std::string&, eSpecType node) { + allNodes.erase(node); + }); + std::for_each(allNodes.begin(), allNodes.end(), [](eSpecType node){ + printf("Warning: Missing opcode definition for special node type with ID %d\n", (int)node); + }); } };