New special nodes to store/unstore PCs outside the party structure

- Stored PCs are lost when you leave the scenario
This commit is contained in:
2015-02-02 14:16:57 -05:00
parent 8d23bbc788
commit 559663dd35
14 changed files with 205 additions and 19 deletions

View File

@@ -730,7 +730,11 @@ Use 0-5 to select a specific PC. To select a monster, look up its unique number
locating it in town, editing it, and checking the "Creature number" field. Add 100 to that
number, and put it in this field. If the selected PC or monster does not match the
criteria set in Extra 1a, then the selected character is not changed and the special in
Extra 1b is called.</dd>
Extra 1b is called. You can also put a unique PC ID (which is 1000 or greater) in this
field. If you leave this at -1, the game will take the value in Extra 2c plus 1000 as the
unique PC ID to select. By using a unique PC ID, you can affect a stored PC without first
unstoring it. Setting Extra 1b to -1 should be done if you have the ID in a Stuff Done
Flag; then you'd place a pointer to that SDF in Extra 1c.</dd>
<dt>Uses:</dt><dd>If you want something to increase the strength of one character, call a
Select a PC node, followed by an Affect Statistic node.</dd></dd>
@@ -888,7 +892,14 @@ effect. Also, Detect Life and Stealth do nothing outdoors while Flight does noth
town.</dd></dd>
<dt>Type 105: Create New PC</dt><dd>This special node adds an entirely new PC to the party.
After this node is called, the new PC is considered to be the selected PC, and any
subsequent Affect nodes will apply only to the new PC.
<dl>
<dt>Stuff Done 1, Stuff Done 2:</dt><dd>If these are set to something other than -1, this
SDF is set to the unique ID of the new PC, minus 1000. If you want to do anything with
this PC after the current node chain ends, you need to set these. It's not sufficient to
just keep track of which slot the PC was placed in, because the player can rearrange their
party at will.</dd>
<dt>Mess3:</dt><dd>The number of a string containing the new PC's name.</dd>
<dt>Pict:</dt><dd>The PC graphic to give to the new PC.</dd>
<dt>Pictype:</dt><dd>A special node that will be called if the party already has 6 PCs.</dd>
@@ -900,6 +911,28 @@ town.</dd></dd>
<dt>Extra 2c:</dt><dd>The new PC's Intelligence skill.</dd>
<dt>Uses:</dt><dd>This could be used to have an NPC join your party.</dd></dd>
<dt>Type 106: Store PC</dt><dd>This special node removes a PC from the party and stores
them away somewhere.
<dl>
<dt>Stuff Done 1, Stuff Done 2:</dt><dd>If these are set to something other than -1, this
SDF is set to the unique ID of the stored PC, minus 1000. Only leave these unset if you
already know the unique ID (for example, because you obtained it when creating the PC with
the above node). If you don't know the unique ID of the stored PC, there is no way to
unstore it.</dd>
<dt>Extra 1a:</dt><dd>If you set this to 1, the PC is not stored, but the SDF (if present)
is updated.</dd>
<dt>Uses:</dt><dd>This could be used if you have an NPC that joins the party, and you want
the NPC to leave at some point and later return to the party.</dd></dd>
<dt>Type 107: Unstore PC</dt><dd>This special node takes a PC previously stored with the
above node and, if there's space, puts it back into the party.
<dl>
<dt>Extra 1a:</dt><dd>The unique ID of the PC to unstore. If less than 1000, the game will
add 1000 to it.</dd>
<dt>Extra 1b:</dt><dd>A special node that will be called if the party already has 6
PCs.</dd></dd>
</dl>
<h3>If-Then Specials</h3>
<p>These special node types don't do anything by themselves. Instead, they choose from
several choices which node to call next. For example, if you want something to happen when

View File

@@ -103,8 +103,8 @@ food
alchemy
party-status
create-pc
store-pc
unstore-pc

View File

