• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ParadoxGameConverters / Vic3ToHoI4 / 17586870883

09 Sep 2025 03:00PM UTC coverage: 94.165% (+0.06%) from 94.109%
17586870883

Pull #750

github

web-flow
Merge 77d9b51d1 into 654134559
Pull Request #750: Adjust repeat_focus to rely on any_... triggers

306 of 308 new or added lines in 28 files covered. (99.35%)

1 existing line in 1 file now uncovered.

23658 of 25124 relevant lines covered (94.16%)

53769.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.89
/src/hoi4_world/focus_trees/focus_tree_assembler.cpp
1
#include "src/hoi4_world/focus_trees/focus_tree_assembler.h"
2

3
#include <ranges>
4

5
#include "src/hoi4_world/roles/triggers/context.h"
6

7

8

9
namespace
10
{
11

12
// For all repeated focuses defined them into the role, expand them into actual focuses.
13
// Also add to a lookup map of the original repeat focus id to the resulting focus ids.
14
std::vector<hoi4::Focus> CreateRepeatedFocuses(const hoi4::Role& role,
13✔
15
    const hoi4::World& world,
16
    std::string_view tag,
17
    std::map<std::string, std::vector<std::string>>& role_lookup)
18
{
19
   std::vector<hoi4::Focus> repeated_focuses;
13✔
20

21
   const auto country_itr = world.GetCountries().find(std::string(tag));
13✔
22
   if (country_itr == world.GetCountries().end())
13✔
23
   {
24
      return {};
8✔
25
   }
26
   hoi4::CountryScope country_scope(country_itr->second);
5✔
27

28
   for (const hoi4::RepeatFocus& repeat_focus: role.GetRepeatFocuses())
12✔
29
   {
30
      // Track how many countries are applicable so that the focuses can be balanced in position.
31
      int num_targets = 0;
7✔
32

33
      // Get all applicable scopes
34
      std::vector<hoi4::Scope> valid_scopes = repeat_focus.GetTrigger().FindAllValid(
7✔
35
          {
36
              .root = country_scope,
37
              .this_scope = country_scope,
38
              .prev = country_scope,
39
              .from = country_scope,
40
          },
41
          world);
7✔
42

43
      // Generate all the focuses
44
      std::map<std::string, std::vector<hoi4::Focus>> target_focuses;
7✔
45
      for (const auto& scope: valid_scopes)
23✔
46
      {
47
         // get the relevant id
48
         std::string new_id;
16✔
49
         if (const hoi4::CountryScope* maybe_country = std::get_if<hoi4::CountryScope>(&scope); maybe_country)
16✔
50
         {
51
            new_id = maybe_country->country.GetTag();
13✔
52
         }
53
         else if (const hoi4::StateScope* maybe_state = std::get_if<hoi4::StateScope>(&scope); maybe_state)
3✔
54
         {
55
            new_id = std::to_string(maybe_state->state.GetId());
3✔
56
         }
57
         else
58
         {
UNCOV
59
            continue;
×
60
         }
61
         ++num_targets;
16✔
62

63
         // update focuses for this scope
64
         for (hoi4::Focus focus_copy: repeat_focus.GetFocuses())
42✔
65
         {
66
            std::string original_id = focus_copy.id;
26✔
67
            focus_copy.ApplyReplacement("$TARGET_ID$", new_id);
26✔
68

69
            role_lookup[original_id].push_back(focus_copy.id);
26✔
70
            if (auto [target_itr, success] = target_focuses.emplace(original_id, std::vector{focus_copy}); !success)
52✔
71
            {
72
               target_itr->second.push_back(focus_copy);
15✔
73
            }
74
         }
26✔
75
      }
16✔
76

77
      // Properly position all the focuses
78
      for (std::vector<hoi4::Focus>& focuses: target_focuses | std::views::values)
18✔
79
      {
80
         int x_position = 1 - num_targets;
11✔
81
         int targets_addressed = 0;
11✔
82
         for (hoi4::Focus& focus: focuses)
37✔
83
         {
84
            focus.x_position = x_position;
26✔
85
            repeated_focuses.push_back(focus);
26✔
86

87
            x_position += 2;
26✔
88
            ++targets_addressed;
26✔
89
            if (targets_addressed == num_targets)
26✔
90
            {
91
               x_position = 1 - num_targets;
11✔
92
               targets_addressed = 0;
11✔
93
            }
94
         }
95
      }
96
   }
7✔
97

98
   return repeated_focuses;
5✔
99
}
39✔
100

101

102
// For any prerequisites that are of the form repeat_focus = <a repeat focus>, expand them to be normal prerequisites
103
// than include all of the focuses that the repeat focus had been expanded into
104
void UpdatePrerequisites(const std::map<std::string, std::vector<std::string>>& role_lookup, hoi4::Focus& focus)
37✔
105
{
106
   for (std::string& prerequisite: focus.prerequisites)
45✔
107
   {
108
      if (!prerequisite.contains("repeat_focus"))
8✔
109
      {
110
         continue;
6✔
111
      }
112

113
      std::string prerequisite_string = prerequisite;
2✔
114

115
      // determine the text to replace
116
      std::string to_replace;
2✔
117
      std::regex prerequisite_regex(R"(.*(repeat_focus = [^\s]+).*)");
2✔
118
      if (std::smatch match; std::regex_match(prerequisite_string, match, prerequisite_regex))
2✔
119
      {
120
         to_replace = match.str(1);
2✔
121
      }
2✔
122

123
      // determine the replacement text
124
      std::string replace_with;
2✔
125
      std::regex lookup_regex(R"(.*repeat_focus =\s([^\s]+).*)");
2✔
126
      if (std::smatch match; std::regex_match(prerequisite_string, match, lookup_regex))
2✔
127
      {
128
         if (auto role = role_lookup.find(match.str(1)); role != role_lookup.end())
2✔
129
         {
130
            // while we're here, position this focus evenly under the repeat focuses
131
            if (!role->second.empty())
2✔
132
            {
133
               if ((role->second.size() % 2) == 0)
2✔
134
               {
135
                  focus.relative_position_id = role->second.at(role->second.size() / 2 - 1);
1✔
136
                  focus.x_position = 1;
1✔
137
               }
138
               else
139
               {
140
                  focus.relative_position_id = role->second.at(role->second.size() / 2);
1✔
141
                  focus.x_position = 0;
1✔
142
               }
143
            }
144

145
            for (auto& new_id: role->second)
7✔
146
            {
147
               if (replace_with.empty())
5✔
148
               {
149
                  replace_with = fmt::format("focus = {}", new_id);
2✔
150
               }
151
               else
152
               {
153
                  replace_with = fmt::format("{} focus = {}", replace_with, new_id);
3✔
154
               }
155
            }
156
         }
157
      }
2✔
158

159
      // actually do the text replacement
160
      while (prerequisite.find(to_replace) != std::string::npos)
4✔
161
      {
162
         prerequisite.replace(prerequisite.find(to_replace), to_replace.size(), replace_with);
2✔
163
      }
164
   }
2✔
165
}
37✔
166

167
}  // namespace
168

