Save and Load function problem

So, I have had this problem for a while now: it is a logic error with two functions, save() and load(). I have checked save(), as far as I can tell it functions properly, so I assume the problem is in load(). The problem is this: when I start a new game (and this is all part of a game I am trying to create, it is my first one) it saves my level, my stats, attributes, everything. I go into combat and I cast a spell, Ignite, which does 3 damage. When I load a saved file and go into battle, it gives me my list of spells as "1) Empty", while it should be "1) Ignite 2) Mend". A little note: the character that this is tested on is a mage and has "Empty" for gloves, shoulders and hat (the string "Empty"), so I assume that one of my equipped armors is leaking over into spells. When I cast this empty, it deals 102 damage, which means that my characters base spell damage, intelligence and willpower (all of which impact spell effectiveness) have also been messed up. The only problem with the save file itself, that I have noticed, is that it gives me an unusually high armor rating (4+ billion), but that might be in my armor recalculation function, which I will work on later. The save and load functions follow

save()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void Character::save()
{
    system("cls");

    spellSize = Character::spells.size();
    skillSize = Character::skills.size();
    weaponsSize = Character::invWeapons.size();
    armorSize = Character::invArmor.size();

    ofstream saveFile;
    saveFile.open("Save.txt");

    saveFile << name << endl;
    saveFile << health << endl;
    saveFile << stamina << endl;
    saveFile << energy << endl;
    saveFile << skillShield << endl;
    saveFile << agil << endl;
    saveFile << endur << endl;
    saveFile << intel << endl;
    saveFile << will << endl;
    saveFile << str << endl;
    saveFile << bonusAgil << endl;
    saveFile << bonusEnd << endl;
    saveFile << bonusEnergy << endl;
    saveFile << bonusHealth << endl;
    saveFile << bonusIntel << endl;
    saveFile << bonusStamina << endl;
    saveFile << bonusStr << endl;
    saveFile << bonusWill << endl;
    saveFile << armor << endl;
    saveFile << damage << endl;
    saveFile << blockChance << endl;
    saveFile << hitChance << endl;
    saveFile << crush << endl;
    saveFile << pierce << endl;
    saveFile << rangedDamage << endl;
    saveFile << rangedCrush << endl;
    saveFile << rangedPierce << endl;
    saveFile << gold << endl;
    saveFile << level << endl;
    saveFile << magicDamage << endl;
    saveFile << magicHeal << endl;
    saveFile << spellSize << endl;
    saveFile << skillSize << endl;
    saveFile << weaponsSize << endl;
    saveFile << armorSize << endl;

    for(int index = 0; index < 6; index++)
    {
        saveFile << armoEquip[index] << "\n";
    }
    for(int index = 0; index < 3; index++)
    {
        saveFile << weapEquip[index] << "\n";
    }
    for(int index = 0; index < skills.size(); index++)
    {
        saveFile << skills[index] << "\n";
    }
    for(int index = 0; index < spells.size(); index++)
    {
        saveFile << spells[index] << "\n";
    }
    for(int index = 0; index < invArmor.size(); index++)
    {
        saveFile << invArmor[index] << "\n";
    }
    for(int index = 0; index < invWeapons.size(); index++)
    {
        saveFile << invWeapons[index] << "\n";
    }

    saveFile.close();
}