@@ -399,8 +399,8 @@ Unused
Special to Jump To
--------------------
Create New PC
Unused
Unused
Stuff Done Flag Part A
Stuff Done Flag Part B
First part of message
Second part of message
PC name
@@ -414,3 +414,35 @@ PC dexterity
PC intelligence
Special to Jump To
--------------------
Store PC
Stuff Done Flag Part A
Stuff Done Flag Part B
First part of message
Second part of message
Unused
Unused
Unused
If 1, just store ID in SDF and leave PC in party
extra 1b
extra 1c
extra 2a
extra 2b
extra 2c
Special to Jump To
--------------------
Unstore PC
Unused
Unused
First part of message
Second part of message
Unused
Unused
Unused
Unique ID of PC to unstore
Special to call if no room
Unused
Unused
Unused
Unused
Special to Jump To
--------------------

View File

@@ -936,6 +936,7 @@ static void handle_victory() {
// This is the point at which we need to transfer any exported graphics over to the party sheet.
univ.exportGraphics();
univ.exportSummons();
univ.clear_stored_pcs();
reload_startup();
overall_mode = MODE_STARTUP;
in_scen_debug = false;

View File

@@ -2719,6 +2719,41 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
can_pick = false;
else if(spec.ex1a == 3 && univ.town.monst[monst].active > 0)
can_pick = false;
} else if(pc >= 1000 || pc == -1) { // Select PC by unique ID; might be a stored PC rather than a party member
if(pc == -1) pc = 1000 + spec.ex2c;
can_pick = false; // Assume ID is invalid unless proven otherwise
cPlayer* found = nullptr;
for(i = 0; i < 6; i++) {
if(univ.party[i].unique_id == pc) {
can_pick = true;
found = &univ.party[i];
break;
}
}
if(!can_pick) {
for(auto& p : univ.stored_pcs) {
if(p.first == pc && p.second->unique_id == pc) {
can_pick = true;
found = p.second;
break;
}
}
}
if(can_pick) {
// Honour the request for alive PCs only.
if(found->main_status == eMainStatus::ABSENT)
can_pick = false;
else if(spec.ex1a % 4 == 0 && found->main_status != eMainStatus::ALIVE)
can_pick = false;
else if(spec.ex1a == 3 && found->main_status == eMainStatus::ALIVE)
can_pick = false;
else if(spec.ex1a == 4 && found->has_space() == 24)
can_pick = false;
}
if(can_pick)
current_pc_picked_in_spec_enc = found;
else *next_spec = spec.ex1b;
break; // To avoid the call to get_target
} else can_pick = false; // Because it's an invalid index
if(can_pick)
current_pc_picked_in_spec_enc = &univ.get_target(pc);
@@ -3088,6 +3123,36 @@ void affect_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
univ.party[pc_num].skills[eSkill::DEXTERITY] = spec.ex2b;
univ.party[pc_num].skills[eSkill::INTELLIGENCE] = spec.ex2c;
current_pc_picked_in_spec_enc = &univ.get_target(pc_num);
if(univ.party.sd_legit(spec.sd1, spec.sd2))
univ.party.stuff_done[spec.sd1][spec.sd2] = univ.party[pc_num].unique_id - 1000;
break;
case eSpecType::STORE_PC:
if(cPlayer* who = dynamic_cast<cPlayer*>(pc)) {
if(univ.party.sd_legit(spec.sd1, spec.sd2))
univ.party.stuff_done[spec.sd1][spec.sd2] = univ.party[pc_num].unique_id - 1000;
if(spec.ex1a == 1) break;
who->main_status += eMainStatus::SPLIT;
univ.stored_pcs[who->unique_id] = who;
univ.party.new_pc(pc_num);
}
break;
case eSpecType::UNSTORE_PC:
if(spec.ex1a < 1000) spec.ex1a += 1000;
if(univ.stored_pcs.find(spec.ex1a) == univ.stored_pcs.end()) {
giveError("Scenario tried to unstore a nonexistent PC!");
break;
}
pc_num = univ.party.free_space();
if(pc_num == 6) {
add_string_to_buf("No room for PC.");
*next_spec = spec.ex1b;
check_mess = false;
break;
}
univ.party.replace_pc(pc_num, univ.stored_pcs[spec.ex1a]);
current_pc_picked_in_spec_enc = &univ.get_target(pc_num);
univ.party[pc_num].main_status -= eMainStatus::SPLIT;
univ.stored_pcs.erase(spec.ex1a);
break;
default:
giveError("Special node type \"" + (*cur_node.type).name() + "\" is either miscategorized or unimplemented!");

