1 #include <cmath>
2 #include <iostream>
3 #include <libxml/xmlmemory.h>
4 #include <libxml/parser.h>
5 
6 #include "util/exception.h"
7 #include "util/helpers.h"
8 #include "xml.h"
9 #include "apibase.h"
10 #include "apiskilltree.h"
11 #include "apicerttree.h"
12 #include "apicharsheet.h"
13 
14 void
set_api_data(EveApiData const & data)15 ApiCharSheet::set_api_data (EveApiData const& data)
16 {
17   this->valid = false;
18   this->ApiBase::set_api_data(data);
19 
20   /* Reset values. */
21   this->implant = 0.0;
22   this->total_sp = 0;
23   for (int i = 0; i < 6; ++i)
24     this->skills_at[i] = 0;
25 
26   /* Parse the data. */
27   this->parse_xml();
28 
29   /* Force the sheet to have at least 30 minutes cache time. */
30   this->enforce_cache_time(API_CHAR_SHEET_MIN_CACHE_TIME);
31 
32   /* Find bonus attributes for skills. */
33   this->total = this->base + this->implant;
34 
35   /* Calculate start SP, destination SP and completed. */
36   ApiSkillTreePtr stree = ApiSkillTree::request();
37   for (std::size_t i = 0; i < this->skills.size(); ++i)
38   {
39     ApiCharSheetSkill& cskill = this->skills[i];
40     ApiSkill const* skill = stree->get_skill_for_id(cskill.id);
41     if (skill == 0)
42     {
43       std::cout << "Warning: Ignoring unknown skill (ID " << cskill.id
44           << "). Update SkillTree.xml." << std::endl;
45       this->skills.erase(this->skills.begin() + i);
46       i -= 1;
47       continue;
48     }
49 
50     /* Update a few fields of the char sheet skill. */
51     cskill.details = skill;
52     cskill.points_start = ApiCharSheet::calc_start_sp
53         (skills[i].level, skill->rank);
54     cskill.points_dest = ApiCharSheet::calc_dest_sp
55         (skills[i].level, skill->rank);
56     cskill.points_max = ApiCharSheet::calc_dest_sp
57         (4, skill->rank);
58 
59     cskill.completed = (double)(skills[i].points - skills[i].points_start)
60         / (double)(skills[i].points_dest - skills[i].points_start);
61 
62     /* Sum up the total amount of SP for the character. */
63     this->total_sp += cskill.points;
64     this->skills_at[cskill.level] += 1;
65   }
66 
67   /* Update certificate field "details". */
68   ApiCertTreePtr ctree = ApiCertTree::request();
69   for (unsigned int i = 0; i < this->certs.size(); ++i)
70   {
71     ApiCharSheetCert& ccert = this->certs[i];
72     ApiCert const* cert = ctree->get_certificate_for_id(ccert.id);
73     if (cert == 0)
74     {
75       std::cout << "Warning: Ignoring unknown certificate (ID " << ccert.id
76           << "). Update GtkEveMon or CertificateTree.xml." << std::endl;
77       this->certs.erase(this->certs.begin() + i);
78       i -= 1;
79       continue;
80     }
81 
82     ccert.details = cert;
83   }
84 
85   //this->debug_dump();
86   this->valid = true;
87 }
88 
89 /* ---------------------------------------------------------------- */
90 
91 void
parse_xml(void)92 ApiCharSheet::parse_xml (void)
93 {
94   this->skills.clear();
95 
96   std::cout << "Parsing XML: CharacterSheet.xml ..." << std::endl;
97   XmlDocumentPtr xml = XmlDocument::create
98       (&this->http_data->data[0], this->http_data->data.size());
99   xmlNodePtr root = xml->get_root_element();
100   this->parse_eveapi_tag(root);
101 }
102 
103 /* ---------------------------------------------------------------- */
104 
105 void
parse_eveapi_tag(xmlNodePtr node)106 ApiCharSheet::parse_eveapi_tag (xmlNodePtr node)
107 {
108   if (node->type != XML_ELEMENT_NODE
109       || xmlStrcmp(node->name, (xmlChar const*)"eveapi"))
110     throw Exception("Invalid XML root. Expecting <eveapi> node.");
111 
112   for (node = node->children; node != 0; node = node->next)
113   {
114     /* Let the base class know of some fields. */
115     this->check_node(node);
116 
117     if (node->type == XML_ELEMENT_NODE
118         && !xmlStrcmp(node->name, (xmlChar const*)"result"))
119     {
120       //std::cout << "Found <result> tag" << std::endl;
121       this->parse_result_tag(node->children);
122     }
123   }
124 }
125 
126 /* ---------------------------------------------------------------- */
127 
128 void
parse_result_tag(xmlNodePtr node)129 ApiCharSheet::parse_result_tag (xmlNodePtr node)
130 {
131   for (; node != 0; node = node->next)
132   {
133     if (node->type != XML_ELEMENT_NODE)
134       continue;
135 
136     this->set_string_if_node_text(node, "characterID", this->char_id);
137     this->set_string_if_node_text(node, "name", this->name);
138     this->set_string_if_node_text(node, "race", this->race);
139     this->set_string_if_node_text(node, "bloodLine", this->bloodline);
140     this->set_string_if_node_text(node, "gender", this->gender);
141     this->set_string_if_node_text(node, "corporationName", this->corp);
142     this->set_string_if_node_text(node, "balance", this->balance);
143 
144     this->set_string_if_node_text(node, "cloneName", this->clone_name);
145     this->set_uint_if_node_text(node, "cloneSkillPoints", this->clone_sp);
146     this->set_uint_if_node_text(node, "freeSkillPoints", this->free_sp);
147     this->set_string_if_node_text(node, "lastRespecDate", this->last_respec);
148     this->set_string_if_node_text(node, "lastTimedRespec", this->last_timed_respec);
149     this->set_uint_if_node_text(node, "freeRespecs", this->free_respecs);
150     this->set_string_if_node_text(node, "cloneJumpDate", this->last_clone_jump);
151 
152     if (!xmlStrcmp(node->name, (xmlChar const*)"attributes"))
153       this->parse_attribute_tag(node->children);
154 
155     if (!xmlStrcmp(node->name, (xmlChar const*)"attributeEnhancers"))
156       this->parse_attrib_enhancers_tag(node->children);
157 
158     if (!xmlStrcmp(node->name, (xmlChar const*)"rowset"))
159     {
160       std::string name = this->get_property(node, "name");
161       if (name == "skills")
162         this->parse_skills_tag(node->children);
163       else if (name == "certificates")
164         this->parse_certificates_tag(node->children);
165     }
166   }
167 }
168 
169 /* ---------------------------------------------------------------- */
170 
171 void
parse_attribute_tag(xmlNodePtr node)172 ApiCharSheet::parse_attribute_tag (xmlNodePtr node)
173 {
174   for (; node != 0; node = node->next)
175   {
176     if (node->type != XML_ELEMENT_NODE)
177       continue;
178 
179     this->set_double_if_node_text(node, "intelligence", this->base.intl);
180     this->set_double_if_node_text(node, "memory", this->base.mem);
181     this->set_double_if_node_text(node, "charisma", this->base.cha);
182     this->set_double_if_node_text(node, "perception", this->base.per);
183     this->set_double_if_node_text(node, "willpower", this->base.wil);
184   }
185 }
186 
187 /* ---------------------------------------------------------------- */
188 
189 void
parse_attrib_enhancers_tag(xmlNodePtr node)190 ApiCharSheet::parse_attrib_enhancers_tag (xmlNodePtr node)
191 {
192   for (; node != 0; node = node->next)
193   {
194     if (node->type != XML_ELEMENT_NODE)
195       continue;
196 
197     this->find_implant_bonus(node, "memoryBonus", this->implant.mem);
198     this->find_implant_bonus(node, "willpowerBonus", this->implant.wil);
199     this->find_implant_bonus(node, "perceptionBonus", this->implant.per);
200     this->find_implant_bonus(node, "intelligenceBonus", this->implant.intl);
201     this->find_implant_bonus(node, "charismaBonus", this->implant.cha);
202   }
203 }
204 
205 /* ---------------------------------------------------------------- */
206 
207 void
parse_skills_tag(xmlNodePtr node)208 ApiCharSheet::parse_skills_tag (xmlNodePtr node)
209 {
210   for (; node != 0; node = node->next)
211   {
212     if (node->type != XML_ELEMENT_NODE)
213       continue;
214 
215     if (!xmlStrcmp(node->name, (xmlChar const*)"row"))
216     {
217       /* Prepare a new skill to append. */
218       ApiCharSheetSkill skill;
219 
220       /* Fetch these fields. If they are not there, it's an error. */
221       try
222       {
223         skill.id = Helpers::get_int_from_string
224             (this->get_property(node, "typeID"));
225         skill.points = Helpers::get_int_from_string
226             (this->get_property(node, "skillpoints"));
227       }
228       catch (Exception& e)
229       {
230         throw Exception(e + " in element \"row\"");
231       }
232 
233       /* Fetch the level field. If it's not there, ignore the skill.
234        * This might happen if the skill in "unpublished" (removed?). */
235       try
236       {
237         skill.level = Helpers::get_int_from_string
238             (this->get_property(node, "level"));
239 
240         /* Add skill to list. */
241         this->skills.push_back(skill);
242       }
243       catch (Exception& e)
244       {
245         std::cout << "Warning: Ignoring skill without "
246             "\"level\" attribute: " << skill.id << std::endl;
247       }
248     }
249   }
250 }
251 
252 /* ---------------------------------------------------------------- */
253 
254 void
parse_certificates_tag(xmlNodePtr node)255 ApiCharSheet::parse_certificates_tag (xmlNodePtr node)
256 {
257   for (; node != 0; node = node->next)
258   {
259     if (node->type != XML_ELEMENT_NODE)
260       continue;
261 
262     if (!xmlStrcmp(node->name, (xmlChar const*)"row"))
263     {
264       ApiCharSheetCert cert;
265 
266       try
267       {
268         cert.id = Helpers::get_int_from_string
269             (this->get_property(node, "certificateID"));
270         this->certs.push_back(cert);
271       }
272       catch (Exception& e)
273       {
274         throw Exception(e + " in element \"row\"");
275       }
276     }
277   }
278 }
279 
280 /* ---------------------------------------------------------------- */
281 
282 void
find_implant_bonus(xmlNodePtr node,char const * name,double & v)283 ApiCharSheet::find_implant_bonus (xmlNodePtr node, char const* name, double& v)
284 {
285   if (node->type == XML_ELEMENT_NODE
286       && !xmlStrcmp(node->name, (xmlChar const*)name))
287   {
288     node = node->children;
289     while (node != 0)
290     {
291       this->set_double_if_node_text(node, "augmentatorValue", v);
292       node = node->next;
293     }
294   }
295 }
296 
297 /* ---------------------------------------------------------------- */
298 
299 void
add_char_skill(int skill_id,int level)300 ApiCharSheet::add_char_skill (int skill_id, int level)
301 {
302   /* Bail out if we get an invalid level. */
303   if (level < 0 || level > 5)
304     return;
305 
306   ApiCharSheetSkill* cskill = this->get_skill_for_id(skill_id);
307 
308   /* Bail out if the character has the skill and level. */
309   if (cskill != 0 && cskill->level >= level)
310     return;
311 
312   /* Create skill if the character does not have it, update otherwise. */
313   if (cskill == 0)
314   {
315     /* Create new skill. */
316     ApiSkillTreePtr tree = ApiSkillTree::request();
317     ApiSkill const* skill = tree->get_skill_for_id(skill_id);
318 
319     if (skill == 0)
320     {
321       std::cout << "Warning: Cannot add skill (ID " << skill_id
322           << ") to " << this->name << ", skill not available!" << std::endl;
323       return;
324     }
325 
326     int skill_start_sp = ApiCharSheet::calc_start_sp(level, skill->rank);
327     int skill_dest_sp = ApiCharSheet::calc_dest_sp(level, skill->rank);
328     int skill_max_points = ApiCharSheet::calc_dest_sp(4, skill->rank);
329 
330     ApiCharSheetSkill new_cskill;
331     new_cskill.id = skill->id;
332     new_cskill.level = level;
333     new_cskill.points = skill_start_sp;
334     new_cskill.points_max = skill_max_points;
335     new_cskill.points_start = skill_start_sp;
336     new_cskill.points_dest = skill_dest_sp;
337     new_cskill.completed = 0.0;
338     new_cskill.details = skill;
339 
340     this->skills.push_back(new_cskill);
341     this->skills_at[level] += 1;
342     this->total_sp += skill_start_sp;
343   }
344   else
345   {
346     /* Update existing skill. */
347     ApiSkill const* skill = cskill->details;
348 
349     int old_level = cskill->level;
350     int old_points = cskill->points;
351 
352     int skill_start_sp = ApiCharSheet::calc_start_sp(level, skill->rank);
353     int skill_dest_sp = ApiCharSheet::calc_dest_sp(level, skill->rank);
354     int skill_max_points = ApiCharSheet::calc_dest_sp(4, skill->rank);
355 
356     cskill->level = level;
357     cskill->points = skill_start_sp;
358     cskill->points_max = skill_max_points;
359     cskill->points_start = skill_start_sp;
360     cskill->points_dest = skill_dest_sp;
361     cskill->completed = 0.0;
362 
363     this->skills_at[old_level] -= 1;
364     this->skills_at[level] += 1;
365     this->total_sp -= old_points;
366     this->total_sp += skill_start_sp;
367   }
368 }
369 
370 /* ---------------------------------------------------------------- */
371 
372 int
get_level_for_skill(int id) const373 ApiCharSheet::get_level_for_skill (int id) const
374 {
375   for (std::size_t i = 0; i < this->skills.size(); ++i)
376     if (this->skills[i].id == id)
377       return this->skills[i].level;
378 
379   /* Return level 0 if skill is not in the list. */
380   return 0;
381 }
382 
383 /* ---------------------------------------------------------------- */
384 
385 ApiCharSheetSkill*
get_skill_for_id(int id)386 ApiCharSheet::get_skill_for_id (int id)
387 {
388   for (std::size_t i = 0; i < this->skills.size(); ++i)
389     if (this->skills[i].id == id)
390       return &this->skills[i];
391 
392   return 0;
393 }
394 
395 /* ---------------------------------------------------------------- */
396 
397 ApiCharSheetCert*
get_cert_for_id(int id)398 ApiCharSheet::get_cert_for_id (int id)
399 {
400   for (std::size_t i = 0; i < this->certs.size(); ++i)
401     if (this->certs[i].id == id)
402       return &this->certs[i];
403 
404   return 0;
405 }
406 
407 /* ---------------------------------------------------------------- */
408 
409 int
get_grade_for_class(int class_id) const410 ApiCharSheet::get_grade_for_class (int class_id) const
411 {
412   int grade = 0;
413   for (std::size_t i = 0; i < this->certs.size(); ++i)
414   {
415     ApiCert const* cert = this->certs[i].details;
416     ApiCertClass const* cclass = cert->class_details;
417     if (cclass->id == class_id && cert->grade > grade)
418       grade = this->certs[i].details->grade;
419   }
420 
421   return grade;
422 }
423 
424 /* ---------------------------------------------------------------- */
425 
426 unsigned int
get_spph_for_skill(ApiSkill const * skill,ApiCharAttribs const & attribs)427 ApiCharSheet::get_spph_for_skill (ApiSkill const* skill,
428     ApiCharAttribs const& attribs)
429 {
430   double pri;
431   double sec;
432 
433   if (skill == 0)
434     return 0;
435 
436   switch (skill->primary)
437   {
438     case API_ATTRIB_INTELLIGENCE: pri = attribs.intl; break;
439     case API_ATTRIB_MEMORY:       pri = attribs.mem; break;
440     case API_ATTRIB_CHARISMA:     pri = attribs.cha; break;
441     case API_ATTRIB_PERCEPTION:   pri = attribs.per; break;
442     case API_ATTRIB_WILLPOWER:    pri = attribs.wil; break;
443     default: pri = 0.0;
444   }
445 
446   switch (skill->secondary)
447   {
448     case API_ATTRIB_INTELLIGENCE: sec = attribs.intl; break;
449     case API_ATTRIB_MEMORY:       sec = attribs.mem; break;
450     case API_ATTRIB_CHARISMA:     sec = attribs.cha; break;
451     case API_ATTRIB_PERCEPTION:   sec = attribs.per; break;
452     case API_ATTRIB_WILLPOWER:    sec = attribs.wil; break;
453     default: sec = 0.0;
454   }
455 
456   double sppm = (pri + sec / 2.0);
457   return (unsigned int)(sppm * 60.0 + 0.5);
458 }
459 
460 /* ---------------------------------------------------------------- */
461 
462 int
calc_start_sp(int level,int rank)463 ApiCharSheet::calc_start_sp (int level, int rank)
464 {
465   if (level <= 0)
466     return 0;
467 
468   return ApiCharSheet::calc_dest_sp(level - 1, rank);
469 }
470 
471 /* ---------------------------------------------------------------- */
472 
473 int
calc_dest_sp(int level,int rank)474 ApiCharSheet::calc_dest_sp (int level, int rank)
475 {
476   return (int)::ceil(250.0 * rank * ::pow(32.0, level / 2.0));
477 }
478 
479 /* ---------------------------------------------------------------- */
480 
481 void
debug_dump(void)482 ApiCharSheet::debug_dump (void)
483 {
484   std::cout << "Character dump for " << this->name << std::endl;
485   std::cout << "List of all skills:" << std::endl;
486 
487   for (std::size_t i = 0; i < this->skills.size(); ++i)
488   {
489     std::cout << "  " << this->skills[i].details->name
490         << " (" << this->skills[i].id << ")" << std::endl;
491   }
492 
493   std::cout << "List of all certs:" << std::endl;
494   for (std::size_t i = 0; i < this->certs.size(); ++i)
495   {
496     std::cout << "  " << /*this->certs[i].details->name*/ "Unnamed yet"
497         << " (" << this->certs[i].id << ")" << std::endl;
498   }
499 }
500 
501 /* ---------------------------------------------------------------- */
502 
503 bool
is_skill_known(int id)504 ApiCharSheet::is_skill_known (int id)
505 {
506   for (size_t i = 0; i < skills.size(); i++)
507   {
508     if (skills[i].id == id)
509       return true;
510   }
511   return false;
512 }
513 
514 /* ---------------------------------------------------------------- */
515 
516 ApiCharAttribs
operator +(ApiCharAttribs const & atts) const517 ApiCharAttribs::operator+ (ApiCharAttribs const& atts) const
518 {
519   ApiCharAttribs result;
520   result.cha = this->cha + atts.cha;
521   result.intl = this->intl + atts.intl;
522   result.mem = this->mem + atts.mem;
523   result.per = this->per + atts.per;
524   result.wil = this->wil + atts.wil;
525   return result;
526 }
527 
528 /* ---------------------------------------------------------------- */
529 
530 ApiCharAttribs
operator +(double const & value) const531 ApiCharAttribs::operator+ (double const& value) const
532 {
533   ApiCharAttribs result;
534   result.cha = this->cha + value;
535   result.intl = this->intl + value;
536   result.mem = this->mem + value;
537   result.per = this->per + value;
538   result.wil = this->wil + value;
539   return result;
540 }
541 
542 /* ---------------------------------------------------------------- */
543 
544 ApiCharAttribs&
operator +=(ApiCharAttribs const & atts)545 ApiCharAttribs::operator+= (ApiCharAttribs const& atts)
546 {
547   this->cha += atts.cha;
548   this->intl += atts.intl;
549   this->mem += atts.mem;
550   this->per += atts.per;
551   this->wil += atts.wil;
552   return *this;
553 }
554 
555 /* ---------------------------------------------------------------- */
556 
557 ApiCharAttribs
operator -(ApiCharAttribs const & atts) const558 ApiCharAttribs::operator- (ApiCharAttribs const& atts) const
559 {
560   ApiCharAttribs result;
561   result.cha = this->cha - atts.cha;
562   result.intl = this->intl - atts.intl;
563   result.mem = this->mem - atts.mem;
564   result.per = this->per - atts.per;
565   result.wil = this->wil - atts.wil;
566   return result;
567 }
568 
569 /* ---------------------------------------------------------------- */
570 
571 ApiCharAttribs
operator -(double const & value) const572 ApiCharAttribs::operator- (double const& value) const
573 {
574   ApiCharAttribs result;
575   result.cha = this->cha - value;
576   result.intl = this->intl - value;
577   result.mem = this->mem - value;
578   result.per = this->per - value;
579   result.wil = this->wil - value;
580   return result;
581 }
582 
583 /* ---------------------------------------------------------------- */
584 
585 ApiCharAttribs&
operator -=(ApiCharAttribs const & atts)586 ApiCharAttribs::operator-= (ApiCharAttribs const& atts)
587 {
588   this->cha -= atts.cha;
589   this->intl -= atts.intl;
590   this->mem -= atts.mem;
591   this->per -= atts.per;
592   this->wil -= atts.wil;
593   return *this;
594 }
595 
596 /* ---------------------------------------------------------------- */
597 
598 ApiCharAttribs
operator *(ApiCharAttribs const & atts) const599 ApiCharAttribs::operator* (ApiCharAttribs const& atts) const
600 {
601   ApiCharAttribs result;
602   result.cha = this->cha * atts.cha;
603   result.intl = this->intl * atts.intl;
604   result.mem = this->mem * atts.mem;
605   result.per = this->per * atts.per;
606   result.wil = this->wil * atts.wil;
607   return result;
608 }
609 
610 /* ---------------------------------------------------------------- */
611 
612 ApiCharAttribs
operator *(double const & value) const613 ApiCharAttribs::operator* (double const& value) const
614 {
615   ApiCharAttribs result;
616   result.cha = this->cha * value;
617   result.intl = this->intl * value;
618   result.mem = this->mem * value;
619   result.per = this->per * value;
620   result.wil = this->wil * value;
621   return result;
622 }
623 
624 /* ---------------------------------------------------------------- */
625 
626 ApiCharAttribs
operator /(ApiCharAttribs const & atts) const627 ApiCharAttribs::operator/ (ApiCharAttribs const& atts) const
628 {
629   ApiCharAttribs result;
630   result.cha = this->cha / atts.cha;
631   result.intl = this->intl / atts.intl;
632   result.mem = this->mem / atts.mem;
633   result.per = this->per / atts.per;
634   result.wil = this->wil / atts.wil;
635   return result;
636 }
637 
638 /* ---------------------------------------------------------------- */
639 
640 ApiCharAttribs
operator /(double const & value) const641 ApiCharAttribs::operator/ (double const& value) const
642 {
643   ApiCharAttribs result;
644   result.cha = this->cha / value;
645   result.intl = this->intl / value;
646   result.mem = this->mem / value;
647   result.per = this->per / value;
648   result.wil = this->wil / value;
649   return result;
650 }
651 
652 /* ---------------------------------------------------------------- */
653 
654 ApiCharAttribs&
operator =(double const & value)655 ApiCharAttribs::operator= (double const& value)
656 {
657   this->cha = value;
658   this->intl = value;
659   this->mem = value;
660   this->per = value;
661   this->wil = value;
662   return *this;
663 }
664