The Battle for Wesnoth  1.19.5+dev
achievements.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program 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 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "achievements.hpp"
17 
18 #include "filesystem.hpp"
19 #include "log.hpp"
21 #include "serialization/parser.hpp"
23 
24 static lg::log_domain log_config("config");
25 #define ERR_CONFIG LOG_STREAM(err, log_config)
26 
27 sub_achievement::sub_achievement(const config& cfg, bool achieved)
28  : id_(cfg["id"].str())
29  , description_(cfg["description"].t_str())
30  , icon_(cfg["icon"].str()+"~GS()")
31  , icon_completed_(cfg["icon"].str())
32  , achieved_(achieved)
33 {}
34 
35 achievement::achievement(const config& cfg, const std::string& content_for, bool achieved, int progress)
36  : id_(cfg["id"].str())
37  , name_(cfg["name"].t_str())
38  , name_completed_(cfg["name_completed"].t_str())
39  , description_(cfg["description"].t_str())
40  , description_completed_(cfg["description_completed"].t_str())
41  , icon_(cfg["icon"].str()+"~GS()")
42  , icon_completed_(cfg["icon_completed"].str())
43  , hidden_(cfg["hidden"].to_bool())
44  , achieved_(achieved)
45  , max_progress_(cfg["max_progress"].to_int(0))
46  , current_progress_(progress)
47  , sound_path_(cfg["sound"].str())
48  , sub_achievements_()
49 {
50  if(name_completed_.empty()) {
52  }
55  }
56  if(icon_completed_.empty()) {
57  // avoid the ~GS() appended to icon_
58  icon_completed_ = cfg["icon"].str();
59  }
60 
61  for(const config& sub_ach : cfg.child_range("sub_achievement"))
62  {
63  std::string sub_id = sub_ach["id"].str();
64 
65  if(sub_id.empty()) {
66  ERR_CONFIG << "Achievement " << id_ << " has a sub-achievement missing the id attribute:\n" << sub_ach.debug();
67  } else {
68  sub_achievements_.emplace_back(sub_ach, achieved_ || prefs::get().sub_achievement(content_for, id_, sub_id));
69  max_progress_++;
70  }
71  }
72 }
73 
75  : display_name_(cfg["display_name"].t_str())
76  , content_for_(cfg["content_for"].str())
77  , achievements_()
78 {
79  for(const config& ach : cfg.child_range("achievement")) {
80  std::string id = ach["id"].str();
81 
82  if(id.empty()) {
83  ERR_CONFIG << content_for_ + " achievement missing id attribute:\n" << ach.debug();
84  } else if(id.find(',') != std::string::npos) {
85  ERR_CONFIG << content_for_ + " achievement id " << id << " contains a comma, skipping.";
86  continue;
87  } else {
88  achievements_.emplace_back(ach, content_for_, prefs::get().achievement(content_for_, id), prefs::get().progress_achievement(content_for_, id));
89  }
90  }
91 }
92 
94  : achievement_list_()
95 {
96  reload();
97 }
98 
99 /**
100  * Reads the mainline achievements.cfg and then all the achievements of each installed add-on.
101  *
102  * This is intentionally handled separately from other WML loading so that:
103  * a) All achievements and their status are able to be displayed on the main menu right after Wesnoth starts and regardless of which add-ons are active.
104  * b) Add-ons can add additional achievements to other content, whether UMC or mainline. For example, a modification that adds more achievements for mainline campaigns.
105  *
106  * NOTE: These are *not* in any way related to Steam achievements!
107  */
109 {
110  achievement_list_.clear();
111  // mainline
112  try {
113  config cfg = read_achievements_file(game_config::path + "/data/achievements.cfg");
114  process_achievements_file(cfg, "Mainline");
115  } catch(const game::error& e) {
116  ERR_CONFIG << "Error processing mainline achievements, ignoring: " << e.what();
117  }
118 
119  // add-ons
120  std::vector<std::string> dirs;
122  for(const std::string& dir : dirs) {
123  try {
124  config cfg = read_achievements_file(filesystem::get_addons_dir() + "/" + dir + "/achievements.cfg");
125  process_achievements_file(cfg, dir);
126  } catch(const game::error& e) {
127  ERR_CONFIG << "Error processing add-on " << dir << " achievements, ignoring: " << e.what();
128  }
129  }
130 }
131 
132 /**
133  * Reads an achievements.cfg file into a config.
134  *
135  * @param path The path to the achievements.cfg file.
136  * @return The config containing all the achievements.
137  */
139 {
140  config cfg;
143  read(cfg, *stream);
144  }
145  return cfg;
146 }
147 
148 /**
149  * Processes a config object to add new achievements to @a achievement_list_.
150  *
151  * @param cfg The config containing additional achievements.
152  * @param content_source The source of the additional achievements - either mainline or an add-on.
153  */
154 void achievements::process_achievements_file(const config& cfg, const std::string& content_source)
155 {
156  for(const config& achgrp : cfg.child_range("achievement_group")) {
157  if(achgrp["content_for"].str().empty()) {
158  ERR_CONFIG << content_source + " achievement_group missing content_for attribute:\n" << achgrp.debug();
159  continue;
160  }
161  achievement_list_.emplace_back(achgrp);
162  }
163 }
#define ERR_CONFIG
static lg::log_domain log_config("config")
void process_achievements_file(const config &cfg, const std::string &content_source)
Processes a config object to add new achievements to achievement_list_.
std::vector< achievement_group > achievement_list_
config read_achievements_file(const std::string &path)
Reads an achievements.cfg file into a config.
void reload()
Reads the mainline achievements.cfg and then all the achievements of each installed add-on.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
child_itors child_range(config_key_type key)
Definition: config.cpp:272
static prefs & get()
bool empty() const
Definition: tstring.hpp:194
Declarations for File-IO.
Standard logging facilities (interface).
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:444
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:325
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
std::string get_addons_dir()
std::string path
Definition: filesystem.cpp:90
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:622
std::string content_for_
The internal ID used for this content.
std::vector< achievement > achievements_
The achievements associated to this content.
achievement_group(const config &cfg)
Represents a single achievement and its data.
std::string icon_completed_
The icon of the achievement to show on the UI if the achievement is completed.
t_string name_completed_
The name of the achievement to show on the UI if the achievement is completed.
achievement(const config &cfg, const std::string &content_for, bool achieved, int progress)
t_string description_completed_
The name of the achievement to show on the UI if the achievement is completed.
t_string name_
The name of the achievement to show on the UI.
bool achieved_
Whether the achievement has been completed.
std::string id_
The ID of the achievement.
int max_progress_
When the achievement's current progress matches or equals this value, then it should be marked as com...
t_string description_
The description of the achievement to show on the UI.
std::vector< sub_achievement > sub_achievements_
The list of distinct sub-achievements for this achievement.
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
Represents a distinct sub-achievement within another achievement.
sub_achievement(const config &cfg, bool achieved)
#define e