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:
@@ -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
|
||||
|
@@ -103,8 +103,8 @@ food
|
||||
alchemy
|
||||
party-status
|
||||
create-pc
|
||||
|
||||
|
||||
store-pc
|
||||
unstore-pc
|
||||
|
||||
|
||||
|
||||
|
@@ -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
|
||||
--------------------
|
||||
|
@@ -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;
|
||||
|
@@ -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!");
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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++)
|
||||
|
@@ -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);
|
||||
};
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user