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

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