Add a lot of stuff scraped from *i's version of the code, plus a couple of additional bits.

Adapted from *i:
- Show a confirm dialog when interrupting a special node sequence
- New monster special ability: call global special node (as an action, not on death)
- New item special ability: call global special node
- Check there's a monster death special before calling it (wasn't necessary before, might be now with the special queue changes)
- Queue specials that are triggered while another special is in progress, instead of ignoring them; they will be run after the current special in progress finishes.
- *i's version of petrification touch is currently active only for monster-on-monster combat; need to merge with my version for monster-on-pc combat.
- Pass party location to special in use special item context
- Fix set town visibility node (was checking wrong field and thus could not hide towns)
Special nodes:
- Town Hostile: change to Set Town Attitude
- Select PC node: option to select random PC
- Affect special nodes can now affect monsters
- Fix affect death node reviving non-existent PCs
- Affect Spells: Can remove spells, and can affect level 1-3 spells
- If Objects: Merged from If Barrels and If Crates
- If Species: Replaces If Cave Lore
- If Trait: Replaces If Woodsman
- If Statistic: Replaces If Enough Mage Lore
- Change Lighting: Can affect town's global lighting setting, player's light level, or both at once.
- Pointers! Actually, I'd already implemented the callbacks for setting and getting them, but they're now actually used, and the implementation has been tweaked a little.
- Campaign flags! Again, I'd already implemented them sorta, but I tweaked things and they ended up sort of halfway between the two implementations. Plus there's now a special node to set them.

Additional bits:
- Special queue now uses an std::queue instead of a basic array.
- Enum for town lighting levels
- Disease touch ability is now honoured for monster-on-monster combat
- See monster special context now passes the monster's location as the trigger location; also, removed the double-trigger from one circumstance.
- Along with the set town attitude change, there's now the possibility for making the town hostile to trigger a special node, which can cause the party to be slain.
- Select PC special node: option to select specific PC
- Spell IDs for use in shops and Affect Spell nodes have changed so that 0 is now the first level 1 spell, and so forth.
- add_string_to_buf can now auto-split the string over multiple lines, and the special node that uses it takes advantage of this
- Special node parser warns if a node type is missing a corresponding opcode
- Reserved "pointers" to access the special node's trigger location (this was *i's idea, but he never implemented it)
This commit is contained in:
2014-12-08 00:58:39 -05:00
parent 0d7ad2c718
commit 5249c6eef7
29 changed files with 796 additions and 193 deletions

View File

@@ -1,5 +1,6 @@
#include <cmath>
#include <queue>
//#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<pending_special_type> 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;
}

View File

@@ -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! ");

View File

@@ -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

View File

@@ -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;

View File

@@ -2,6 +2,7 @@
#include <SFML/Graphics.hpp>
#include <cstdio>
#include <queue>
//#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<pending_special_type> 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;

View File

@@ -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);

View File

@@ -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]);
}

View File

@@ -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<std::string, 6>& strs,short pic_num,ePicType pic_type,std::array<short, 3>& buttons) ;
//short fancy_choice_dialog(short which_dlog,short parent);

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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<short>::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;
}

View File

@@ -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);

View File

@@ -1,6 +1,7 @@
#include <cstdio>
#include <cstring>
#include <queue>
//#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<pending_special_type> 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());
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -1,5 +1,6 @@
#include <cstdio>
#include <queue>
//#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<pending_special_type> 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

View File

@@ -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);

View File

@@ -12,6 +12,7 @@
#include <sstream>
#include <stdexcept>
#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,&quot,&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++)

View File

@@ -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<cMonster> 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<std::string,std::vector<signed short> > campaign_flags;
std::map<short,std::pair<unsigned short,unsigned char> > pointers;
private:
std::map<std::string,campaign_flag_type> campaign_flags;
std::map<unsigned short,std::pair<unsigned short,unsigned char>> 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<cJournal>::iterator journalIter;
typedef std::vector<cConvers>::iterator talkIter;
typedef std::vector<cTimer>::iterator timerIter;
typedef std::map<std::string,std::vector<signed short> >::iterator campIter;
typedef std::map<short,std::pair<unsigned short,unsigned char> >::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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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<eSpecType, node_properties_t> 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, {}},

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<cField> 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];

View File

@@ -1360,7 +1360,7 @@ bool save_party(fs::path dest_file)
static_cast<short>(in_town ? 1342 : 5790), // is the party in town?
static_cast<short>(in_scen ? 100 : 200), // is the party in a scenario?
static_cast<short>(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

View File

@@ -13,6 +13,7 @@
#include <fstream>
#include <sstream>
#include <iterator>
#include <set>
#include <boost/phoenix/bind.hpp>
#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<eSpecType>::type;
struct node_less : std::binary_function<eSpecType, eSpecType, bool> {
bool operator()(const eSpecType& x, const eSpecType& y) const {return underlying(x) < underlying(y);}
};
std::set<eSpecType, node_less> allNodes;
for(underlying i = 0; i < std::numeric_limits<underlying>::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);
});
}
};