169

170

171
hoi4::FocusTree hoi4::AssembleTree(const std::vector<Role>& roles, std::string_view tag, const hoi4::World& world)
11✔
172
{
173
   FocusTree tree;
11✔
174

175
   std::map<std::string, std::vector<std::string>> role_lookup;
11✔
176
   for (const Role& role: roles)
24✔
177
   {
178
      // add all shared focuses to the tree
179
      const std::vector<std::string>& shared_focuses = role.GetSharedFocuses();
13✔
180
      tree.shared_focuses.insert(tree.shared_focuses.end(), shared_focuses.begin(), shared_focuses.end());
13✔
181

182
      // add all regular focuses to the tree
183
      const std::vector<Focus>& focuses = role.GetFocuses();
13✔
184
      tree.focuses.insert(tree.focuses.end(), focuses.begin(), focuses.end());
13✔
185

186
      // add all repeated focuses to the tree
187
      const std::vector<Focus> repeated_focuses = CreateRepeatedFocuses(role, world, tag, role_lookup);
13✔
188
      tree.focuses.insert(tree.focuses.end(), repeated_focuses.begin(), repeated_focuses.end());
13✔
189
   }
13✔
190

191
   // make final updates to the focuses - positions, fixes to prerequisites, and replace $TAG$ with the actual tag
192
   int position = static_cast<int>(tree.shared_focuses.size()) * 10;
11✔
193
   for (Focus& focus: tree.focuses)
48✔
194
   {
195
      if (focus.tree_starter)
37✔
196
      {
197
         focus.x_position = position;
4✔
198
         position += 10;
4✔
199
      }
200

201
      UpdatePrerequisites(role_lookup, focus);
37✔
202
      focus.ApplyReplacement("$TAG$", tag);
37✔
203
   }
204

205
   return tree;
22✔
206
}
11✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc