Skip to content

Commit ae3e1a7

Browse files
committed
outline of the json loader
1 parent ea5cfad commit ae3e1a7

File tree

8 files changed

+356
-9
lines changed

8 files changed

+356
-9
lines changed

.github/workflows/cpp-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ jobs:
2323
steps:
2424
- uses: actions/checkout@v4
2525

26-
- name: Install CMake and GTest
26+
- name: Install CMake, GTest, and nlohmann-json
2727
run: |
2828
sudo apt-get update
29-
sudo apt-get install -y cmake libgtest-dev
29+
sudo apt-get install -y cmake libgtest-dev nlohmann-json3-dev
3030
cd /usr/src/gtest
3131
sudo cmake CMakeLists.txt
3232
sudo make

cpp-backend-new/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ set(CMAKE_CXX_STANDARD 20)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
88

9+
# Add Homebrew include path
10+
if(APPLE)
11+
execute_process(
12+
COMMAND brew --prefix
13+
OUTPUT_VARIABLE BREW_PREFIX
14+
OUTPUT_STRIP_TRAILING_WHITESPACE
15+
)
16+
include_directories(${BREW_PREFIX}/include)
17+
endif()
18+
919
include_directories(include src)
1020

1121
file(GLOB_RECURSE SOURCES "src/*.cpp")
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef SKILL_PARSER_HPP
2+
#define SKILL_PARSER_HPP
3+
4+
#include <string>
5+
#include <vector>
6+
#include <optional>
7+
#include <memory>
8+
#include <nlohmann/json.hpp>
9+
#include "../src/skills/Skill.hpp"
10+
#include "../src/skills/StatusSkill.hpp"
11+
#include "../src/skills/DamageSkill.hpp"
12+
#include "../src/skills/SkillType.hpp"
13+
#include "../src/skills/SkillTarget.hpp"
14+
#include "../src/skills/ConditionType.hpp"
15+
#include "../src/effects/EffectType.hpp"
16+
#include "../src/effects/TimedEffect.hpp"
17+
18+
using json = nlohmann::json;
19+
20+
class SkillParser {
21+
public:
22+
SkillParser(const std::string& json_file_path);
23+
24+
// Search for all skills belonging to a commander and convert to Skill objects
25+
std::vector<std::unique_ptr<Skill>> getCommanderSkills(const std::string& commander_name) const;
26+
27+
// Search for a specific skill by ID and convert to Skill object
28+
std::unique_ptr<Skill> getSkillById(const std::string& skill_id) const;
29+
30+
// Gets skills from the "Skills" section
31+
std::vector<std::unique_ptr<Skill>> getGenericSkill(const std::string& generic_skill) const;
32+
33+
// Load and parse the JSON file
34+
bool loadJson();
35+
36+
private:
37+
std::string file_path;
38+
json skill_data;
39+
40+
// Convert JSON skill to actual Skill object (StatusSkill, DamageSkill, etc.)
41+
std::unique_ptr<Skill> jsonToSkill(const json& skill_json) const;
42+
43+
// Helper functions to convert strings to enums
44+
SkillType stringToSkillType(std::string_view& type_str) const;
45+
EffectType stringToEffectType(std::string_view& effect_str) const;
46+
SkillTarget stringToSkillTarget(std::string_view& target_str) const;
47+
ConditionType stringToConditionType(std::string_view& condition_str) const;
48+
SkillTarget stringToTargetType(std::string_view& target_str) const;
49+
50+
// Build SkillCondition from trigger requirements
51+
SkillCondition buildSkillCondition(const json& trigger_json) const;
52+
53+
// Determine skill target from JSON (FRIENDLY, ENEMY, etc.)
54+
SkillTarget determineSkillTarget(const json& skill_json) const;
55+
};
56+
57+
#endif

