Adonthell  0.4
achievements.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 Kai Sterker <kai.sterker@gmail.com>
3  Part of the Adonthell Project <http://adonthell.nongnu.org>
4 
5  Adonthell is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  Adonthell is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with Adonthell. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 /**
20  * @file achievements.cc
21  *
22  * @author Kai Sterker
23  * @brief Manages in-game achievements
24  */
25 
26 #include "achievements.h"
27 #include "game.h"
28 #include "gamedata.h"
29 
30 py_callback* achievements::_callback = NULL;
31 vector<achievement_data> achievements::_achievements;
32 
33 achievements::achievements()
34 {
35 
36 }
37 
38 achievements::~achievements()
39 {
40 
41 }
42 
43 bool achievements::create(const u_int8 & achievement, const u_int32 & bitmask)
44 {
45  if (achievement == 0 || achievement == 255)
46  return false;
47 
48  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
49  {
50  if (i->id() == achievement)
51  {
52  *i = achievement_data(achievement, bitmask);
53  return true;
54  }
55  }
56 
57  _achievements.push_back(achievement_data(achievement, bitmask));
58  return true;
59 }
60 
61 void achievements::update(const u_int8 & achievement, const u_int8 & bit)
62 {
63  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
64  {
65  if (i->id() == achievement)
66  {
67  bool unlocked = i->update(bit);
68  if (unlocked)
69  {
70  // immediately update global achievement cache
72 
73  // notify listener that achievement was unlocked
74  if (_callback)
75  {
76  _callback->callback_func1 (i->id());
77  }
78  }
79  break;
80  }
81  }
82 }
83 
85 {
86  int result = 0;
87  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
88  {
89  if (i->is_unlocked())
90  {
91  result++;
92  }
93  }
94  return result;
95 }
96 
97 bool achievements::is_unlocked (const u_int32 & index)
98 {
99  if (index < _achievements.size())
100  {
101  return _achievements[index].is_unlocked();
102  }
103  return false;
104 }
105 
107 {
108  if (index < _achievements.size())
109  {
110  return _achievements[index].id();
111  }
112  return 0;
113 }
114 
116 {
117  // if achievements are loaded already, only update current value
118  bool update_only = _achievements.size() > 0;
119 
120  u_int8 id;
121  u_int32 j, expected, current;
122  j << file;
123 
124  for (u_int32 i = 0; i < j; i++)
125  {
126  id << file;
127  expected << file;
128 
129  if (update_only)
130  {
131  current << file;
132  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
133  {
134  if (i->id() == id)
135  {
136  i->_current = current;
137  break;
138  }
139  }
140  }
141  else
142  {
143  _achievements.push_back(achievement_data(id, expected));
144  _achievements[i]._current << file;
145  }
146  }
147 
148  return true;
149 }
150 
152 {
153  u_int32 j;
154 
155  j = _achievements.size ();
156  j >> file;
157 
158  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
159  {
160  i->_id >> file;
161  i->_expected >> file;
162  i->_current >> file;
163  }
164 }
165 
167 {
168  igzstream in;
169  string filepath;
170 
171  // initialize achievements data from game data directory
172  if (_achievements.size() == 0)
173  {
174  if (!gamedata::load_achievements(0))
175  return;
176  }
177 
178  // Load global achievement state
179  filepath = game::user_data_dir();
180  filepath += "/achievement.data";
181 
182  // initially, global achievement data does not exist
183  if (!in.open (filepath))
184  {
185  // --> try to create it in that case
186  make_persistent();
187  return;
188  }
189 
190  u_int8 id;
191  u_int32 j, global;
192  Bytef buffer[5];
193  uLong checksum = adler32(0L, Z_NULL, 0);
194 
195  j << in;
196  for (u_int32 i = 0; i < j; i++)
197  {
198  id << in;
199  global << in;
200 
201  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
202  {
203  if (i->id() == id)
204  {
205  i->_persistent = global;
206  break;
207  }
208  }
209 
210  // update checksum
211  buffer[4] = id;
212  memcpy(buffer, &global, 4);
213  checksum = adler32(checksum, buffer, 5);
214  }
215 
216  u_int32 previous_checksum;
217  previous_checksum << in;
218 
219  if (previous_checksum != checksum)
220  {
221  cout << "Checksum error: " << previous_checksum << " != " << checksum << endl;
222 
223  _achievements.clear();
224  _achievements.push_back(achievement_data(255, 0x50554e4b));
225  _achievements[0]._persistent = _achievements[0]._expected;
226  }
227 
228  in.close ();
229 }
230 
232 {
233  ogzstream file;
234  string filepath;
235 
236  filepath = game::user_data_dir();
237  filepath += "/achievement.data";
238 
239  // initially, global achievement data does not exist
240  if (!file.open (filepath))
241  {
242  cerr << "Failed writing " << filepath << endl;
243  return;
244  }
245 
246 
247  num_achievements() >> file;
248 
249  Bytef buffer[5];
250  uLong checksum = adler32(0L, Z_NULL, 0);
251 
252  for (vector<achievement_data>::iterator i = _achievements.begin(); i != _achievements.end(); i++)
253  {
254  i->_id >> file;
255  i->_persistent >> file;
256 
257  // update checksum
258  buffer[4] = i->_id;
259  memcpy(buffer, &i->_persistent, 4);
260  checksum = adler32(checksum, buffer, 5);
261  }
262 
263  ((u_int32) checksum) >> file;
264  file.close();
265 }
266 
267 void achievements::py_signal_connect (PyObject *pyfunc, PyObject *args)
268 {
269  if (_callback)
270  {
271  delete _callback;
272  }
273  _callback = new py_callback (pyfunc, args);
274 }
275 
276 
277 achievement_data::achievement_data(const u_int8 & id, const u_int32 & expected) :
278  _id(id), _expected(expected)
279 {
280  _current = 0;
281  _persistent = 0;
282 }
283 
284 achievement_data::~achievement_data() { }
285 
287 {
288  // only ever unlock achievement once
289  if (!is_unlocked() && bit < 32)
290  {
291  _current |= (1 << bit);
292 
293  // unlock achievement if expected value is reached
294  if (_current == _expected)
295  {
296  _persistent = _current;
297  return true;
298  }
299  }
300 
301  return false;
302 }
303 
void callback_func1(int arg)
Calls the python function with an integer.
Definition: py_callback.cc:82
Class to write data from a Gzip compressed file.
Definition: fileops.h:227
void close()
Close the file that was opened.
Definition: fileops.cc:63
Class to read data from a Gzip compressed file.
Definition: fileops.h:135
static void make_persistent()
Write permanent unlock status of available achievements.
static bool is_unlocked(const u_int32 &index)
Checks whether the achievement at the given index is unlocked.
Definition: achievements.cc:97
static void put_state(ogzstream &file)
Save achievement data to stream.
static bool get_state(igzstream &file)
Load achievement data from stream.
static bool create(const u_int8 &achievement, const u_int32 &bitmask)
Create a new achievement with the given id and the bitmask that will eventually unlock it...
Definition: achievements.cc:43
Declares the game class.
#define u_int32
32 bits long unsigned integer
Definition: types.h:41
#define u_int8
8 bits long unsigned integer
Definition: types.h:35
static int num_achievements()
Return the total number of available achievements.
Definition: achievements.h:127
achievement_data(const u_int8 &id, const u_int32 &expected)
Create a new achievement with the given id and the bitmask that will eventually unlock it...
static void init()
Initialize achievements by loading all available achievements and their permanent unlocked status...
bool is_unlocked() const
Check whether the achievement is permanently unlocked.
Definition: achievements.h:74
static void py_signal_connect(PyObject *pyfunc, PyObject *args=NULL)
Allow to connect a python callback to get notified when a new achievement was unlocked.
Manages in-game achievements.
bool open(const string &fname)
Opens a file for write access.
Definition: fileops.cc:266
bool open(const string &fname)
Opens a file for read access.
Definition: fileops.cc:81
static u_int8 achievement_id(const u_int32 &index)
Get the achievement id at the given index.
Declares the gamedata and data classes.
static void update(const u_int8 &achievement, const u_int8 &bit)
Set the nth bit of the given achievement to 1.
Definition: achievements.cc:61
bool update(const u_int8 &bit)
Set the nth bit of the given achievement to 1.
Data for a single achievement.
Definition: achievements.h:43
static int num_unlocked()
Returns how many achievements have been permanently unlocked.
Definition: achievements.cc:84
Stores the C++ <-> Python callback binding.
Definition: py_callback.h:41
static string user_data_dir()
Returns the absolute path to the user data directory (usually ~/.adonthell).
Definition: game.h:80