load()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
void Character::load()
{
    char input[100];

    system("cls");

    ifstream saveFile;
    saveFile.open("Save.txt");

    saveFile.seekg( ios::beg );


    //Get our name, which is a string
    saveFile.getline( input, 100 );
    name = input;
    //Using atoi for all other variables (which are numbers)
    saveFile.getline( input, 100 );
    health = atoi( input );

    saveFile.getline( input, 100 );
    stamina = atoi( input );

    saveFile.getline( input, 100 );
    energy = atoi( input );

    saveFile.getline( input, 100 );
    skillShield = atoi( input );

    saveFile.getline( input, 100 );
    agil = atoi( input );

    saveFile.getline( input, 100 );
    endur = atoi( input );

    saveFile.getline( input, 100 );
    intel = atoi( input );

    saveFile.getline( input, 100 );
    will = atoi( input );

    saveFile.getline( input, 100 );
    str = atoi( input );

    saveFile.getline( input, 100 );
    bonusAgil = atoi( input );

    saveFile.getline( input, 100 );
    bonusEnd = atoi( input );

    saveFile.getline( input, 100 );
    bonusEnergy = atoi( input );

    saveFile.getline( input, 100 );
    bonusHealth = atoi( input );

    saveFile.getline( input, 100 );
    bonusIntel = atoi( input );

    saveFile.getline( input, 100 );
    bonusStamina = atoi( input );

    saveFile.getline( input, 100 );
    bonusStr = atoi( input );

    saveFile.getline( input, 100 );
    bonusWill = atoi( input );

    saveFile.getline( input, 100 );
    armor = atoi( input );

    saveFile.getline( input, 100 );
    damage = atoi( input );

    saveFile.getline( input, 100 );
    crush = atoi( input );

    saveFile.getline( input, 100 );
    pierce = atoi( input );

    saveFile.getline( input, 100 );
    rangedDamage = atoi( input );

    saveFile.getline( input, 100 );
    rangedCrush = atoi( input );

    saveFile.getline( input, 100 );
    rangedPierce = atoi( input );

    saveFile.getline( input, 100 );
    gold = atoi( input );

    saveFile.getline( input, 100 );
    level = atoi( input );

    saveFile.getline( input, 100 );
    magicDamage = atoi( input );

    saveFile.getline( input, 100 );
    magicHeal = atoi( input );

    saveFile.getline( input, 100 );
    spellSize = atoi( input );

    saveFile.getline( input, 100 );
    skillSize = atoi( input );

    saveFile.getline( input, 100 );
    weaponsSize = atoi( input );

    saveFile.getline( input, 100 );
    armorSize = atoi( input );

    for(int index = 0; index < 6; )
    {
        saveFile.getline( input, 100);
        armoEquip[index] = input;
        index += 1;
    }
    for(int index = 0; index < 3; )
    {
        saveFile.getline( input, 100 );
        weapEquip[index] = input;
        index += 1;
    }
    //Clearing the vectors to make push_back() usage safe (preventing duplicates)
    skills.clear();
    spells.clear();
    invArmor.clear();
    invWeapons.clear();
    for(int index = 0; index < 4; )
    {
        saveFile.getline( input, 100 );
        skills.push_back( input );
        index += 1;
    }
    for(int index = 0; index < 2; )
    {
        saveFile.getline( input, 100 );
        spells.push_back( input );
        index += 1;
    }
    for(int index = 0; index < armorSize; )
    {
        saveFile.getline( input, 100 );
        invArmor.push_back( input );
        index += 1;
    }
    for(int index = 0; index < weaponsSize; )
    {
        saveFile.getline( input, 100 );
        invWeapons.push_back( input );
        index += 1;
    }

    saveFile.close();
}
I'd like to suggest an easier way of saving and loading character info, and I think it may solve your problem indirectly.

We can easily read and write whole structures/classes to binary files with one function, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <fstream>

class Person {
public :
	Person() {health = 100; age = 20; foo = 'b';}
	int health;
	short age;
	char foo;

	void savegame();
	void loadgame();
};

void write(const char* filename, Person& data) {
	std::ofstream stream(filename, std::ios::binary);
	stream.write(reinterpret_cast<char*>(&data), sizeof(Person));
}

void read(const char* filename, Person& data) {
	std::ifstream stream(filename, std::ios::binary);
	stream.read(reinterpret_cast<char*>(&data), sizeof(Person));
}

void Person::savegame() {
	write("save.txt", *this);
}

void Person::loadgame() {
	read("save.txt", *this);
}

int main(int argc, char* argv[]) {
	Person steve;

	steve.savegame();
	steve.loadgame();

	std::cout << steve.health << std::endl;
	std::cout << steve.age << std::endl;
	std::cout << steve.foo << std::endl;

	std::cin.get();
	return 0;
}


