Add tests for reading player data from a saved game

- Changed cPlayer::equip to a bitset
- Use a static constant instead of a loop to initialized player starting spells
- Only save spell points if the player has any (current if different from max)
- Symbolic forms for trait enum (and save symbolic forms also for skills)
- When loading a player, clear data which is not always present in the file
- Also add an init test for cPlayer
This commit is contained in:
2016-09-15 17:25:43 -04:00
parent 8aaa0a24c0
commit 863ac053c4
9 changed files with 242 additions and 33 deletions

View File

@@ -253,6 +253,8 @@
91BC33921B4388E80008882C /* libboost_thread.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 910D9CA31B36439100414B17 /* libboost_thread.dylib */; };
91BC33981B4481EF0008882C /* scen.fileio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91B3EEF20F969BA700BF5B67 /* scen.fileio.cpp */; };
91BFA3D71901B18F001686E4 /* mask.vert in Copy Shaders */ = {isa = PBXBuildFile; fileRef = 91BFA3D61901B024001686E4 /* mask.vert */; };
91C548F81D8B2FE400FE6A7B /* pc_read.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91C548F71D8B2EE400FE6A7B /* pc_read.cpp */; };
91C548F91D8B338700FE6A7B /* pc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 913D05BB0FA1EA0A00184C18 /* pc.cpp */; };
91C6864A0FD5EEFD000F6D01 /* pc.graphics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91B3EF0A0F969BD300BF5B67 /* pc.graphics.cpp */; };
91C749BA1A2D670D008E0E10 /* dialogs in Copy Data Files */ = {isa = PBXBuildFile; fileRef = 91C749B91A2D66F7008E0E10 /* dialogs */; };
91C763D91B4C50710086D879 /* enums.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 91C763D81B4C4BB30086D879 /* enums.cpp */; };
@@ -738,6 +740,7 @@
91C2A6EC1B8FA91400346948 /* town_read.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = town_read.cpp; sourceTree = "<group>"; };
91C2A6ED1B8FA9FB00346948 /* out_read.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = out_read.cpp; sourceTree = "<group>"; };
91C2A6EE1B8FAA8E00346948 /* talk_read.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = talk_read.cpp; sourceTree = "<group>"; };
91C548F71D8B2EE400FE6A7B /* pc_read.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pc_read.cpp; sourceTree = "<group>"; };
91C688E60FD702B9000F6D01 /* cursors.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = cursors.hpp; sourceTree = "<group>"; };
91C688E70FD702B9000F6D01 /* cursors.mac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = cursors.mac.mm; sourceTree = "<group>"; };
91C749B71A2D6432008E0E10 /* strings */ = {isa = PBXFileReference; lastKnownFileType = folder; path = strings; sourceTree = "<group>"; };
@@ -1360,6 +1363,7 @@
9176FEC01D550EFC006EF694 /* out_legacy.cpp */,
91C2A6ED1B8FA9FB00346948 /* out_read.cpp */,
91E381491B97678D00F69B81 /* out_write.cpp */,
91C548F71D8B2EE400FE6A7B /* pc_read.cpp */,
9176FEC11D550EFC006EF694 /* scen_legacy.cpp */,
91CC173A1B421CA0003D9A69 /* scen_read.cpp */,
91CC173B1B421CA0003D9A69 /* scen_write.cpp */,
@@ -1904,6 +1908,8 @@
9176FEC81D550EFE006EF694 /* scen_legacy.cpp in Sources */,
9176FECB1D550EFE006EF694 /* talk_legacy.cpp in Sources */,
9176FECC1D550EFE006EF694 /* town_legacy.cpp in Sources */,
91C548F81D8B2FE400FE6A7B /* pc_read.cpp in Sources */,
91C548F91D8B338700FE6A7B /* pc.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -166,6 +166,24 @@ std::istream& operator >> (std::istream& in, eSkill& e){
return in;
}
// MARK: eTrait
cEnumLookup trait_names = {
"tough", "magic-apt", "ambidex", "nimble", "cave-lore", "wood-lore", "const", "alert",
"strong", "regen", "slow", "magic-inept", "frail", "sickly", "bad-back", "pacifist", "anama"
};
std::ostream& operator << (std::ostream& out, eTrait e) {
writeEnum(out, e, trait_names, "tough");
return out;
}
std::istream& operator >> (std::istream& in, eTrait& e){
if(!readEnum(in, e, trait_names, eTrait::TOUGHNESS))
in.setstate(std::ios::failbit);
return in;
}
// MARK: eItemType
cEnumLookup item_types = {

View File

@@ -23,6 +23,8 @@ extern const std::multiset<eItemType> num_hands_to_use;
extern std::map<const eItemType, const short> excluding_types;
extern short skill_bonus[21];
// A nice convenient bitset with just the low 30 bits set, for initializing spells
const uint32_t cPlayer::basic_spells = std::numeric_limits<uint32_t>::max() >> 2;
void cPlayer::import_legacy(legacy::pc_record_type old){
main_status = (eMainStatus) old.main_status;
@@ -940,12 +942,10 @@ cPlayer::cPlayer(cParty& party) : party(&party), weap_poisoned(*this) {
skill_pts = 65;
level = 1;
std::fill(items.begin(), items.end(), cItem());
std::fill(equip.begin(), equip.end(), false);
equip.reset();
for(short i = 0; i < 62; i++) {
priest_spells[i] = i < 30;
mage_spells[i] = i < 30;
}
priest_spells = basic_spells;
mage_spells = basic_spells;
which_graphic = 0;
race = eRace::HUMAN;
@@ -995,7 +995,7 @@ cPlayer::cPlayer(cParty& party,long key,short slot) : cPlayer(party) {
skill_pts = 60;
level = 1;
std::fill(items.begin(), items.end(), cItem());
std::fill(equip.begin(), equip.end(), false);
equip.reset();
priest_spells.set();
mage_spells.set();
@@ -1084,13 +1084,9 @@ cPlayer::cPlayer(cParty& party,long key,short slot) : cPlayer(party) {
level = 1;
std::fill(items.begin(), items.end(), cItem());
std::fill(equip.begin(), equip.end(), false);
equip.reset();
cur_sp = pc_sp[slot];
max_sp = pc_sp[slot];
for(short i = 0; i < 62; i++) {
priest_spells[i] = i < 30;
mage_spells[i] = i < 30;
}
for(short i = 0; i < 15; i++) {
eTrait trait = eTrait(i);
traits[trait] = pc_t[slot].count(trait);
@@ -1180,14 +1176,16 @@ void cPlayer::writeTo(std::ostream& file) const {
file << "UID " << unique_id << '\n';
file << "STATUS -1 " << main_status << '\n';
file << "NAME " << maybe_quote_string(name) << '\n';
file << "SKILL 19 " << max_health << '\n';
file << "SKILL 20 " << max_sp << '\n';
file << "SKILL " << eSkill::MAX_HP << ' ' << max_health << '\n';
if(max_sp > 0)
file << "SKILL " << eSkill::MAX_SP << ' ' << max_sp << '\n';
for(auto p : skills) {
if(p.second > 0)
file << "SKILL " << int(p.first) << ' ' << p.second << '\n';
}
file << "HEALTH " << cur_health << '\n';
file << "MANA " << cur_sp << '\n';
if(cur_sp != max_sp)
file << "MANA " << cur_sp << '\n';
file << "EXPERIENCE " << experience << '\n';
file << "SKILLPTS " << skill_pts << '\n';
file << "LEVEL " << level << '\n';
@@ -1231,7 +1229,17 @@ void cPlayer::readFrom(std::istream& file){
std::string cur;
getline(file, cur, '\f');
bin.str(cur);
std::fill(equip.begin(), equip.end(), false);
// Clear some data that is not always present
equip.reset();
mage_spells.reset();
priest_spells.reset();
weap_poisoned.clear();
status.clear();
traits.clear();
skills.clear();
cur_sp = max_sp = ap = 0;
while(bin) { // continue as long as no error, such as eof, occurs
getline(bin, cur);
sin.str(cur);
@@ -1244,20 +1252,30 @@ void cPlayer::readFrom(std::istream& file){
}else if(cur == "NAME")
name = read_maybe_quoted_string(sin);
else if(cur == "SKILL"){
int i;
sin >> i;
switch(i){
case -1:
case 20:
sin >> max_sp;
break;
case -2:
case 19:
eSkill skill;
sin >> skill;
switch(skill) {
case eSkill::MAX_HP:
sin >> max_health;
break;
case eSkill::MAX_SP:
sin >> max_sp;
break;
case eSkill::CUR_HP:
sin >> cur_health;
break;
case eSkill::CUR_SP:
sin >> cur_sp;
break;
case eSkill::CUR_XP:
sin >> experience;
break;
case eSkill::CUR_SKILL:
break;
case eSkill::CUR_LEVEL:
sin >> level;
break;
default:
if(i < 0 || i >= 19) break;
eSkill skill = eSkill(i);
sin >> skills[skill];
}
}else if(cur == "HEALTH")
@@ -1291,10 +1309,8 @@ void cPlayer::readFrom(std::istream& file){
sin >> i;
priest_spells[i] = true;
}else if(cur == "TRAIT"){
int i;
sin >> i;
if(i < 0 || i > 15) continue;
eTrait trait = eTrait(i);
eTrait trait;
sin >> trait;
traits[trait] = true;
}else if(cur == "ICON")
sin >> which_graphic;

View File

@@ -64,7 +64,10 @@ class cPlayer : public iLiving {
cParty* party;
template<typename Fcn>
cInvenSlot find_item_matching(Fcn fcn);
static const int INVENTORY_SIZE = 24;
public:
// A nice convenient bitset with just the low 30 bits set, for initializing spells
static const uint32_t basic_spells;
static void(* give_help)(short,short);
eMainStatus main_status;
std::string name;
@@ -77,8 +80,8 @@ public:
unsigned short experience;
short skill_pts;
short level;
std::array<cItem,24> items;
std::array<bool,24> equip;
std::array<cItem,INVENTORY_SIZE> items;
std::bitset<INVENTORY_SIZE> equip;
std::bitset<62> priest_spells;
std::bitset<62> mage_spells;
pic_num_t which_graphic;
@@ -90,7 +93,7 @@ public:
// transient stuff
std::map<eSkill,eSpell> last_cast;
location combat_pos;
short parry;
short parry = 0;
iLiving* last_attacked = nullptr; // Note: Currently this is assigned but never read
bool is_alive() const;
@@ -175,5 +178,7 @@ void operator += (eMainStatus& stat, eMainStatus othr);
void operator -= (eMainStatus& stat, eMainStatus othr);
std::ostream& operator << (std::ostream& out, eMainStatus e);
std::istream& operator >> (std::istream& in, eMainStatus& e);
std::ostream& operator << (std::ostream& out, eTrait e);
std::istream& operator >> (std::istream& in, eTrait& e);
#endif