cpp-backend-new/resources/skills.json

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,25 @@
66
"id": "sigrid_primary_hit",
77
"trigger": {
88
"type": "round_repeating",
9-
"value": 3,
10-
"triggerRequirement": [
11-
"poison",
12-
"basicAttack"
13-
]
9+
"modulo": 6,
10+
"triggerRequirement": "poison",
11+
"dependentRequirement": "basicAttack"
1412
},
15-
"effects": [
13+
"sub_skills": [
1614
{
1715
"type": "heal",
1816
"magnitude": 50,
1917
"skillDuration": 2,
2018
"removable": false,
21-
"chance": 1.0
19+
"chance": 1.0,
20+
"target": "friendly"
21+
},
22+
{
23+
"type": "damage",
24+
"magnitude": 50,
25+
"removable": false,
26+
"chance": 0.5,
27+
"target": "enemy"
2228
}
2329
],
2430
"skillType": {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include "DamageSkill.hpp"
2+
3+
DamageSkill::DamageSkill(const double damage_magnitude, const SkillType skill_type, const EffectType effect_type,
4+
SkillCondition skill_condition, SkillTarget skill_target)
5+
: Skill(skill_type, effect_type, skill_condition, skill_target),
6+
damage_magnitude { damage_magnitude }
7+
{}
8+
9+
void DamageSkill::onDependent(Combatant& friendly_combatant, Combatant& enemy_combatant) const
10+
{}
11+
12+
bool DamageSkill::operator==(const Skill& other) const
13+
{
14+
const DamageSkill* other_skill = dynamic_cast<const DamageSkill*>(&other);
15+
if (other_skill)
16+
{
17+
return getSkillType() == other_skill->getSkillType();
18+
}
19+
return false;
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include "Skill.hpp"
2+
#include "../effects/TimedEffect.hpp"
3+
4+
class DamageSkill: public Skill
5+
{
6+
public:
7+
DamageSkill(const double damage_magnitude, const SkillType skill_type, const EffectType effect_type, SkillCondition skill_condition, SkillTarget skill_target);
8+
void onDependent(Combatant& friendly_combatant, Combatant& enemy_combatant) const override;
9+
bool operator==(const Skill& other) const override;
10+
private:
11+
const double damage_magnitude;
12+
};

cpp-backend-new/src/skills/JsonParser.cpp

Whitespace-only changes.
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#include "../../include/SkillParser.hpp"
2+
#include <fstream>
3+
#include <iostream>
4+
5+
SkillParser::SkillParser(const std::string& json_file_path)
6+
: file_path(json_file_path)
7+
{}
8+
9+
bool SkillParser::loadJson() {
10+
try {
11+
std::ifstream file(file_path);
12+
if (!file.is_open()) {
13+
std::cerr << "Failed to open file: " << file_path << std::endl;
14+
return false;
15+
}
16+
17+
file >> skill_data;
18+
return true;
19+
} catch (const json::exception& e) {
20+
std::cerr << "JSON parsing error: " << e.what() << std::endl;
21+
return false;
22+
}
23+
}
24+
25+
std::vector<std::unique_ptr<Skill>> SkillParser::getCommanderSkills(const std::string& commander_name) const {
26+
std::vector<std::unique_ptr<Skill>> skills;
27+
28+
if (!skill_data.contains("Commanders") || !skill_data["Commanders"].contains(commander_name)) {
29+
return skills;
30+
}
31+
32+
const json& commander = skill_data["Commanders"][commander_name];
33+
34+
// Parse all skill categories (AwakenedActive, Secondary, etc.)
35+
for (auto& [category, skill_array] : commander.items()) {
36+
if (skill_array.is_array()) {
37+
for (const auto& skill_json : skill_array) {
38+
auto skill = jsonToSkill(skill_json);
39+
if (skill) {
40+
skills.push_back(std::move(skill));
41+
}
42+
}
43+
}
44+
}
45+
46+
return skills;
47+
}
48+
49+
std::unique_ptr<Skill> SkillParser::getSkillById(const std::string& skill_id) const {
50+
// Search in Commanders
51+
if (skill_data.contains("Commanders")) {
52+
for (auto& [commander_name, commander_data] : skill_data["Commanders"].items()) {
53+
for (auto& [category, skill_array] : commander_data.items()) {
54+
if (skill_array.is_array()) {
55+
for (const auto& skill_json : skill_array) {
56+
if (skill_json.contains("id") && skill_json["id"] == skill_id) {
57+
return jsonToSkill(skill_json);
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
// Search in Skills section
66+
if (skill_data.contains("Skills")) {
67+
for (auto& [skill_name, skill_json] : skill_data["Skills"].items()) {
68+
if (skill_json.contains("id") && skill_json["id"] == skill_id) {
69+
return jsonToSkill(skill_json);
70+
}
71+
}
72+
}
73+
74+
return nullptr;
75+
}
76+
77+
std::vector<std::unique_ptr<Skill>> SkillParser::getGenericSkills() const {
78+
std::vector<std::unique_ptr<Skill>> skills;
79+
80+
if (!skill_data.contains("Skills")) {
81+
return skills;
82+
}
83+
84+
for (auto& [skill_name, skill_json] : skill_data["Skills"].items()) {
85+
auto skill = jsonToSkill(skill_json);
86+
if (skill) {
87+
skills.push_back(std::move(skill));
88+
}
89+
}
90+
91+
return skills;
92+
}
93+
94+
std::unique_ptr<Skill> SkillParser::jsonToSkill(const json& skill_json) const {
95+
if (!skill_json.contains("effects") || !skill_json["effects"].is_array()) {
96+
std::cerr << "Skill missing effects array" << std::endl;
97+
return nullptr;
98+
}
99+
100+
// Get the skill type from skillType or metaType
101+
SkillType skill_type = SkillType::COMMAND; // Default
102+
if (skill_json.contains("skillType") && skill_json["skillType"].contains("category")) {
103+
skill_type = stringToSkillType(skill_json["skillType"]["category"].get<std::string>());
104+
} else if (skill_json.contains("metaType") && skill_json["metaType"].contains("category")) {
105+
skill_type = stringToSkillType(skill_json["metaType"]["category"].get<std::string>());
106+
}
107+
108+
// Build skill condition from trigger requirements
109+
SkillCondition condition = buildSkillCondition(skill_json);
110+
111+
// Determine skill target
112+
SkillTarget target = determineSkillTarget(skill_json);
113+
114+
// Process effects - create appropriate skill type based on first effect
115+
const json& effects = skill_json["effects"];
116+
if (effects.empty()) {
117+
std::cerr << "Skill has no effects" << std::endl;
118+
return nullptr;
119+
}
120+
121+
const json& first_effect = effects[0];
122+
if (!first_effect.contains("type") || !first_effect.contains("magnitude")) {
123+
std::cerr << "Effect missing type or magnitude" << std::endl;
124+
return nullptr;
125+
}
126+
127+
std::string effect_type_str = first_effect["type"].get<std::string>();
128+
EffectType effect_type = stringToEffectType(effect_type_str);
129+
double magnitude = first_effect["magnitude"].get<double>();
130+
131+
// Determine if this is a status effect (heal, poison, burn, shield, etc.) or direct damage
132+
if (effect_type_str == "directDamage" || effect_type_str == "damage") {
133+
// Create DamageSkill
134+
return std::make_unique<DamageSkill>(
135+
magnitude,
136+
skill_type,
137+
effect_type,
138+
condition,
139+
target
140+
);
141+
} else {
142+
// Create StatusSkill (heal, poison, burn, shield, etc.)
143+
// Need duration for TimedEffect
144+
int duration = 1; // Default duration
145+
if (first_effect.contains("skillDuration")) {
146+
duration = first_effect["skillDuration"].get<int>();
147+
}
148+
149+
TimedEffect timed_effect(duration, magnitude);
150+
151+
return std::make_unique<StatusSkill>(
152+
timed_effect,
153+
skill_type,
154+
effect_type,
155+
condition,
156+
target
157+
);
158+
}
159+
}
160+
161+
SkillCondition SkillParser::buildSkillCondition(const json& skill_json) const {
162+
// TODO: Parse trigger requirements and build proper SkillCondition
163+
// For now, return a default condition
164+
// Need to map triggerRequirement array to ConditionType and EffectType
165+
166+
ConditionType condition_type = ConditionType::HAS_EFFECT_SELF; // Default
167+
EffectType effect_type = EffectType::POISON; // Default
168+
169+
if (skill_json.contains("trigger") && skill_json["trigger"].contains("triggerRequirement")) {
170+
const json& requirements = skill_json["trigger"]["triggerRequirement"];
171+
if (requirements.is_array() && !requirements.empty()) {
172+
// TODO: Map string requirements to ConditionType and EffectType
173+
// Example: "poison" -> ConditionType::HAS_EFFECT_SELF, EffectType::POISON
174+
// Example: "basicAttack" -> ConditionType related to attack
175+
std::string first_req = requirements[0].get<std::string>();
176+
177+
// Basic mapping (needs expansion based on your requirements)
178+
if (first_req == "poison") {
179+
condition_type = ConditionType::HAS_EFFECT_SELF;
180+
effect_type = EffectType::POISON;
181+
}
182+
// TODO: Add more condition mappings
183+
}
184+
}
185+
186+
return SkillCondition(condition_type, effect_type);
187+
}
188+
189+
SkillTarget SkillParser::determineSkillTarget(const json& skill_json) const {
190+
// TODO: Determine from JSON if skill targets FRIENDLY or ENEMY
191+
// This might be in a "target" field or inferred from effect type
192+
// For now, default to FRIENDLY for heals, ENEMY for damage
193+
194+
if (skill_json.contains("effects") && skill_json["effects"].is_array() && !skill_json["effects"].empty()) {
195+
std::string effect_type = skill_json["effects"][0]["type"].get<std::string>();
196+
if (effect_type == "heal" || effect_type == "shield" || effect_type == "buff") {
197+
return SkillTarget::FRIENDLY;
198+
} else if (effect_type == "damage" || effect_type == "directDamage" || effect_type == "poison" || effect_type == "burn") {
199+
return SkillTarget::ENEMY;
200+
}
201+
}
202+
203+
// TODO: Add explicit target field parsing if it exists in JSON
204+
return SkillTarget::ENEMY; // Default
205+
}
206+
207+
SkillType SkillParser::stringToSkillType(const std::string& type_str) const {
208+
if (type_str == "command") return SkillType::COMMAND;
209+
if (type_str == "passive") return SkillType::PASSIVE;
210+
if (type_str == "cooperation") return SkillType::COOPERATION;
211+
if (type_str == "counterattack") return SkillType::COUNTERATTACK;
212+
// TODO: Add more SkillType mappings as needed
213+
return SkillType::COMMAND; // Default
214+
}
215+
216+
EffectType SkillParser::stringToEffectType(const std::string& effect_str) const {
217+
if (effect_str == "poison") return EffectType::POISON;
218+
if (effect_str == "heal" || effect_str == "healing") return EffectType::HEALING;
219+
if (effect_str == "burn") return EffectType::BURN;
220+
if (effect_str == "bleed") return EffectType::BLEED;
221+
if (effect_str == "absorption") return EffectType::ABSORPTION;
222+
if (effect_str == "retribution") return EffectType::RETRIBUTION;
223+
// TODO: Add specific damage effect type or use appropriate existing type
224+
if (effect_str == "damage" || effect_str == "directDamage") return EffectType::BURN; // Using BURN as placeholder for damage
225+
// TODO: Add more EffectType mappings based on your EffectType enum
226+
return EffectType::POISON; // Default
227+
}
228+
229+
SkillTarget SkillParser::stringToSkillTarget(const std::string& target_str) const {
230+
if (target_str == "friendly" || target_str == "FRIENDLY") return SkillTarget::FRIENDLY;
231+
if (target_str == "enemy" || target_str == "ENEMY") return SkillTarget::ENEMY;
232+
// TODO: Add more SkillTarget mappings if needed
233+
return SkillTarget::ENEMY; // Default
234+
}
235+
236+
ConditionType SkillParser::stringToConditionType(const std::string& condition_str) const {
237+
if (condition_str == "HAS_EFFECT_SELF") return ConditionType::HAS_EFFECT_SELF;
238+
if (condition_str == "HAS_EFFECT_TARGET") return ConditionType::HAS_EFFECT_TARGET;
239+
if (condition_str == "TROOP_COUNT_GREATER_THAN_TARGET") return ConditionType::TROOP_COUNT_GREATER_THAN_TARGET;
240+
// TODO: Add more ConditionType mappings as needed
241+
return ConditionType::HAS_EFFECT_SELF; // Default
242+
}

0 commit comments

Comments
 (0)