Note, that this only works with POD (plain-old-data) structures/classes. In other words, it only works if the size of the class is known and can be determined by the compiler. If the class/struct has pointers or dynamic containers of any sort (vectors, strings) then this will not work.
Damn, I have both vectors and strings. Does this method discard the vectors and strings? I can save and load those separately.
That method doesn't "discard" the vectors and strings in the sense that it doesn't skip over them. Rather, for classes that have pointers as members (I'm pretty sure both vector and string do), it'll output the value of those pointers (which are just memory addresses) rather than the data that it's actually pointing to.

For instance, consider this (rather simple) example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <fstream>

struct someData
{
    int num1;
    short num2;
    const char* someString;
};

int main()
{
    someData mySaveData = {10, 20, "Hello"};
    std::ofstream save("outputdatafile.dat", std::ios::binary);
    save.write(reinterpret_cast<char*>(&mySaveData), sizeof(someData));
}

Wanna know what the result looks like? (For me, at least -- not sure how it'll turn out for you)
<4 bytes representing the number 10 in binary (little-endian format for me)>
<2 bytes representing the number 20>
<2 bytes of...padding? Not sure what this value represents>
<4 bytes representing the memory address of the string>

(There's no newline between them, by the way; I just wrote them on separate lines so that it doesn't run off the screen)

Now, if you wanted to load this file back into memory, you might be able to handle loading back the 10 and the 20 (but be careful since on some computers, you might end up with 167772160 and 5120 instead), but when it comes time to load the string back into memory, all you have is a memory address which probably doesn't even point to anything remotely useful since you reloaded the program (so all of the data that the original pointer was pointing to is either gone or possibly in a different location now).

In general, I wouldn't try to do it this way.
You'll probably just have to deal with what you have now.

As for your original problem, have you tried running your code through a debugger?
You might have mixed up some of your variables or missed one or something.

In particular,
130
131
132
133
134
135
136
137
138
139
140
141
for(int index = 0; index < 4; )
{
    saveFile.getline( input, 100 );
    skills.push_back( input );
    index += 1;
}
for(int index = 0; index < 2; )
{
    saveFile.getline( input, 100 );
    spells.push_back( input );
    index += 1;
}

Where did the 4 and the 2 come from?
These are limits on attacks and spells (respectively) which I put into place out of frustration with the problem, hoping that it would give me some sort of solution.

As for your solution, do you think I could use it, but then write all of my vectors and strings using a different function and to a different file? That way, I could load all of the numbers first, and then, whatever that gives me for vectors and strings, I can overwrite from the vector and string file.
I think I might have found the problem:

In your code for saving:
31
32
33
34
35
saveFile << armor << endl;
saveFile << damage << endl;
saveFile << blockChance << endl;
saveFile << hitChance << endl;
saveFile << crush << endl;

In your code for loading:
68
69
70
71
72
73
74
75
saveFile.getline( input, 100 );
armor = atoi( input );

saveFile.getline( input, 100 );
damage = atoi( input );

saveFile.getline( input, 100 );
crush = atoi( input );

What happened to blockChance and hitChance?

I would personally not use the reinterpret_cast solution, since that just seems a bit too hack-ish for me.
I think the best way is to create some kind of standard format for your savefiles, so maybe something like this:
Name Bob
Health 97
Stamina 20
Energy 45
... etc. ...
ArmorEquip 3
<equip #1>
<equip #2>
<equip #3>
... etc. ...

Then to load it, you simply read the first word of each line (a cin >> statName; would do), and then depending on what that word is ("Health", "Energy", etc.) you can then read the rest of the line and load it into the proper location.
That way, even if you accidentally omit something or get something mixed up, it'll only affect the stats you missed/messed up and not everything else below that.
xismn wrote:
I'd like to suggest an easier way of saving and loading character info, and I think it may solve your problem indirectly.

We can easily read and write whole structures/classes to binary files with one function, like so:


I strongly recommend AGAINST doing this. Ever.

Writing raw binary dumps is problematic for several reasons:

1) It only works with basic built-in types (throw a string or vector in there and your program explodes)
2) It doesn't work if you have pointers (explode)
3) It doesn't work if the class has any virtual methods (again: explode)
4) It is subject to system endianness (not portable)
5) It is subject to how the compiler decides to pad your class/struct (not necessarily reliable or portable -- may differ between compiler versions)

I've written big posts about this in the past, which other people have copied and submitted as articles:
http://www.cplusplus.com/articles/DzywvCM9/
http://www.cplusplus.com/articles/oyhv0pDG/



Alternatively, if you have a lot of stats that are treated as a clump like this, but you still want them to have unique names.... I've done something like this in the past:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
enum IntStats
{
    health = 0,
    stamina,
    energy,
    skillShield,
    agil,

    // ... add additional integer stats here ...

    IntStats_Count
};

int intstats[ IntStats_Count ];

/*
    now, instead of this:
*/
    foo = health + stamina;
/*
    ... you'd do this:
*/
    foo = intstats[health] + intstats[stamina];
/*
    but you can also do this:
*/

    // save all int stats
    for(int i = 0; i < IntStats_Count; ++i)
        write( intstats[i] );  // where 'write' is whatever function you use to
                               // save the int to a file

    // loading is just as easy
    for(int i = 0; i < IntStats_Count; ++i)
        intstats[i] = read();




-------------------------------------------
EDIT
-------------------------------------------

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Example of a consolidated 'stat' struct.
//   using strong typed 'enum class'es

enum class st_int // integer stats
{
    health = 0,
    stamina,
    agility,
    
    //... etc
    
    _count_
};

enum class st_str // string stats
{
    name,
    classname,
    
    _count_
};

enum class st_dbl // double stats
{
    critrate,
    
    _count_
};
    
struct Stats
{
    int&            operator [] (st_int i)      { return stat_int[ static_cast<int>(i) ]; }
    std::string&    operator [] (st_str i)      { return stat_str[ static_cast<int>(i) ]; }
    double&         operator [] (st_dbl i)      { return stat_dbl[ static_cast<int>(i) ]; }
    
    // add save/load routines here

private:
    int             stat_int[ static_cast<int>(st_int::_count_) ];
    std::string     stat_str[ static_cast<int>(st_str::_count_) ];
    dbl             stat_int[ static_cast<int>(st_dbl::_count_) ];
};


// usage:

int main()
{
    Stats stats;
    
    stats[st_int::health]   = 50;
    stats[st_str::name]     = "Jeff Bridges";
    stats[st_dbl::critrate] = 0.08;
    //...
}
Last edited on
Thank you both, it finally works now!!!
Topic archived. No new replies allowed.