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 
achievements::is_unlocked
static bool is_unlocked(const u_int32 &index)
Checks whether the achievement at the given index is unlocked.
Definition: achievements.cc:97
achievement_data
Data for a single achievement.
Definition: achievements.h:43
igzstream
Class to read data from a Gzip compressed file.
Definition: fileops.h:135
py_callback::callback_func1
void callback_func1(int arg)
Calls the python function with an integer.
Definition: py_callback.cc:82
u_int32
#define u_int32
32 bits long unsigned integer
Definition: types.h:41
achievements::make_persistent
static void make_persistent()
Write permanent unlock status of available achievements.
Definition: achievements.cc:231
achievement_data::is_unlocked
bool is_unlocked() const
Check whether the achievement is permanently unlocked.
Definition: achievements.h:74
achievements::get_state
static bool get_state(igzstream &file)
Load achievement data from stream.
Definition: achievements.cc:115
game::user_data_dir
static string user_data_dir()
Returns the absolute path to the user data directory (usually ~/.adonthell).
Definition: game.h:80
gamedata.h
Declares the gamedata and data classes.
achievements::py_signal_connect
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.
Definition: achievements.cc:267
ogzstream
Class to write data from a Gzip compressed file.
Definition: fileops.h:227
u_int8
#define u_int8
8 bits long unsigned integer
Definition: types.h:35
achievements::num_achievements
static int num_achievements()
Return the total number of available achievements.
Definition: achievements.h:127
gz_file::close
void close()
Close the file that was opened.
Definition: fileops.cc:63
ogzstream::open
bool open(const string &fname)
Opens a file for write access.
Definition: fileops.cc:266
achievements::put_state
static void put_state(ogzstream &file)
Save achievement data to stream.
Definition: achievements.cc:151
achievements::create
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
igzstream::open
bool open(const string &fname)
Opens a file for read access.
Definition: fileops.cc:81
achievement_data::achievement_data
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.
Definition: achievements.cc:277
achievements::init
static void init()
Initialize achievements by loading all available achievements and their permanent unlocked status.
Definition: achievements.cc:166
achievement_data::update
bool update(const u_int8 &bit)
Set the nth bit of the given achievement to 1.
Definition: achievements.cc:286
achievements::num_unlocked
static int num_unlocked()
Returns how many achievements have been permanently unlocked.
Definition: achievements.cc:84
achievements::achievement_id
static u_int8 achievement_id(const u_int32 &index)
Get the achievement id at the given index.
Definition: achievements.cc:106
game.h
Declares the game class.
achievements::update
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
py_callback
Stores the C++ <-> Python callback binding.
Definition: py_callback.h:41
achievements.h
Manages in-game achievements.