View File

@@ -199,9 +199,13 @@ void cParty::append(legacy::pc_record_type(& old)[6]) {
}
void cParty::new_pc(size_t spot) {
if(spot < 6){
replace_pc(spot, new cPlayer(*this));
}
void cParty::replace_pc(size_t spot, cPlayer* with) {
if(spot < 6 && with != nullptr) {
delete adven[spot];
adven[spot] = new cPlayer(*this);
adven[spot] = with;
}
}
@@ -483,6 +487,7 @@ void cParty::writeTo(std::ostream& file) const {
file << "AGE " << age << '\n';
file << "GOLD " << gold << '\n';
file << "FOOD " << food << '\n';
file << "NEXTID " << next_pc_id << '\n';
for(int i = 0; i < 310; i++)
for(int j = 0; j < 50; j++)
if(stuff_done[i][j] > 0)
@@ -676,6 +681,8 @@ void cParty::readFrom(std::istream& file){
sin >> gold;
else if(cur == "FOOD")
sin >> food;
else if(cur == "NEXTID")
sin >> next_pc_id;
else if(cur == "SDF"){
int i,j;
unsigned int n;

View File

@@ -75,6 +75,7 @@ public:
};
// formerly party_record_type
// TODO: Should we make age a long long?
long next_pc_id = 1000;
unsigned long age;
unsigned short gold;
unsigned short food;
@@ -173,6 +174,7 @@ public:
int get_level() const;
void new_pc(size_t spot);
void replace_pc(size_t spot, cPlayer* with);
size_t free_space();
void void_pcs();
bool save_talk(const std::string& who, const std::string& where, const std::string& str1, const std::string& str2);

View File

@@ -682,11 +682,15 @@ cPlayer::cPlayer(cParty& party) : party(party) {
race = eRace::HUMAN;
direction = DIR_N;
combat_pos = {-1,-1};
unique_id = party.next_pc_id++;
}
cPlayer::cPlayer(cParty& party,long key,short slot) : cPlayer(party) {
short i;
main_status = eMainStatus::ALIVE;
unique_id = slot + 1000;
party.next_pc_id = max(unique_id + 1, party.next_pc_id);
if(key == 'dbug'){
switch(slot) {
case 0:
@@ -855,6 +859,7 @@ void operator -= (eMainStatus& stat, eMainStatus othr){
}
void cPlayer::writeTo(std::ostream& file) const {
file << "UID " << unique_id << '\n';
file << "STATUS -1 " << main_status << '\n';
file << "NAME " << name << '\n';
file << "SKILL 19 " << max_health << '\n';
@@ -952,6 +957,9 @@ void cPlayer::readFrom(std::istream& file){
eStatus i;
sin >> i;
sin >> status[i];
} else if(cur == "UID") {
sin >> unique_id;
party.next_pc_id = max(unique_id + 1, party.next_pc_id);
}else if(cur == "EQUIP"){
int i;
sin >> i;

View File

@@ -49,6 +49,7 @@ public:
// HACK: This is only really marked mutable so that I can use operator[] from const methods
mutable std::map<eTrait,bool> traits;
eRace race;
long unique_id;
// transient stuff
std::map<eSkill,eSpell> last_cast;
location combat_pos;

View File

@@ -616,6 +616,8 @@ enum class eSpecType {
AFFECT_ALCHEMY = 103,
AFFECT_PARTY_STATUS = 104,
CREATE_NEW_PC = 105,
STORE_PC = 106,
UNSTORE_PC = 107,
IF_SDF = 130,
IF_TOWN_NUM = 131,
IF_RANDOM = 132,
@@ -706,7 +708,7 @@ inline eSpecCat getNodeCategory(eSpecType node) {
return eSpecCat::GENERAL;
if(code >= 50 && code <= 63)
return eSpecCat::ONCE;
if(code >= 80 && code <= 105)
if(code >= 80 && code <= 107)
return eSpecCat::AFFECT;
if(code >= 130 && code <= 160)
return eSpecCat::IF_THEN;

View File

@@ -421,17 +421,17 @@ static const char*const button_dict[7][11] = {
"s s s S", // ex2b
" ", // ex2c
}, { // affect pc nodes
"mmmmmmmmmmm mmmmmmmm", // msg1
" ", // msg2
" M M", // msg3
" 5", // pic
" s", // pictype
" w q AP a ", // ex1a
" ", // ex1b
" e Q", // ex1c
" K E ", // ex2a
" D ", // ex2b
" x ", // ex2c
"mmmmmmmmmmm mmmmmmmmmm", // msg1
" ", // msg2
" M M ", // msg3
" 5 ", // pic
" s ", // pictype
" w q AP a ", // ex1a
" s", // ex1b
" e Q ", // ex1c
" K E ", // ex2a
" D ", // ex2b
" x ", // ex2c
}, { // if-then nodes
" f $ $ ", // msg1
" s ", // msg2

View File

@@ -1176,6 +1176,16 @@ short cUniverse::difficulty_adjust() const {
return adj;
}
cUniverse::~cUniverse() {
clear_stored_pcs();
}
void cUniverse::clear_stored_pcs() {
for(auto& p : stored_pcs)
delete p.second;
stored_pcs.clear();
}
short cCurTown::countMonsters(){
short to_ret = 0;
for(short i = 0; i < monst.size(); i++)

View File

@@ -167,16 +167,19 @@ public:
cScenario scenario;
cParty party;
std::map<long,cPlayer*> stored_pcs;
cCurTown town;
char town_maps[200][8][64]; // formerly stored_town_maps_type
cCurOut out;
char out_maps[100][6][48]; // formerly stored_outdoor_maps_type
fs::path file;
void clear_stored_pcs();
void append(legacy::stored_town_maps_type& old);
void append(legacy::stored_outdoor_maps_type& old);
short difficulty_adjust() const;
explicit cUniverse(long party_type = 'dflt');
~cUniverse();
static void(* print_result)(std::string);
};

View File

@@ -329,6 +329,18 @@ bool load_party_v2(fs::path file_to_load, cUniverse& univ, bool town_restore, bo
univ.party[i].readFrom(fin);
}
// Including stored PCs
if(partyIn.hasFile("save/stored_pcs.txt")) {
std::istream& fin = partyIn.getFile("save/stored_pcs.txt");
long next_uid;
while(fin >> next_uid) {
std::string fname = "save/pc~" + std::to_string(next_uid) + ".txt";
cPlayer* stored_pc = new cPlayer(univ.party);
stored_pc->readFrom(partyIn.getFile(fname));
univ.stored_pcs[next_uid] = stored_pc;
}
}
if(in_scen) {
fs::path path;
path = progDir/"Blades of Exile Scenarios"/univ.party.scen_name;
@@ -422,6 +434,16 @@ bool save_party(fs::path dest_file, const cUniverse& univ) {
univ.party[i].writeTo(partyOut.newFile(fname));
}
// And stored PCs
if(univ.stored_pcs.size()) {
std::ostream& fout = partyOut.newFile("save/stored_pcs.txt");
for(auto p : univ.stored_pcs) {
std::string fname = "save/pc~" + std::to_string(p.first) + ".txt";
p.second->writeTo(partyOut.newFile(fname));
fout << p.first << '\n';
}
}
if(in_scen) {
if(in_town) {
// Write the current town data