1 /*
2 * Copyright (C) 2011-2012 Me and My Shadow
3 *
4 * This file is part of Me and My Shadow.
5 *
6 * Me and My Shadow is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Me and My Shadow is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "LevelPack.h"
21 #include "Functions.h"
22 #include "FileManager.h"
23 #include "TreeStorageNode.h"
24 #include "POASerializer.h"
25 #include "MD5.h"
26 #include <string.h>
27 #include <string>
28 #include <vector>
29 #include <fstream>
30 #include <algorithm>
31 #include <iostream>
32 using namespace std;
33
34 //This is a special TreeStorageNode which only load node name/value and attributes, early exists when meeting any subnodes.
35 //This is used for fast loading of levels during game startup.
36 class LoadAttributesOnlyTreeStorageNode : public TreeStorageNode {
37 public:
newNode()38 virtual ITreeStorageBuilder* newNode() override {
39 //Early exit.
40 return NULL;
41 }
42 };
43
LevelPack()44 LevelPack::LevelPack():currentLevel(0),loaded(false),levels(),customTheme(false){
45 //We need to set the pointer to the dictionaryManager to NULL.
46 dictionaryManager=NULL;
47 //The type of levelpack is determined in the loadLevels method, but 'fallback' is CUSTOM.
48 type=CUSTOM;
49 }
50
~LevelPack()51 LevelPack::~LevelPack(){
52 //We call clear, since that already takes care of the deletion, including the dictionaryManager.
53 clear();
54 }
55
clear()56 void LevelPack::clear(){
57 currentLevel=0;
58 loaded=false;
59 levels.clear();
60 levelpackDescription.clear();
61 levelpackPath.clear();
62 levelProgressFile.clear();
63 congratulationText.clear();
64 levelpackMusicList.clear();
65
66 //Also delete the dictionaryManager if it isn't null.
67 if(dictionaryManager){
68 delete dictionaryManager;
69 dictionaryManager=NULL;
70 }
71 }
72
loadLevels(const std::string & levelListFile)73 bool LevelPack::loadLevels(const std::string& levelListFile){
74 //We're going to load a new levellist so first clean any existing levels.
75 clear();
76
77 //If the levelListFile is empty we have nothing to load so we return false.
78 if(levelListFile.empty()){
79 cerr<<"ERROR: No levellist file given."<<endl;
80 return false;
81 }
82
83 //Determine the levelpack type.
84 if(levelListFile.find(getDataPath())==0){
85 type=MAIN;
86 }else if(levelListFile.find(getUserPath(USER_DATA)+"levelpacks/")==0){
87 type=ADDON;
88 }else{
89 type=CUSTOM;
90 }
91
92 levelpackPath=pathFromFileName(levelListFile);
93
94 //Create input streams for the levellist file.
95 ifstream level(levelListFile.c_str());
96
97 if(!level){
98 cerr<<"ERROR: Can't load level list "<<levelListFile<<endl;
99 return false;
100 }
101
102 //Load the level list file.
103 TreeStorageNode obj;
104 {
105 POASerializer objSerializer;
106 if(!objSerializer.readNode(level,&obj,true)){
107 cerr<<"ERROR: Invalid file format of level list "<<levelListFile<<endl;
108 return false;
109 }
110 }
111
112 //Check for folders inside the levelpack folder.
113 {
114 //Get all the sub directories.
115 vector<string> v;
116 v=enumAllDirs(pathFromFileName(levelListFile),false);
117
118 //Check if there's a locale folder containing translations.
119 if(std::find(v.begin(),v.end(),"locale")!=v.end()){
120 //Folder is present so configure the levelDictionaryManager.
121 dictionaryManager=new tinygettext::DictionaryManager();
122 dictionaryManager->set_use_fuzzy(false);
123 dictionaryManager->add_directory(pathFromFileName(levelListFile)+"locale/");
124 dictionaryManager->set_charset("UTF-8");
125 dictionaryManager->set_language(tinygettext::Language::from_name(language));
126 }else{
127 dictionaryManager=NULL;
128 }
129
130 //Check for a theme folder.
131 if(std::find(v.begin(),v.end(),"theme")!=v.end()){
132 customTheme=true;
133 }
134 }
135
136 //Look for the name.
137 {
138 vector<string> &v=obj.attributes["name"];
139 if(!v.empty()){
140 levelpackName=v[0];
141 }else{
142 //Name is not defined so take the folder name.
143 levelpackName=pathFromFileName(levelListFile);
144 //Remove the last character '/'
145 levelpackName=levelpackName.substr(0,levelpackName.size()-1);
146 levelpackName=fileNameFromPath(levelpackName);
147 }
148 }
149
150 //Look for the description.
151 {
152 vector<string> &v=obj.attributes["description"];
153 if(!v.empty())
154 levelpackDescription=v[0];
155 }
156
157 //Look for the congratulation text.
158 {
159 vector<string> &v=obj.attributes["congratulations"];
160 if(!v.empty())
161 congratulationText=v[0];
162 }
163
164 //Look for the music list.
165 {
166 vector<string> &v=obj.attributes["musiclist"];
167 if(!v.empty())
168 levelpackMusicList=v[0];
169 }
170
171 //Loop through the level list entries.
172 for(unsigned int i=0;i<obj.subNodes.size();i++){
173 TreeStorageNode* obj1=obj.subNodes[i];
174 if(obj1==NULL)
175 continue;
176 if(!obj1->value.empty() && obj1->name=="levelfile"){
177 Level level;
178 level.file=obj1->value[0];
179 level.targetTime=0;
180 level.targetRecordings=0;
181 memset(level.md5Digest, 0, sizeof(level.md5Digest));
182
183 //The path to the file to open.
184 //NOTE: In this function we are always loading levels from a level pack, so levelpackPath is always used.
185 string levelFile=levelpackPath+level.file;
186
187 //Open the level file to retrieve the name and target time/recordings.
188 LoadAttributesOnlyTreeStorageNode obj;
189 POASerializer objSerializer;
190 if(objSerializer.loadNodeFromFile(levelFile.c_str(),&obj,true)){
191 //Get the name of the level.
192 vector<string>& v=obj.attributes["name"];
193 if(!v.empty())
194 level.name=v[0];
195 //If the name is empty then we set it to the file name.
196 if(level.name.empty())
197 level.name=fileNameFromPath(level.file);
198
199 //Get the target time of the level.
200 v=obj.attributes["time"];
201 if(!v.empty())
202 level.targetTime=atoi(v[0].c_str());
203 else
204 level.targetTime=-1;
205 //Get the target recordings of the level.
206 v=obj.attributes["recordings"];
207 if(!v.empty())
208 level.targetRecordings=atoi(v[0].c_str());
209 else
210 level.targetRecordings=-1;
211 }
212
213 //The default for locked is true, unless it's the first one.
214 level.locked=!levels.empty();
215 level.won=false;
216 level.time=-1;
217 level.recordings=-1;
218
219 //Add the level to the levels.
220 levels.push_back(level);
221 }
222 }
223
224 loaded=true;
225 return true;
226 }
227
loadProgress()228 void LevelPack::loadProgress(){
229 //Make sure that a levelProgressFile is set.
230 if(levelProgressFile.empty()){
231 levelProgressFile=getLevelProgressPath();
232 }
233
234 //Open the file.
235 ifstream levelProgress;
236 levelProgress.open(processFileName(this->levelProgressFile).c_str());
237
238 //Check if the file exists.
239 if(levelProgress){
240 //Now load the progress/statistics.
241 TreeStorageNode obj;
242 {
243 POASerializer objSerializer;
244 if(!objSerializer.readNode(levelProgress,&obj,true)){
245 cerr<<"ERROR: Invalid file format of level progress file."<<endl;
246 }
247 }
248
249 //Loop through the entries.
250 for(unsigned int i=0;i<obj.subNodes.size();i++){
251 TreeStorageNode* obj1=obj.subNodes[i];
252 if(obj1==NULL)
253 continue;
254 if(!obj1->value.empty() && obj1->name=="level"){
255 //We've found an entry for a level, now search the correct level.
256 Level* level=NULL;
257 for(unsigned int o=0;o<levels.size();o++){
258 if(obj1->value[0]==levels[o].file){
259 level=&levels[o];
260 break;
261 }
262 }
263
264 //Check if we found the level.
265 if(!level)
266 continue;
267
268 //Get the progress/statistics.
269 for(map<string,vector<string> >::iterator i=obj1->attributes.begin();i!=obj1->attributes.end();++i){
270 if(i->first=="locked"){
271 level->locked=(i->second[0]=="1");
272 }
273 if(i->first=="won"){
274 level->won=(i->second[0]=="1");
275 }
276 if(i->first=="time"){
277 level->time=(atoi(i->second[0].c_str()));
278 }
279 if(i->first=="recordings"){
280 level->recordings=(atoi(i->second[0].c_str()));
281 }
282 }
283 }
284 }
285 }
286 }
287
saveLevels(const std::string & levelListFile)288 void LevelPack::saveLevels(const std::string& levelListFile){
289 //Get the fileName.
290 string levelListNew=levelListFile;
291 //Open an output stream.
292 ofstream level(levelListNew.c_str());
293
294 //Check if we can use the file.
295 if(!level){
296 cerr<<"ERROR: Can't save level list "<<levelListNew<<endl;
297 return;
298 }
299
300 //Storage node that will contain the data that should be written.
301 TreeStorageNode obj;
302
303 //Also store the name of the levelpack.
304 if(!levelpackName.empty())
305 obj.attributes["name"].push_back(levelpackName);
306
307 //Make sure that there's a description.
308 if(!levelpackDescription.empty())
309 obj.attributes["description"].push_back(levelpackDescription);
310
311 //Make sure that there's a congratulation text.
312 if(!congratulationText.empty())
313 obj.attributes["congratulations"].push_back(congratulationText);
314
315 //Make sure that there's a music list.
316 if (!levelpackMusicList.empty())
317 obj.attributes["musiclist"].push_back(levelpackMusicList);
318
319 //Add the levels to the file.
320 for(unsigned int i=0;i<levels.size();i++){
321 TreeStorageNode* obj1=new TreeStorageNode;
322 obj1->name="levelfile";
323 obj1->value.push_back(fileNameFromPath(levels[i].file));
324 obj1->value.push_back(levels[i].name);
325 obj.subNodes.push_back(obj1);
326 }
327
328 //Write the it away.
329 POASerializer objSerializer;
330 objSerializer.writeNode(&obj,level,false,true);
331 }
332
updateLanguage()333 void LevelPack::updateLanguage(){
334 if(dictionaryManager!=NULL)
335 dictionaryManager->set_language(tinygettext::Language::from_name(language));
336 }
337
addLevel(const string & levelFileName,int levelno)338 void LevelPack::addLevel(const string& levelFileName,int levelno){
339 //Fill in the details.
340 Level level;
341 if(type!=COLLECTION && !levelpackPath.empty() && levelFileName.compare(0,levelpackPath.length(),levelpackPath)==0){
342 level.file=fileNameFromPath(levelFileName);
343 }else{
344 level.file=levelFileName;
345 }
346 level.targetTime=0;
347 level.targetRecordings=0;
348 memset(level.md5Digest, 0, sizeof(level.md5Digest));
349
350 //Get the name of the level.
351 LoadAttributesOnlyTreeStorageNode obj;
352 POASerializer objSerializer;
353 if(objSerializer.loadNodeFromFile(levelFileName.c_str(),&obj,true)){
354 //Get the name of the level.
355 vector<string>& v=obj.attributes["name"];
356 if(!v.empty())
357 level.name=v[0];
358 //If the name is empty then we set it to the file name.
359 if(level.name.empty())
360 level.name=fileNameFromPath(levelFileName);
361
362 //Get the target time of the level.
363 v=obj.attributes["time"];
364 if(!v.empty())
365 level.targetTime=atoi(v[0].c_str());
366 else
367 level.targetTime=-1;
368 //Get the target recordings of the level.
369 v=obj.attributes["recordings"];
370 if(!v.empty())
371 level.targetRecordings=atoi(v[0].c_str());
372 else
373 level.targetRecordings=-1;
374 }
375 //Set if it should be locked or not.
376 level.won=false;
377 level.time=-1;
378 level.recordings=-1;
379 level.locked=levels.empty()?false:true;
380
381 //Check if the level should be at the end or somewhere in the middle.
382 if(levelno<0 || levelno>=int(levels.size())){
383 levels.push_back(level);
384 }else{
385 levels.insert(levels.begin()+levelno,level);
386 }
387
388 //NOTE: We set loaded to true.
389 loaded=true;
390 }
391
moveLevel(unsigned int level1,unsigned int level2)392 void LevelPack::moveLevel(unsigned int level1,unsigned int level2){
393 if(level1>=levels.size())
394 return;
395 if(level2>=levels.size())
396 return;
397 if(level1==level2)
398 return;
399
400 levels.insert(levels.begin()+level2,levels[level1]);
401 if(level2<=level1)
402 levels.erase(levels.begin()+level1+1);
403 else
404 levels.erase(levels.begin()+level1);
405 }
406
saveLevelProgress()407 void LevelPack::saveLevelProgress(){
408 //Check if the levels are loaded and a progress file is given.
409 if(!loaded || levelProgressFile.empty())
410 return;
411
412 //Open the progress file.
413 ofstream levelProgress(processFileName(levelProgressFile).c_str());
414 if(!levelProgress)
415 return;
416
417 //Open an output stream.
418 TreeStorageNode node;
419
420 //Loop through the levels.
421 for(unsigned int o=0;o<levels.size();o++){
422 TreeStorageNode* obj=new TreeStorageNode;
423 node.subNodes.push_back(obj);
424
425 char s[64];
426
427 //Set the name of the node.
428 obj->name="level";
429 obj->value.push_back(levels[o].file);
430
431 //Set the values.
432 obj->attributes["locked"].push_back(levels[o].locked?"1":"0");
433 obj->attributes["won"].push_back(levels[o].won?"1":"0");
434 sprintf(s,"%d",levels[o].time);
435 obj->attributes["time"].push_back(s);
436 sprintf(s,"%d",levels[o].recordings);
437 obj->attributes["recordings"].push_back(s);
438 }
439
440
441 //Create a POASerializer and write away the leve node.
442 POASerializer objSerializer;
443 objSerializer.writeNode(&node,levelProgress,true,true);
444 }
445
getLevelName(int level)446 const string& LevelPack::getLevelName(int level){
447 if(level<0)
448 level=currentLevel;
449 return levels[level].name;
450 }
451
getLevelMD5(int level)452 const unsigned char* LevelPack::getLevelMD5(int level){
453 if(level<0)
454 level=currentLevel;
455
456 //Check if the md5Digest is not initialized.
457 bool notInitialized = true;
458 for (int i = 0; i < 16; i++) {
459 if (levels[level].md5Digest[i]) {
460 notInitialized = false;
461 break;
462 }
463 }
464
465 //Calculate md5Digest if needed.
466 if (notInitialized) {
467 string levelFile = getLevelFile(level);
468
469 TreeStorageNode obj;
470 POASerializer objSerializer;
471 if (objSerializer.loadNodeFromFile(levelFile.c_str(), &obj, true)) {
472 obj.name.clear();
473 obj.calcMD5(levels[level].md5Digest);
474 } else {
475 cerr << "ERROR: Failed to load file '" << levelFile << "' for calculating MD5" << endl;
476
477 //Fill in a fake MD5
478 for (int i = 0; i < 16; i++) {
479 levels[level].md5Digest[i] = 0xCC;
480 }
481 }
482 }
483
484 return levels[level].md5Digest;
485 }
486
getLevelAutoSaveRecordPath(int level,std::string & bestTimeFilePath,std::string & bestRecordingFilePath,bool createPath)487 void LevelPack::getLevelAutoSaveRecordPath(int level,std::string &bestTimeFilePath,std::string &bestRecordingFilePath,bool createPath){
488 if(level<0)
489 level=currentLevel;
490
491 bestTimeFilePath.clear();
492 bestRecordingFilePath.clear();
493
494 //get level pack path.
495 string levelpackPath = (type == COLLECTION ? std::string() : LevelPack::levelpackPath);
496 string s=levels[level].file;
497
498 //process level pack name
499 for(;;){
500 string::size_type lps=levelpackPath.find_last_of("/\\");
501 if(lps==string::npos){
502 break;
503 }else if(lps==levelpackPath.size()-1){
504 levelpackPath.resize(lps);
505 }else{
506 levelpackPath=levelpackPath.substr(lps+1);
507 break;
508 }
509 }
510
511 //profess file name
512 {
513 string::size_type lps=s.find_last_of("/\\");
514 if(lps!=string::npos) s=s.substr(lps+1);
515 }
516
517 //check if it's custom level
518 {
519 string path="%USER%/records/autosave/";
520 if(!levelpackPath.empty()){
521 path+=levelpackPath;
522 path+='/';
523 }
524 path=processFileName(path);
525 if(createPath) createDirectory(path.c_str());
526 s=path+s;
527 }
528
529 //calculate MD5
530 s+='-';
531 s += Md5::toString(getLevelMD5(level));
532
533 //over
534 bestTimeFilePath=s+"-best-time.mnmsrec";
535 bestRecordingFilePath=s+"-best-recordings.mnmsrec";
536 }
537
getLevelProgressPath()538 string LevelPack::getLevelProgressPath(){
539 if(levelProgressFile.empty()){
540 levelProgressFile="%USER%/progress/";
541
542 //Use the levelpack folder name instead of the levelpack name.
543 //NOTE: Remove the trailing slash.
544 string folderName=levelpackPath.substr(0,levelpackPath.size()-1);
545 folderName=fileNameFromPath(folderName);
546
547 //Depending on the levelpack type add a folder.
548 switch(type){
549 case MAIN:
550 levelProgressFile+="main/";
551 levelProgressFile+=folderName+".progress";
552 break;
553 case ADDON:
554 levelProgressFile+="addon/";
555 levelProgressFile+=folderName+".progress";
556 break;
557 case CUSTOM:
558 levelProgressFile+="custom/";
559 levelProgressFile+=folderName+".progress";
560 break;
561 case COLLECTION:
562 //NOTE: For collections we use their name since they don't have a folder.
563 //FIXME: Make sure the name contains legal characters.
564 levelProgressFile+=levelpackName+".progress";
565 break;
566 }
567 }
568
569 return levelProgressFile;
570 }
571
setLevelName(unsigned int level,const std::string & name)572 void LevelPack::setLevelName(unsigned int level,const std::string& name){
573 if(level<levels.size())
574 levels[level].name=name;
575 }
576
getLevelFile(int level)577 const string LevelPack::getLevelFile(int level){
578 if(level<0)
579 level=currentLevel;
580
581 string levelFile;
582 if(type!=COLLECTION)
583 levelFile=levelpackPath+levels[level].file;
584 else
585 levelFile=levels[level].file;
586
587 return levelFile;
588 }
589
getLevelpackPath()590 const string& LevelPack::getLevelpackPath(){
591 return levelpackPath;
592 }
593
getLevel(int level)594 struct LevelPack::Level* LevelPack::getLevel(int level){
595 if(level<0)
596 return &levels[currentLevel];
597 return &levels[level];
598 }
599
resetLevel(int level)600 void LevelPack::resetLevel(int level){
601 if(level<0)
602 level=currentLevel;
603
604 //Set back to default.
605 levels[level].locked=(level!=0);
606 levels[level].won=false;
607 levels[level].time=-1;
608 levels[level].recordings=-1;
609 }
610
nextLevel()611 void LevelPack::nextLevel(){
612 currentLevel++;
613 }
614
getLocked(unsigned int level)615 bool LevelPack::getLocked(unsigned int level){
616 return levels[level].locked;
617 }
618
setCurrentLevel(unsigned int level)619 void LevelPack::setCurrentLevel(unsigned int level){
620 currentLevel=level;
621 }
622
setLocked(unsigned int level,bool locked)623 void LevelPack::setLocked(unsigned int level,bool locked){
624 levels[level].locked=locked;
625 }
626
swapLevel(unsigned int level1,unsigned int level2)627 void LevelPack::swapLevel(unsigned int level1,unsigned int level2){
628 if(level1<levels.size()&&level2<levels.size()){
629 swap(levels[level1],levels[level2]);
630 }
631 }
632
removeLevel(unsigned int level)633 void LevelPack::removeLevel(unsigned int level){
634 if(level<levels.size()){
635 levels.erase(levels.begin()+level);
636 }
637 }
638