Crossfire Server, Trunk  R22047
CREMainWindow.cpp
Go to the documentation of this file.
1 #include <Qt>
2 #include <QtWidgets>
3 #include <CREMainWindow.h>
4 #include <CREResourcesWindow.h>
6 #include "CREExperienceWindow.h"
7 #include "QuestManager.h"
8 #include "MessageManager.h"
9 #include "CREReportDisplay.h"
10 #include "CREPixmap.h"
11 #include "CRESmoothFaceMaker.h"
12 #include "CREHPBarMaker.h"
13 #include "ResourcesManager.h"
14 #include "CRECombatSimulator.h"
15 #include "Quest.h"
16 #include "CREHPBarMaker.h"
17 #include "ScriptFileManager.h"
18 #include "FaceMakerDialog.h"
19 #include "EditMonstersDialog.h"
20 
21 extern "C" {
22 #include "global.h"
23 #include "sproto.h"
24 #include "image.h"
25 }
26 #include "assets.h"
27 #include "AssetsManager.h"
28 
30 {
31  myArea = new QMdiArea();
32  setCentralWidget(myArea);
33 
34  createActions();
35  createMenus();
36 
37  statusBar()->showMessage(tr("Ready"));
38  myMapBrowseStatus = new QLabel(tr("Browsing maps..."));
39  statusBar()->addPermanentWidget(myMapBrowseStatus);
40 
41  setWindowTitle(tr("Crossfire Resource Editor"));
42 
45 
48 
51 
53 
55  connect(myMapManager, SIGNAL(browsingMap(const QString&)), this, SLOT(browsingMap(const QString&)));
56  connect(myMapManager, SIGNAL(finished()), this, SLOT(browsingFinished()));
58 }
59 
60 void CREMainWindow::closeEvent(QCloseEvent* event)
61 {
63  delete myMapManager;
64  delete myQuestManager;
65  delete myMessageManager;
66  delete myResourcesManager;
67  cleanup();
68  QMainWindow::closeEvent(event);
69 }
70 
72 {
73  myOpenArtifacts = new QAction(tr("Artifacts"), this);
74  myOpenArtifacts->setStatusTip(tr("List all defined artifacts."));
75  connect(myOpenArtifacts, SIGNAL(triggered()), this, SLOT(onOpenArtifacts()));
76 
77  myOpenArchetypes = new QAction(tr("Archetypes"), this);
78  myOpenArchetypes->setStatusTip(tr("List all defined archetypes."));
79  connect(myOpenArchetypes, SIGNAL(triggered()), this, SLOT(onOpenArchetypes()));
80 
81  myOpenTreasures = new QAction(tr("Treasures"), this);
82  myOpenTreasures->setStatusTip(tr("List all defined treasure lists."));
83  connect(myOpenTreasures, SIGNAL(triggered()), this, SLOT(onOpenTreasures()));
84 
85  myOpenAnimations = new QAction(tr("Animations"), this);
86  myOpenAnimations->setStatusTip(tr("List all defined animations."));
87  connect(myOpenAnimations, SIGNAL(triggered()), this, SLOT(onOpenAnimations()));
88 
89  myOpenFormulae = new QAction(tr("Formulae"), this);
90  myOpenFormulae->setStatusTip(tr("List all defined alchemy recipes."));
91  connect(myOpenFormulae, SIGNAL(triggered()), this, SLOT(onOpenFormulae()));
92 
93  myOpenResources = new QAction(tr("Resources"), this);
94  myOpenResources->setStatusTip(tr("List all defined elements, except experience table."));
95  connect(myOpenResources, SIGNAL(triggered()), this, SLOT(onOpenResources()));
96 
97  myOpenFaces = new QAction(tr("Faces"), this);
98  myOpenFaces->setStatusTip(tr("List all defined faces."));
99  connect(myOpenFaces, SIGNAL(triggered()), this, SLOT(onOpenFaces()));
100 
101  myOpenMaps = new QAction(tr("Maps"), this);
102  myOpenMaps->setStatusTip(tr("List all maps, with their region."));
103  connect(myOpenMaps, SIGNAL(triggered()), this, SLOT(onOpenMaps()));
104 
105  myOpenQuests = new QAction(tr("Quests"), this);
106  myOpenQuests->setStatusTip(tr("List all defined quests."));
107  connect(myOpenQuests, SIGNAL(triggered()), this, SLOT(onOpenQuests()));
108 
109  myOpenMessages = new QAction(tr("NPC dialogs"), this);
110  myOpenMessages->setStatusTip(tr("List all NPC dialogs in files."));
111  connect(myOpenMessages, SIGNAL(triggered()), this, SLOT(onOpenMessages()));
112 
113  myOpenExperience = new QAction(tr("Experience"), this);
114  myOpenExperience->setStatusTip(tr("Display the experience table."));
115  connect(myOpenExperience, SIGNAL(triggered()), this, SLOT(onOpenExperience()));
116 
117  myOpenScripts = new QAction(tr("Scripts"), this);
118  myOpenExperience->setStatusTip(tr("List all scripts references in maps."));
119  connect(myOpenScripts, SIGNAL(triggered()), this, SLOT(onOpenScripts()));
120 
121  myOpenRandomMaps = new QAction(tr("Random maps"), this);
122  myOpenRandomMaps->setStatusTip(tr("List all random maps."));
123  connect(myOpenRandomMaps, SIGNAL(triggered()), this, SLOT(onOpenRandomMaps()));
124 
125  myOpenGeneralMessages = new QAction(tr("Messages"), this);
126  myOpenGeneralMessages->setStatusTip(tr("Display all general messages."));
127  connect(myOpenGeneralMessages, SIGNAL(triggered()), this, SLOT(onOpenGeneralMessages()));
128 
129  mySaveFormulae = new QAction(tr("Formulae"), this);
130  mySaveFormulae->setEnabled(false);
131  connect(mySaveFormulae, SIGNAL(triggered()), this, SLOT(onSaveFormulae()));
132 
133  mySaveQuests = new QAction(tr("Quests"), this);
134  mySaveQuests->setStatusTip(tr("Save all modified quests to disk."));
135  connect(mySaveQuests, SIGNAL(triggered()), this, SLOT(onSaveQuests()));
136 
137  mySaveMessages = new QAction(tr("Dialogs"), this);
138  mySaveMessages->setStatusTip(tr("Save all modified NPC dialogs."));
139  connect(mySaveMessages, SIGNAL(triggered()), this, SLOT(onSaveMessages()));
140 
141  myReportDuplicate = new QAction(tr("Faces and animations report"), this);
142  myReportDuplicate->setStatusTip(tr("Show faces and animations which are used by multiple archetypes, or not used."));
143  connect(myReportDuplicate, SIGNAL(triggered()), this, SLOT(onReportDuplicate()));
144 
145  myReportSpellDamage = new QAction(tr("Spell damage"), this);
146  myReportSpellDamage->setStatusTip(tr("Display spell damage by level (bullet spells only for now)"));
147  connect(myReportSpellDamage, SIGNAL(triggered()), this, SLOT(onReportSpellDamage()));
148 
149  myReportAlchemy = new QAction(tr("Alchemy"), this);
150  myReportAlchemy->setStatusTip(tr("Display alchemy formulae, in a table."));
151  connect(myReportAlchemy, SIGNAL(triggered()), this, SLOT(onReportAlchemy()));
152 
153  myReportAlchemyGraph = new QAction(tr("Alchemy graph"), this);
154  myReportAlchemyGraph->setStatusTip(tr("Export alchemy relationship as a DOT file."));
155  connect(myReportAlchemyGraph, SIGNAL(triggered()), this, SLOT(onReportAlchemyGraph()));
156 
157  myReportSpells = new QAction(tr("Spells"), this);
158  myReportSpells->setStatusTip(tr("Display all spells, in a table."));
159  connect(myReportSpells, SIGNAL(triggered()), this, SLOT(onReportSpells()));
160 
161  myReportPlayer = new QAction(tr("Player vs monsters"), this);
162  myReportPlayer->setStatusTip(tr("Compute statistics related to player vs monster combat."));
163  // can't use that while map browsing is running ; will be enabled in browsingFinished()
164  myReportPlayer->setEnabled(false);
165  connect(myReportPlayer, SIGNAL(triggered()), this, SLOT(onReportPlayer()));
166 
167  myReportSummon = new QAction(tr("Summoned pets statistics"), this);
168  myReportSummon->setStatusTip(tr("Display wc, hp, speed and other statistics for summoned pets."));
169  connect(myReportSummon, SIGNAL(triggered()), this, SLOT(onReportSummon()));
170 
171  myReportShops = new QAction(tr("Shop specialization"), this);
172  myReportShops->setStatusTip(tr("Display the list of shops and their specialization for items."));
173  // can't use that while map browsing is running ; will be enabled in browsingFinished()
174  myReportShops->setEnabled(false);
175  connect(myReportShops, SIGNAL(triggered()), this, SLOT(onReportShops()));
176 
177  myReportQuests = new QAction(tr("Quest solved by players"), this);
178  myReportQuests->setStatusTip(tr("Display quests the players have solved."));
179  // can't use that while map browsing is running ; will be enabled in browsingFinished()
180  myReportQuests->setEnabled(false);
181  connect(myReportQuests, SIGNAL(triggered()), this, SLOT(onReportQuests()));
182 
183  myReportMaterials = new QAction(tr("Materials"), this);
184  myReportMaterials->setStatusTip(tr("Display all materials with their properties."));
185  connect(myReportMaterials, SIGNAL(triggered()), this, SLOT(onReportMaterials()));
186 
187  myReportArchetypes = new QAction(tr("Unused archetypes"), this);
188  myReportArchetypes->setStatusTip(tr("Display all archetypes which seem unused."));
189  myReportArchetypes->setEnabled(false);
190  connect(myReportArchetypes, SIGNAL(triggered()), this, SLOT(onReportArchetypes()));
191 
192  myToolEditMonsters = new QAction(tr("Edit monsters"), this);
193  myToolEditMonsters->setStatusTip(tr("Edit monsters in a table."));
194  connect(myToolEditMonsters, SIGNAL(triggered()), this, SLOT(onToolEditMonsters()));
195 
196  myToolSmooth = new QAction(tr("Generate smooth face base"), this);
197  myToolSmooth->setStatusTip(tr("Generate the basic smoothed picture for a face."));
198  connect(myToolSmooth, SIGNAL(triggered()), this, SLOT(onToolSmooth()));
199 
200  myToolHPBar = new QAction(tr("Generate HP bar"), this);
201  myToolHPBar->setStatusTip(tr("Generate faces for a HP bar."));
202  connect(myToolHPBar, SIGNAL(triggered()), this, SLOT(onToolBarMaker()));
203 
204  myToolCombatSimulator = new QAction(tr("Combat simulator"), this);
205  myToolCombatSimulator->setStatusTip(tr("Simulate fighting between two objects."));
206  connect(myToolCombatSimulator, SIGNAL(triggered()), this, SLOT(onToolCombatSimulator()));
207 
208  myToolFaceMaker = new QAction(tr("Generate face variants"), this);
209  myToolFaceMaker->setStatusTip(tr("Generate faces by changing colors of existing faces."));
210  connect(myToolFaceMaker, SIGNAL(triggered()), this, SLOT(onToolFaceMaker()));
211 
212  myClearMapCache = new QAction(tr("Clear map cache"), this);
213  myClearMapCache->setStatusTip(tr("Force a refresh of all map information at next start."));
214  connect(myClearMapCache, SIGNAL(triggered()), this, SLOT(onClearCache()));
215  /* can't clear map cache while collecting information */
216  myClearMapCache->setEnabled(false);
217 }
218 
220 {
221  myOpenMenu = menuBar()->addMenu(tr("&Open"));
222  myOpenMenu->addAction(myOpenResources);
223  myOpenMenu->addAction(myOpenArtifacts);
224  myOpenMenu->addAction(myOpenArchetypes);
225  myOpenMenu->addAction(myOpenTreasures);
226  myOpenMenu->addAction(myOpenAnimations);
227  myOpenMenu->addAction(myOpenFormulae);
228  myOpenMenu->addAction(myOpenFaces);
229  myOpenMenu->addAction(myOpenMaps);
230  myOpenMenu->addAction(myOpenQuests);
231  myOpenMenu->addAction(myOpenMessages);
232  myOpenMenu->addAction(myOpenExperience);
233  myOpenMenu->addAction(myOpenScripts);
234  myOpenMenu->addAction(myOpenRandomMaps);
235  myOpenMenu->addAction(myOpenGeneralMessages);
236  myOpenMenu->addSeparator();
237  QAction* exit = myOpenMenu->addAction(tr("&Exit"));
238  exit->setStatusTip(tr("Close the application."));
239  connect(exit, SIGNAL(triggered()), this, SLOT(close()));
240 
241  mySaveMenu = menuBar()->addMenu(tr("&Save"));
242  mySaveMenu->addAction(mySaveFormulae);
243  mySaveMenu->addAction(mySaveQuests);
244  mySaveMenu->addAction(mySaveMessages);
245 
246  QMenu* reportMenu = menuBar()->addMenu("&Reports");
247  reportMenu->addAction(myReportDuplicate);
248  reportMenu->addAction(myReportSpellDamage);
249  reportMenu->addAction(myReportAlchemy);
250  reportMenu->addAction(myReportAlchemyGraph);
251  reportMenu->addAction(myReportSpells);
252  reportMenu->addAction(myReportPlayer);
253  reportMenu->addAction(myReportSummon);
254  reportMenu->addAction(myReportShops);
255  reportMenu->addAction(myReportQuests);
256  reportMenu->addAction(myReportMaterials);
257  reportMenu->addAction(myReportArchetypes);
258 
259  QMenu* toolsMenu = menuBar()->addMenu("&Tools");
260  toolsMenu->addAction(myToolEditMonsters);
261  toolsMenu->addAction(myToolSmooth);
262  toolsMenu->addAction(myToolHPBar);
263  toolsMenu->addAction(myToolCombatSimulator);
264  toolsMenu->addAction(myToolFaceMaker);
265  toolsMenu->addAction(myClearMapCache);
266 }
267 
269 {
271  connect(this, SIGNAL(updateFilters()), resources, SLOT(updateFilters()));
272  connect(resources, SIGNAL(filtersModified()), this, SLOT(onFiltersModified()));
273  connect(this, SIGNAL(updateReports()), resources, SLOT(updateReports()));
274  connect(resources, SIGNAL(reportsModified()), this, SLOT(onReportsModified()));
275  connect(this, SIGNAL(commitData()), resources, SLOT(commitData()));
276  myArea->addSubWindow(resources);
277  resources->show();
278 }
279 
281 {
283 }
284 
286 {
288 }
289 
291 {
293 }
294 
296 {
298 }
299 
301 {
303 }
304 
306 {
308 }
309 
311 {
313 }
314 
316 {
318 }
319 
321 {
323 }
324 
326 {
328 }
329 
331 {
332  QWidget* experience = new CREExperienceWindow();
333  myArea->addSubWindow(experience);
334  experience->show();
335 }
336 
338 {
340 }
341 
343 {
345 }
346 
348 {
350 }
351 
353 {
354 }
355 
357 {
358  emit commitData();
360 }
361 
363 {
364  emit commitData();
366 }
367 
368 void CREMainWindow::browsingMap(const QString& path)
369 {
370  myMapBrowseStatus->setText(tr("Browsing map %1").arg(path));
371 }
372 
374 {
375  statusBar()->showMessage(tr("Finished browsing maps."), 5000);
376  myMapBrowseStatus->setVisible(false);
377  myReportPlayer->setEnabled(true);
378  myReportShops->setEnabled(true);
379  myReportQuests->setEnabled(true);
380  myReportArchetypes->setEnabled(true);
381  myClearMapCache->setEnabled(true);
382 }
383 
385 {
386  emit updateFilters();
387 }
388 
390 {
391  emit updateReports();
392 }
393 
401 {
402  QHash<QString, QStringList> faces, anims;
403 
404  // browse all archetypes
405  getManager()->archetypes()->each([&faces, &anims] (const auto arch)
406  {
407  if (arch->head)
408  {
409  return;
410  }
411  // if there is an animation, don't consider the face, since it's part of the animation anyway (hopefully, see lower for report on that)
412  if (arch->clone.animation == NULL)
413  {
414  if (arch->clone.face) {
415  faces[QString::fromLatin1(arch->clone.face->name)].append(QString(arch->name) + " (arch)");
416  sstring key = object_get_value(&arch->clone, "identified_face");
417  if (key)
418  {
419  faces[QString(key)].append(QString(arch->name) + " (arch)");
420  }
421  }
422  }
423  else
424  {
425  anims[arch->clone.animation->name].append(QString(arch->name) + " (arch)");
426  sstring key = object_get_value(&arch->clone, "identified_animation");
427  if (key)
428  {
429  anims[QString(key)].append(QString(arch->name) + " (arch)");
430  }
431  }
432  });
433 
434  // list faces in animations
435  getManager()->animations()->each([&faces] (const auto anim)
436  {
437  QStringList done;
438  for (int i = 0; i < anim->num_animations; i++)
439  {
440  // don't list animation twice if they use the same face
441  if (!done.contains(QString::fromLatin1(anim->faces[i]->name)))
442  {
443  faces[QString::fromLatin1(anim->faces[i]->name)].append(QString(anim->name) + " (animation)");
444  done.append(QString::fromLatin1(anim->faces[i]->name));
445  }
446  }
447  });
448 
449  // list faces and animations for artifacts
450  artifactlist* list;
451  artifact* art;
452  for (list = first_artifactlist; list != NULL; list = list->next)
453  {
454  for (art = list->items; art != NULL; art = art->next)
455  {
456  if (art->item->animation == 0)
457  {
458  if (art->item->face) {
459  faces[QString::fromLatin1(art->item->face->name)].append(QString(art->item->name) + " (art)");
460  sstring key = object_get_value(art->item, "identified_face");
461  if (key)
462  {
463  faces[QString(key)].append(QString(art->item->name) + " (art)");
464  }
465  }
466  }
467  else
468  {
469  anims[art->item->animation->name].append(QString(art->item->name) + " (art)");
470  sstring key = object_get_value(art->item, "identified_animation");
471  if (key)
472  {
473  anims[QString(key)].append(QString(art->item->name) + " (arch)");
474  }
475  }
476  }
477  }
478 
479  QString report("<p><strong>Warning:</strong> this list doesn't take into account faces for all artifacts, especially the 'animation_suffix' ones. Also, faces and archetypes defined in maps will not be taken into account in this list.</p><h1>Faces used multiple times:</h1><ul>");
480 
481  QStringList keys = faces.keys();
482  keys.sort();
483  foreach(QString name, keys)
484  {
485  if (faces[name].size() <= 1 || name.compare("blank.111") == 0)
486  continue;
487 
488  faces[name].sort();
489  report += "<li>" + name + ": ";
490  report += faces[name].join(", ");
491  report += "</li>";
492  }
493 
494  report += "</ul>";
495 
496  report += "<h1>Unused faces:</h1><ul>";
497  getManager()->faces()->each([&faces, &report] (const auto face)
498  {
499  if (faces[face->name].size() > 0)
500  return;
501  report += QString("<li>") + face->name + "</li>";
502  });
503  report += "</ul>";
504 
505  report += "<h1>Animations used multiple times:</h1><ul>";
506  keys = anims.keys();
507  keys.sort();
508  foreach(QString name, keys)
509  {
510  if (anims[name].size() <= 1)
511  continue;
512 
513  anims[name].sort();
514  report += "<li>" + name + ": ";
515  report += anims[name].join(", ");
516  report += "</li>";
517  }
518  report += "</ul>";
519 
520  report += "<h1>Unused animations:</h1><ul>";
521  getManager()->animations()->each([&anims, &report] (const auto anim)
522  {
523  if (anims[anim->name].size() > 0 || !strcmp(anim->name, "###none"))
524  return;
525  report += QString("<li>") + anim->name + "</li>";
526  });
527  report += "</ul>";
528 
529  // Find faces used for an object having an animation not including this face
530  report += "<h1>Objects having a face not part of their animation:</h1><ul>";
531 
532  getManager()->archetypes()->each([&report] (archetype *arch) {
533  // if there is an animation, don't consider the face, since it's part of the animation anyway (hopefully)
534  if (arch->clone.animation == NULL || arch->clone.face == NULL) {
535  return;
536  }
537  bool included = false;
538  for (int f = 0; f < arch->clone.animation->num_animations && !included; f++) {
539  if (arch->clone.animation->faces[f] == arch->clone.face) {
540  included = true;
541  }
542  }
543  if (!included) {
544  report += QString("<li>%1 (%2) has face %3 not in animation %4</li>\n").arg(arch->name, arch->clone.name, arch->clone.face->name, arch->clone.animation->name);
545  }
546  });
547 
548  CREReportDisplay show(report, "Faces and animations report");
549  show.exec();
550 }
551 
553 {
554  QStringList spell;
555  QList<QStringList> damage;
556 
557  archetype* arch = get_next_archetype(NULL);
558  object* caster = create_archetype("orc");
559  int dm, cost;
560 
561  while (arch != NULL)
562  {
563  if (arch->clone.type == SPELL && arch->clone.subtype == SP_BULLET && arch->clone.skill && strcmp(arch->clone.skill, "praying") == 0)
564  {
565  spell.append(arch->clone.name);
566  QStringList dam;
567  for (int l = 0; l < settings.max_level; l++)
568  {
569  caster->level = l;
570  dm = arch->clone.stats.dam + SP_level_dam_adjust(caster, &arch->clone);
571  cost = SP_level_spellpoint_cost(caster, &arch->clone, SPELL_GRACE);
572  dam.append(tr("%1 [%2]").arg(dm).arg(cost));
573  }
574  damage.append(dam);
575  }
576 
577  arch = get_next_archetype(arch);
578  }
580 
581  QString report("<table><thead><tr><th>level</th>");
582 
583  for (int i = 0; i < spell.size(); i++)
584  {
585  report += "<th>" + spell[i] + "</th>";
586  }
587 
588  report += "</tr></thead><tbody>";
589 
590  for (int l = 0; l < settings.max_level; l++)
591  {
592  report += "<tr><td>" + QString::number(l) + "</td>";
593  for (int s = 0; s < spell.size(); s++)
594  report += "<td>" + damage[s][l] + "</td>";
595  report += "</tr>";
596  }
597 
598  report += "</tbody></table>";
599 
600  CREReportDisplay show(report, "Spell damage");
601  show.exec();
602 }
603 
604 static QString alchemyTable(const QString& skill, QStringList& noChance, QStringList& allIngredients)
605 {
606  int count = 0;
607 
608  QHash<int, QStringList> recipes;
609 
610  const recipelist* list;
611  const recipe* recipe;
612 
613  for (int ing = 1; ; ing++)
614  {
615  list = get_formulalist(ing);
616  if (!list)
617  break;
618 
619  for (recipe = list->items; recipe; recipe = recipe->next)
620  {
621  if (skill == recipe->skill)
622  {
623  if (recipe->arch_names == 0)
624  // hu?
625  continue;
626 
627  archetype* arch = find_archetype(recipe->arch_name[0]);
628  if (arch == NULL) {
629  continue;
630  }
631 
632  QString name;
633  if (strcmp(recipe->title, "NONE") == 0)
634  {
635  if (arch->clone.title == NULL)
636  name = arch->clone.name;
637  else
638  name = QString("%1 %2").arg(arch->clone.name, arch->clone.title);
639  }
640  else
641  {
642  name = QString("%1 of %2").arg(arch->clone.name, recipe->title);
643  }
644 
645  QStringList ingredients;
646  for (const linked_char* ingred = recipe->ingred; ingred != NULL; ingred = ingred->next)
647  {
648  ingredients.append(ingred->name);
649  const char* name = ingred->name;
650  if (isdigit(ingred->name[0])) {
651  name = strchr(ingred->name, ' ') + 1;
652  }
653  if (!allIngredients.contains(name)) {
654  allIngredients.append(name);
655  }
656  }
657 
658  recipes[recipe->diff].append(QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>%4</td><td>%5</td></tr>").arg(name).arg(recipe->diff).arg(recipe->ingred_count).arg(recipe->exp).arg(ingredients.join(", ")));
659  count++;
660 
661  if (recipe->chance == 0) {
662  noChance.append(name);
663  }
664  }
665  }
666  }
667 
668  if (count == 0)
669  return QString();
670 
671  QString report = QString("<h2>%1 (%2 recipes)</h2><table><thead><tr><th>product</th><th>difficulty</th><th>ingredients count</th><th>experience</th><th>Ingredients</th>").arg(skill).arg(count);
672  report += "</tr></thead><tbody>";
673 
674  QList<int> difficulties = recipes.keys();
675  qSort(difficulties);
676  foreach(int difficulty, difficulties)
677  {
678  QStringList line = recipes[difficulty];
679  qSort(line);
680  report += line.join("\n");
681  }
682 
683  report += "</tbody></table>";
684 
685  return report;
686 }
687 
689 {
690  QStringList skills;
691 
692  archt* arch = get_next_archetype(NULL);
693  for (; arch; arch = get_next_archetype(arch))
694  {
695  if (arch->clone.type == SKILL)
696  skills.append(arch->clone.name);
697  }
698 
699  skills.sort();
700 
701  QString report("<h1>Alchemy formulae</h1>");
702  QStringList noChance, allIngredients;
703 
704  foreach(const QString skill, skills)
705  {
706  report += alchemyTable(skill, noChance, allIngredients);
707  }
708 
709  qSort(noChance);
710  report += tr("<h1>Formulae with chance of 0</h1>");
711  report += "<table><th>";
712  foreach(const QString& name, noChance) {
713  report += "<tr><td>" + name + "</td></tr>";
714  }
715  report += "</th></table>";
716 
717  qSort(allIngredients.begin(), allIngredients.end(), [] (const QString &s1, const QString &s2) {
718  return s1.toLower() < s2.toLower();
719  });
720  report += tr("<h1>All items used as ingredients</h1>");
721  report += "<ul>";
722  foreach(const QString& name, allIngredients) {
723  report += "<li>" + name + "</li>";
724  }
725  report += "</ul>";
726 
727  CREReportDisplay show(report, "Alchemy formulae");
728  show.exec();
729 }
730 
732 {
733  QString output = QFileDialog::getSaveFileName(this, tr("Destination file"), "", tr("Dot files (*.dot);;All files (*.*)"));
734  if (output.isEmpty()) {
735  return;
736  }
737 
738  QFile file(output);
739  if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
740  QMessageBox::critical(this, "Write error", tr("Unable to write to %1").arg(output));
741  return;
742  }
743  QTextStream out(&file);
744 
745  out << "digraph alchemy {\n";
746 
747  QVector<const recipe*> recipes;
748  QHash<const recipe*, QString> names;
749  QHash<QString, QVector<const recipe*> > products;
750 
751  for (int ing = 1; ; ing++)
752  {
753  const recipelist* list = get_formulalist(ing);
754  if (!list)
755  break;
756 
757  for (const recipe* recipe = list->items; recipe; recipe = recipe->next)
758  {
759  QString product("???");
760  for (size_t idx = 0; idx < recipe->arch_names; idx++) {
761  auto arch = getManager()->archetypes()->find(recipe->arch_name[idx]);
762  if (!arch) {
763  continue;
764  }
765  if (recipe->title && strcmp(recipe->title, "NONE")) {
766  product = tr("%1 of %2").arg(arch->clone.name, recipe->title);
767  } else {
768  product = arch->clone.name;
769  }
770  products[product].append(recipe);
771  }
772  names[recipe] = product;
773  recipes.append(recipe);
774  }
775  }
776 
777  QHash<const recipe*, bool> added;
778 
779  foreach (const recipe* rec, recipes) {
780  for (linked_char* ing = rec->ingred; ing; ing = ing->next) {
781  const char* name = ing->name;
782  if (isdigit(name[0]) && strchr(name, ' ')) {
783  name = strchr(name, ' ') + 1;
784  }
785  QHash<QString, QVector<const recipe*> >::iterator item = products.find(name);
786  if (item != products.end()) {
787  if (!added[rec]) {
788  out << tr("alchemy_%1 [label=\"%2\"]\n").arg(recipes.indexOf(rec)).arg(names[rec]);
789  added[rec] = true;
790  }
791  for (auto r = item->begin(); r != item->end(); r++) {
792  if (!added[*r]) {
793  out << tr("alchemy_%1 [label=\"%2\"]\n").arg(recipes.indexOf(*r)).arg(names[*r]);
794  added[*r] = true;
795  }
796  out << tr("alchemy_%1 -> alchemy_%2\n").arg(recipes.indexOf(*r)).arg(recipes.indexOf(rec));
797  }
798  }
799  }
800  }
801 
802  int ignored = 0;
803  foreach (const recipe* rec, recipes) {
804  if (!added[rec]) {
805  ignored++;
806  }
807  }
808  out << "graph [labelloc=\"b\" labeljust=\"c\" label=\"Alchemy graph, formulae producing ingredients of other formulae";
809  if (ignored) {
810  out << tr(" (%1 formulae not displayed)").arg(ignored);
811  }
812  out << "\"]\n";
813 
814  out << "}\n";
815 }
816 
817 static QString spellsTable(const QString& skill)
818 {
819  bool one = false;
820 
821  QString report = QString("<h2>%1</h2><table><thead><tr><th>Spell</th><th>Level</th>").arg(skill);
822  report += "</tr></thead><tbody>";
823 
824  QHash<int, QStringList> spells;
825 
826  archetype* spell;
827 
828  for (spell = get_next_archetype(NULL); spell; spell = get_next_archetype(spell))
829  {
830  if (spell->clone.type == SPELL && spell->clone.skill == skill)
831  {
832  spells[spell->clone.level].append(QString("<tr><td>%1</td><td>%2</td></tr>").arg(spell->clone.name).arg(spell->clone.level));
833  one = true;
834  }
835  }
836 
837  if (!one)
838  return QString();
839 
840  QList<int> levels = spells.keys();
841  qSort(levels);
842  foreach(int level, levels)
843  {
844  report += spells[level].join("\n");
845  }
846 
847  report += "</tbody></table>";
848 
849  return report;
850 }
851 
853 {
854  QStringList skills;
855 
856  archt* arch = get_next_archetype(NULL);
857  for (; arch; arch = get_next_archetype(arch))
858  {
859  if (arch->clone.type == SKILL)
860  skills.append(arch->clone.name);
861  }
862 
863  skills.sort();
864 
865  QString report("<h1>Spell list</h1>");
866 
867  foreach(const QString skill, skills)
868  {
869  report += spellsTable(skill);
870  }
871 
872  CREReportDisplay show(report, "Spell list");
873  show.exec();
874 }
875 
885 static int monsterFight(archetype* monster, archetype* skill, int level)
886 {
887  int limit = 50, result = 1;
888  player pl;
889  memset(&pl, 0, sizeof(player));
890  strncpy(pl.savebed_map, "/HallOfSelection", MAX_BUF);
891  pl.bed_x = 5;
892  pl.bed_y = 5;
893  pl.socket.faces_sent = (uint8_t*)calloc(sizeof(uint8_t), get_faces_count());
894 
895  archetype *dwarf_player_arch = find_archetype("dwarf_player");
896  if (dwarf_player_arch == NULL) {
897  free(pl.socket.faces_sent);
898  return 0;
899  }
900  object* obfirst = object_create_arch(dwarf_player_arch);
901  obfirst->level = level;
902  obfirst->contr = &pl;
903  pl.ob = obfirst;
904  object* obskill = object_create_arch(skill);
905  obskill->level = level;
906  SET_FLAG(obskill, FLAG_APPLIED);
907  object_insert_in_ob(obskill, obfirst);
908  archetype *skill_arch = find_archetype((skill->clone.subtype == SK_TWO_HANDED_WEAPON) ? "sword_3" : "sword");
909  if (skill_arch == NULL) {
910  free(pl.socket.faces_sent);
911  return 0;
912  }
913  object* sword = object_create_arch(skill_arch);
914  SET_FLAG(sword, FLAG_APPLIED);
915  object_insert_in_ob(sword, obfirst);
916  fix_object(obfirst);
917  obfirst->stats.hp = obfirst->stats.maxhp;
918 
919  object* obsecond = object_create_arch(monster);
920  tag_t tagfirst = obfirst->count;
921  tag_t tagsecond = obsecond->count;
922 
923  // make a big map so large monsters are ok in map
924  mapstruct* test_map = get_empty_map(50, 50);
925 
926  obfirst = object_insert_in_map_at(obfirst, test_map, NULL, 0, 0, 0);
927  obsecond = object_insert_in_map_at(obsecond, test_map, NULL, 0, 1 + monster->tail_x, monster->tail_y);
928 
929  if (!obsecond || object_was_destroyed(obsecond, tagsecond))
930  {
931  qDebug() << "second removed??";
932  }
933 
934  while (limit-- > 0 && obfirst->stats.hp >= 0 && obsecond->stats.hp >= 0)
935  {
936  if (obfirst->weapon_speed_left > 0) {
937  --obfirst->weapon_speed_left;
938  do_some_living(obfirst);
939 
940  move_player(obfirst, 3);
941  if (object_was_destroyed(obsecond, tagsecond))
942  break;
943 
944  /* the player may have been killed (acid for instance), so check here */
945  if (object_was_destroyed(obfirst, tagfirst) || (obfirst->map != test_map))
946  {
947  result = 0;
948  break;
949  }
950  }
951 
952  if (obsecond->speed_left > 0) {
953  --obsecond->speed_left;
954  monster_do_living(obsecond);
955 
956  attack_ob(obfirst, obsecond);
957  /* when player is killed, she is teleported to bed of reality -> check map */
958  if (object_was_destroyed(obfirst, tagfirst) || (obfirst->map != test_map))
959  {
960  result = 0;
961  break;
962  }
963  }
964 
965  obfirst->weapon_speed_left += obfirst->weapon_speed;
966  if (obfirst->weapon_speed_left > 1.0)
967  obfirst->weapon_speed_left = 1.0;
968  if (obsecond->speed_left <= 0)
969  obsecond->speed_left += FABS(obsecond->speed);
970  }
971 
972  free(pl.socket.faces_sent);
973  if (!object_was_destroyed(obfirst, tagfirst))
974  {
975  object_remove(obfirst);
976  object_free(obfirst, FREE_OBJ_FREE_INVENTORY);
977  }
978  if (!object_was_destroyed(obsecond, tagsecond))
979  {
980  object_remove(obsecond);
981  object_free(obsecond, FREE_OBJ_FREE_INVENTORY);
982  }
983  delete_map(test_map);
984 
985  return result;
986 }
987 
998 static int monsterFight(archetype* monster, archetype* skill, int level, int count)
999 {
1000  int victory = 0;
1001  while (count-- > 0)
1002  victory += monsterFight(monster, skill, level);
1003 
1004  return victory;
1005 }
1006 
1016 static QString monsterFight(archetype* monster, archetype* skill)
1017 {
1018  qDebug() << "monsterFight:" << monster->clone.name << skill->clone.name;
1019  int ret, min = settings.max_level + 1, half = settings.max_level + 1, count = 5, level;
1020  int first = 1, max = settings.max_level;
1021 
1022  while (first != max)
1023  {
1024  level = (max + first) / 2;
1025  if (level < first)
1026  level = first;
1027  if (first > max)
1028  first = max;
1029 
1030  ret = monsterFight(monster, skill, level, count);
1031  if (ret > 0)
1032  {
1033  if (level < min)
1034  min = level;
1035  if (ret > (count / 2) && (level < half))
1036  half = level;
1037 
1038  max = level;
1039  }
1040  else
1041  {
1042  if (first == level)
1043  break;
1044  first = level;
1045  }
1046  }
1047 
1048  //qDebug() << " result:" << min << half;
1049 
1050  // if player was killed, then HallOfSelection was loaded, so clean it now.
1051  // This speeds up various checks, like in free_all_objects().
1052  mapstruct* hos = has_been_loaded("/HallOfSelection");
1053  if (hos)
1054  {
1055  hos->reset_time = 1;
1056  hos->in_memory = MAP_IN_MEMORY;
1057  delete_map(hos);
1058  }
1059  /*
1060  extern int nroffreeobjects;
1061  extern int nrofallocobjects;
1062  qDebug() << "free: " << nroffreeobjects << ", all: " << nrofallocobjects;
1063  */
1064 
1065  if (min == settings.max_level + 1)
1066  return "<td colspan=\"2\">-</td>";
1067  return "<td>" + QString::number(min) + "</td><td>" + ((half != 0) ? QString::number(half) : "") + "</td>";
1068 }
1069 
1076 static QString monsterTable(archetype* monster, QList<archetype*> skills)
1077 {
1078  QString line = "<tr>";
1079 
1080  line += "<td>" + QString(monster->clone.name) + "</td>";
1081  line += "<td>" + QString::number(monster->clone.level) + "</td>";
1082  line += "<td>" + QString::number(monster->clone.speed) + "</td>";
1083  line += "<td>" + QString::number(monster->clone.stats.wc) + "</td>";
1084  line += "<td>" + QString::number(monster->clone.stats.dam) + "</td>";
1085  line += "<td>" + QString::number(monster->clone.stats.ac) + "</td>";
1086  line += "<td>" + QString::number(monster->clone.stats.hp) + "</td>";
1087  line += "<td>" + QString::number(monster->clone.stats.Con) + "</td>";
1088 
1089  foreach(archetype* skill, skills)
1090  {
1091  line += monsterFight(monster, skill);
1092  }
1093  line += "</tr>\n";
1094 
1095  return line;
1096 }
1097 
1103 {
1104  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1105 
1106  QStringList names;
1107  QMap<QString, archetype*> monsters;
1108  QList<archetype*> skills;
1109 
1110  archt* arch = get_next_archetype(NULL);
1111  for (; arch; arch = get_next_archetype(arch))
1112  {
1113  if (QUERY_FLAG(&arch->clone, FLAG_MONSTER) && arch->clone.stats.hp > 0 && arch->head == NULL)
1114  {
1115  QString name(QString(arch->clone.name).toLower());
1116  if (monsters.contains(name))
1117  {
1118  int suffix = 1;
1119  do
1120  {
1121  name = QString(arch->clone.name).toLower() + "_" + QString::number(suffix);
1122  suffix++;
1123  } while (monsters.contains(name));
1124  }
1125 
1126  monsters[name] = arch;
1127  }
1128  if (arch->clone.type == SKILL && IS_COMBAT_SKILL(arch->clone.subtype))
1129  {
1130  if (strcmp(arch->name, "skill_missile_weapon") == 0 || strcmp(arch->name, "skill_throwing") == 0)
1131  continue;
1132  skills.append(arch);
1133  }
1134  }
1135 
1136  names = monsters.keys();
1137  names.sort();
1138 
1139  QString report(tr("<h1>Player vs monsters</h1><p><strong>fv</strong> is the level at which the first victory happened, <strong>hv</strong> is the level at which at least 50% of fights were victorious.</p>\n")), line;
1140  report += "<table border=\"1\"><tbody>\n";
1141  report += "<tr>";
1142  report += "<th rowspan=\"2\">Monster</th>";
1143  report += "<th rowspan=\"2\">level</th>";
1144  report += "<th rowspan=\"2\">speed</th>";
1145  report += "<th rowspan=\"2\">wc</th>";
1146  report += "<th rowspan=\"2\">dam</th>";
1147  report += "<th rowspan=\"2\">ac</th>";
1148  report += "<th rowspan=\"2\">hp</th>";
1149  report += "<th rowspan=\"2\">regen</th>";
1150 
1151  line = "<tr>";
1152  foreach(archetype* skill, skills)
1153  {
1154  report += "<th colspan=\"2\">" + QString(skill->clone.name) + "</th>";
1155  line += "<th>fv</th><th>hv</th>";
1156  }
1157  report += "</tr>\n" + line + "</tr>\n";
1158 
1159  int limit = 500;
1160  foreach(const QString name, names)
1161  {
1162  report += monsterTable(monsters[name], skills);
1163  if (limit-- <= 0)
1164  break;
1165  }
1166 
1167  report += "</tbody></table>\n";
1168 
1169  CREReportDisplay show(report, "Player vs monsters (hand to hand)");
1170  QApplication::restoreOverrideCursor();
1171  show.exec();
1172 }
1173 
1174 static QString reportSummon(const archetype* summon, const object* other, QString name)
1175 {
1176  QString report;
1177  int level, wc_adj = 0;
1178 
1179  const object* spell = &summon->clone;
1180  sstring rate = object_get_value(spell, "wc_increase_rate");
1181  if (rate != NULL) {
1182  wc_adj = atoi(rate);
1183  }
1184 
1185  // hp, dam, speed, wc
1186 
1187  QString ac("<tr><td>ac</td>");
1188  QString hp("<tr><td>hp</td>");
1189  QString dam("<tr><td>dam</td>");
1190  QString speed("<tr><td>speed</td>");
1191  QString wc("<tr><td>wc</td>");
1192  int ihp, idam, iwc, diff;
1193  float fspeed;
1194 
1195  for (level = 1; level < 120; level += 10)
1196  {
1197  if (level < spell->level)
1198  {
1199  ac += "<td></td>";
1200  hp += "<td></td>";
1201  dam += "<td></td>";
1202  speed += "<td></td>";
1203  wc += "<td></td>";
1204  continue;
1205  }
1206 
1207  diff = level - spell->level;
1208 
1209  ihp = other->stats.hp + spell->duration + (spell->duration_modifier != 0 ? (diff / spell->duration_modifier) : 0);
1210  idam = (spell->stats.dam ? spell->stats.dam : other->stats.dam) + (spell->dam_modifier != 0 ? (diff / spell->dam_modifier) : 0);
1211  fspeed = MIN(1.0, FABS(other->speed) + .02 * (spell->range_modifier != 0 ? (diff / spell->range_modifier) : 0));
1212  iwc = other->stats.wc;
1213  if (wc_adj > 0)
1214  iwc -= (diff / wc_adj);
1215 
1216  ac += "<td>" + QString::number(other->stats.ac) + "</td>";
1217  hp += "<td>" + QString::number(ihp) + "</td>";
1218  dam += "<td>" + QString::number(idam) + "</td>";
1219  speed += "<td>" + QString::number(fspeed) + "</td>";
1220  wc += "<td>" + QString::number(iwc) + "</td>";
1221  }
1222 
1223  report += "<tr><td colspan=\"13\"><strong>" + name + "</strong></td></tr>\n";
1224 
1225  report += ac + "</tr>\n" + hp + "</tr>\n" + dam + "</tr>\n" + speed + "</tr>\n" + wc + "</tr>\n\n";
1226 
1227  return report;
1228 }
1229 
1231 {
1232  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1233 
1234  int level;
1235 
1236  QString report(tr("<h1>Summoned pet statistics</h1>\n")), line;
1237  report += "<table border=\"1\">\n<thead>\n";
1238  report += "<tr>";
1239  report += "<th rowspan=\"2\">Spell</th>";
1240  report += "<th colspan=\"12\">Level</th>";
1241  report += "</tr>\n";
1242  report += "<tr>";
1243 
1244  for (level = 1; level < 120; level += 10)
1245  {
1246  report += "<th>" + QString::number(level) + "</th>";
1247  }
1248  report += "</tr>\n</thead>\n<tbody>\n";
1249 
1250  QMap<QString, QString> spells;
1251 
1252  for (archetype* summon = get_next_archetype(NULL); summon; summon = get_next_archetype(summon))
1253  {
1254  if (summon->clone.type != SPELL || summon->clone.subtype != SP_SUMMON_GOLEM)
1255  continue;
1256  if (summon->clone.other_arch != NULL)
1257  {
1258  spells[summon->clone.name] = reportSummon(summon, &summon->clone.other_arch->clone, QString(summon->clone.name));
1259  continue;
1260  }
1261 
1262  // god-based summoning
1263  for (archetype* god = get_next_archetype(NULL); god; god = get_next_archetype(god))
1264  {
1265  if (god->clone.type != GOD)
1266  continue;
1267 
1268  QString name(QString(summon->clone.name) + " (" + QString(god->name) + ")");
1269  archetype* holy = determine_holy_arch(&god->clone, summon->clone.race);
1270  if (holy == NULL)
1271  continue;
1272 
1273  spells[name] = reportSummon(summon, &holy->clone, name);
1274  }
1275  }
1276 
1277  QStringList keys = spells.keys();
1278  keys.sort();
1279  foreach(QString key, keys)
1280  {
1281  report += spells[key];
1282  }
1283 
1284  report += "</tbody>\n</table>\n";
1285 
1286  CREReportDisplay show(report, "Summoned pet statistics");
1287  QApplication::restoreOverrideCursor();
1288  show.exec();
1289 }
1290 
1291 static QString buildShopReport(const QString& title, const QStringList& types, const QList<CREMapInformation*>& maps, QStringList& items)
1292 {
1293  QString report("<h2>" + title + "</h2>");
1294  report += "<table border=\"1\">\n<thead>\n";
1295  report += "<tr>";
1296  report += "<th>Shop</th>";
1297  report += "<th>Greed</th>";
1298  report += "<th>Race</th>";
1299  report += "<th>Min</th>";
1300  report += "<th>Max</th>";
1301  foreach (QString item, types)
1302  {
1303  report += "<th>" + item + "</th>";
1304  items.removeAll(item);
1305  }
1306  report += "</tr>\n</thead><tbody>";
1307 
1308  foreach(const CREMapInformation* map, maps)
1309  {
1310  QString line;
1311  bool keep = false;
1312 
1313  if (map->shopItems().size() == 0)
1314  continue;
1315 
1316  line += "<tr>";
1317 
1318  line += "<td>" + map->name() + " " + map->path() + "</td>";
1319  line += "<td>" + QString::number(map->shopGreed()) + "</td>";
1320  line += "<td>" + map->shopRace() + "</td>";
1321  line += "<td>" + (map->shopMin() != 0 ? QString::number(map->shopMin()) : "") + "</td>";
1322  line += "<td>" + (map->shopMax() != 0 ? QString::number(map->shopMax()) : "") + "</td>";
1323 
1324  foreach(const QString item, types)
1325  {
1326  if (map->shopItems()[item] == 0)
1327  {
1328  if (map->shopItems()["*"] == 0)
1329  line += "<td></td>";
1330  else
1331  line += "<td>" + QString::number(map->shopItems()["*"]) + "</td>";
1332  continue;
1333  }
1334  keep = true;
1335  line += "<td>" + QString::number(map->shopItems()[item]) + "</td>";
1336  }
1337 
1338  line += "</tr>";
1339  if (keep)
1340  report += line;
1341  }
1342 
1343  report += "</tbody></table>";
1344  return report;
1345 }
1346 
1348 {
1349  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1350 
1351  QString report(tr("<h1>Shop information</h1>\n"));
1352 
1353  QList<CREMapInformation*> maps = myMapManager->allMaps();
1354  QStringList items;
1355  foreach(const CREMapInformation* map, maps)
1356  {
1357  QStringList add = map->shopItems().keys();
1358  foreach(const QString item, add)
1359  {
1360  if (!items.contains(item))
1361  items.append(item);
1362  }
1363  }
1364  qSort(items);
1365 
1366  QStringList part;
1367 
1368  part << "weapon" << "weapon improver" << "bow" << "arrow";
1369  report += buildShopReport("Weapons", part, maps, items);
1370 
1371  part.clear();
1372  part << "armour" << "armour improver" << "boots" << "bracers" << "cloak" << "girdle" << "gloves" << "helmet" << "shield";
1373  report += buildShopReport("Armour", part, maps, items);
1374 
1375  part.clear();
1376  part << "amulet" << "potion" << "power_crystal" << "ring" << "rod" << "scroll" << "skillscroll" << "spellbook" << "wand";
1377  report += buildShopReport("Magical", part, maps, items);
1378 
1379  part.clear();
1380  part << "container" << "food" << "key" << "lamp" << "skill tool" << "special key";
1381  report += buildShopReport("Equipment", part, maps, items);
1382 
1383  if (!items.isEmpty())
1384  {
1385 
1386  part = items;
1387  report += buildShopReport("Others", part, maps, items);
1388  }
1389 
1390  CREReportDisplay show(report, "Shop information");
1391  QApplication::restoreOverrideCursor();
1392  show.exec();
1393 }
1394 
1395 void readDirectory(const QString& path, QHash<QString, QHash<QString, bool> >& states)
1396 {
1397  QDir dir(path);
1398  QStringList subdirs = dir.entryList(QStringList("*"), QDir::Dirs | QDir::NoDotAndDotDot);
1399  foreach(QString subdir, subdirs)
1400  {
1401  readDirectory(path + QDir::separator() + subdir, states);
1402  }
1403 
1404  QStringList quests = dir.entryList(QStringList("*.quest"), QDir::Files);
1405  foreach(QString file, quests)
1406  {
1407  qDebug() << "read quest:" << path << file;
1408  QString name = file.left(file.length() - 6);
1409  QFile read(path + QDir::separator() + file);
1410  read.open(QFile::ReadOnly);
1411  QTextStream stream(&read);
1412  QString line, code;
1413  bool completed;
1414  while (!(line = stream.readLine(0)).isNull())
1415  {
1416  if (line.startsWith("quest "))
1417  {
1418  code = line.mid(6);
1419  completed = false;
1420  continue;
1421  }
1422  if (code.isEmpty())
1423  continue;
1424  if (line == "end_quest")
1425  {
1426  states[code][name] = completed;
1427  code.clear();
1428  continue;
1429  }
1430  if (line.startsWith("state "))
1431  continue;
1432  if (line == "completed 1")
1433  completed = true;
1434  }
1435  }
1436 }
1437 
1439 {
1440  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1441 
1442  QHash<QString, QHash<QString, bool> > states;
1443  QString directory(settings.localdir);
1444  directory += QDir::separator();
1445  directory += settings.playerdir;
1446  readDirectory(directory, states);
1447 
1448  QStringList codes;
1449  foreach(const Quest* quest, myQuestManager->quests())
1450  {
1451  codes.append(quest->code());
1452  }
1453 
1454  QString report("<html><body>\n<h1>Quests</h1>\n");
1455 
1456  QStringList keys = states.keys();
1457  keys.sort();
1458 
1459  foreach(QString key, keys)
1460  {
1461  codes.removeAll(key);
1462  Quest* quest = myQuestManager->findByCode(key);
1463  report += "<h2>Quest: " + (quest != NULL ? quest->title() : (key + " ???")) + "</h2>\n";
1464  report += "<p>";
1465  QHash<QString, bool> done = states[key];
1466  QStringList players = done.keys();
1467  players.sort();
1468  int completed = 0;
1469  QString sep;
1470  foreach(QString player, players)
1471  {
1472  report += sep;
1473  sep = ", ";
1474  if (done[player])
1475  {
1476  completed++;
1477  report += "<strong>" + player + "</strong>";
1478  }
1479  else
1480  {
1481  report += player;
1482  }
1483  }
1484  report += "</p>\n";
1485  report += "<p>" + tr("%1 completed out of %2 (%3%)").arg(completed).arg(players.size()).arg(completed * 100 / players.size()) + "</p>\n";
1486  }
1487 
1488  if (codes.length() > 0)
1489  {
1490  codes.sort();
1491  QString sep;
1492  report += "<h2>Quests never done</h2>\n<p>\n";
1493  foreach(QString code, codes)
1494  {
1495  report += sep;
1496  sep = ", ";
1497  Quest* quest = myQuestManager->findByCode(code);
1498  report += (quest != NULL ? quest->title() : (code + " ???"));
1499  }
1500  report += "</p>\n";
1501  }
1502 
1503  report += "</body>\n</html>\n";
1504 
1505  CREReportDisplay show(report, "Quests report");
1506  QApplication::restoreOverrideCursor();
1507  show.exec();
1508 }
1509 
1511 {
1512  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1513 
1514  QString report;
1515  report += "<html>";
1516 
1517  report += "<h1>Materials</h1>";
1518  report += "<table><tr><th>Name</th><th>Description</th></tr>";
1519  auto mat = materialt;
1520  while (mat) {
1521  report += tr("<tr><td>%1</td><td>%2</td></tr>").arg(mat->name, mat->description);
1522  mat = mat->next;
1523  }
1524  report += "</table>";
1525 
1526  for (int s = 0; s < 2; s++) {
1527  QString name(s == 0 ? "Saves" : "Resistances");
1528  report += tr("<h1>%1</h1>").arg(name);
1529  report += tr("<tr><th rowspan='2'>Name</th><th colspan='%1'>%2</th></tr>").arg(NROFATTACKS).arg(name);
1530  report += "<tr>";
1531  for (int r = 0; r < NROFATTACKS; r++) {
1532  report += "<th>" + QString(attacktype_desc[r]) + "</th>";
1533  }
1534  report += "</tr>";
1535 
1536  mat = materialt;
1537  while (mat) {
1538  report += tr("<tr><td>%1</td>").arg(mat->name);
1539  for (int r = 0; r < NROFATTACKS; r++) {
1540  report += tr("<td>%1</td>").arg(s == 0 ? mat->save[r] : mat->mod[r]);
1541  }
1542  report += "</tr>";
1543  mat = mat->next;
1544  }
1545  report += "</table>";
1546  }
1547 
1548  report += "</html>";
1549 
1550  CREReportDisplay show(report, "Materials report");
1551  QApplication::restoreOverrideCursor();
1552  show.exec();
1553 }
1554 
1556 {
1557  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1558 
1559  QString report;
1560  report += "<html>";
1561 
1562  report += "<h1>Apparently unused archetypes</h1>";
1563  report += "<h3>Warning: this list contains skills, items on style maps, and other things which are actually used.</h3>";
1564  report += "<table>";
1565  report += "<tr><th>Image</th><th>Archetype name</th><th>Item name</th><th>Type</th></tr>";
1566 
1567  getManager()->archetypes()->each([this, &report] (const archt* arch)
1568  {
1569  if (arch->head || arch->clone.type == PLAYER || arch->clone.type == MAP || arch->clone.type == EVENT_CONNECTOR)
1570  return;
1571  if (strstr(arch->name, "hpbar") != nullptr)
1572  return;
1573 
1574  bool used = false;
1576  (ArchetypeUse, const archt*, const treasurelist*, const CREMapInformation*, const recipe*) -> bool
1577  {
1578  used = true;
1579  return false;
1580  });
1581 
1582  if (!used)
1583  {
1584  QImage image(CREPixmap::getIcon(arch->clone.face->number).pixmap(32,32).toImage());
1585  QByteArray byteArray;
1586  QBuffer buffer(&byteArray);
1587  image.save(&buffer, "PNG");
1588  QString iconBase64 = QString::fromLatin1(byteArray.toBase64().data());
1589  auto td = get_typedata(arch->clone.type);
1590  report += tr("<tr><td><img src='data:image/png;base64,%1'></td><td>%2</td><td>%3</td><td>%4</td></tr>")
1591  .arg(iconBase64, arch->name, arch->clone.name, td ? td->name : tr("unknown: %1").arg(arch->clone.type));
1592  }
1593  });
1594 
1595  report += "</table>";
1596  report += "</html>";
1597 
1598  CREReportDisplay show(report, "Unused archetypes report");
1599  QApplication::restoreOverrideCursor();
1600  show.exec();
1601 }
1602 
1604 {
1606  edit.exec();
1607 }
1608 
1610 {
1611  CRESmoothFaceMaker smooth;
1612  smooth.exec();
1613 }
1614 
1616 {
1617  CRECombatSimulator simulator;
1618  simulator.exec();
1619 }
1620 
1622 {
1623  CREHPBarMaker maker;
1624  maker.exec();
1625 }
1626 
1628 {
1629  FaceMakerDialog maker(this, myResourcesManager);
1630  maker.exec();
1631 }
1632 
1634 {
1635  QMessageBox confirm;
1636  confirm.setWindowTitle(tr("Crossfire Resource Editor"));
1637  confirm.setText("Really clear map cache?");
1638  confirm.setInformativeText("This will force cache rebuild at next application start.");
1639  confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1640  confirm.setDefaultButton(QMessageBox::No);
1641  confirm.setIcon(QMessageBox::Question);
1642  if (confirm.exec() == QMessageBox::Yes)
1643  {
1645  }
1646 }
QAction * myOpenScripts
Definition: CREMainWindow.h:54
Definition: player.h:92
quint64 shopMax() const
int diff
Definition: recipe.h:16
void object_free(object *ob, int flags)
Definition: object.c:1348
int move_player(object *op, int dir)
Definition: player.c:2913
void onOpenArchetypes()
QAction * myReportDuplicate
Definition: CREMainWindow.h:60
QList< CREMapInformation * > allMaps()
void onReportDuplicate()
QAction * myReportSpellDamage
Definition: CREMainWindow.h:61
void onOpenGeneralMessages()
mapstruct * get_empty_map(int sizex, int sizey)
Definition: map.c:870
#define SET_FLAG(xyz, p)
Definition: define.h:223
void updateFilters()
QMdiArea * myArea
Definition: CREMainWindow.h:35
QAction * myOpenAnimations
Definition: CREMainWindow.h:46
int16_t bed_x
Definition: player.h:98
EXTERN materialtype_t * materialt
Definition: material.h:43
#define FABS(x)
Definition: define.h:22
unsigned char uint8_t
Definition: win32.h:161
Definition: Quest.h:32
void do_some_living(object *op)
Definition: player.c:3237
void onFiltersModified()
AllAnimations * animations()
Definition: AssetsManager.h:48
int attack_ob(object *op, object *hitter)
Definition: attack.c:912
archetype * find_archetype(const char *name)
Definition: assets.cpp:253
struct artifactstruct * items
Definition: artifact.h:30
double shopGreed() const
#define SPELL_GRACE
Definition: spells.h:59
uint32_t in_memory
Definition: map.h:346
QMenu * myOpenMenu
Definition: CREMainWindow.h:40
const char * playerdir
Definition: global.h:247
int16_t max_level
Definition: global.h:302
const char * object_get_value(const object *op, const char *const key)
Definition: object.c:4136
QAction * myToolEditMonsters
Definition: CREMainWindow.h:71
struct pl player
QAction * myReportQuests
Definition: CREMainWindow.h:68
void onOpenExperience()
socket_struct socket
Definition: player.h:94
linked_char * ingred
Definition: recipe.h:22
uint32_t reset_time
Definition: map.h:333
int16_t SP_level_spellpoint_cost(object *caster, object *spell, int flags)
Definition: spell_util.c:275
QAction * myOpenFormulae
Definition: CREMainWindow.h:47
const QString & code() const
Definition: Quest.cpp:63
static QString reportSummon(const archetype *summon, const object *other, QString name)
void commitData()
const QString & shopRace() const
int SP_level_dam_adjust(const object *caster, const object *spob)
Definition: spell_util.c:326
void onOpenTreasures()
QAction * mySaveMessages
Definition: CREMainWindow.h:59
static QString buildShopReport(const QString &title, const QStringList &types, const QList< CREMapInformation *> &maps, QStringList &items)
static QString spellsTable(const QString &skill)
mapstruct * test_map
Definition: comet_perf.c:74
void onToolCombatSimulator()
void onReportsModified()
void loadQuests()
QAction * myToolHPBar
Definition: CREMainWindow.h:73
char savebed_map[MAX_BUF]
Definition: player.h:97
#define MIN(x, y)
Definition: compat.h:17
quint64 shopMin() const
void cleanup(void)
Definition: server.c:1153
QAction * myOpenTreasures
Definition: CREMainWindow.h:45
int ingred_count
Definition: recipe.h:23
void closeEvent(QCloseEvent *event)
void onReportArchetypes()
const typedata * get_typedata(int itemtype)
Definition: item.c:318
AssetsManager * getManager()
Definition: assets.cpp:304
void onOpenArtifacts()
const QString & title() const
Definition: Quest.cpp:76
QAction * myOpenRandomMaps
Definition: CREMainWindow.h:55
QAction * myToolSmooth
Definition: CREMainWindow.h:72
#define MAP_IN_MEMORY
Definition: map.h:131
int chance
Definition: recipe.h:14
void object_free_drop_inventory(object *ob)
Definition: object.c:1316
object * object_insert_in_map_at(object *op, mapstruct *m, object *originator, int flag, int x, int y)
Definition: object.c:1849
QAction * myOpenMessages
Definition: CREMainWindow.h:53
ResourcesManager * myResourcesManager
Definition: CREMainWindow.h:81
void doResourceWindow(DisplayMode mode)
Quest * findByCode(const QString &code)
#define SP_BULLET
Definition: spells.h:79
struct artifactliststruct * next
Definition: artifact.h:29
sstring title
Definition: recipe.h:11
Faces * faces()
Definition: AssetsManager.h:38
char ** arch_name
Definition: recipe.h:13
object * object_insert_in_ob(object *op, object *where)
Definition: object.c:2602
void onOpenAnimations()
QAction * myOpenArtifacts
Definition: CREMainWindow.h:43
MessageManager * myMessageManager
Definition: CREMainWindow.h:80
QLabel * myMapBrowseStatus
Definition: CREMainWindow.h:77
void onToolEditMonsters()
object * create_archetype(const char *name)
Definition: arch.cpp:281
QAction * myOpenGeneralMessages
Definition: CREMainWindow.h:56
static QString alchemyTable(const QString &skill, QStringList &noChance, QStringList &allIngredients)
void onReportAlchemy()
QAction * myReportShops
Definition: CREMainWindow.h:67
struct linked_char * next
Definition: global.h:88
ArchetypeUse
struct recipestruct recipe
QuestManager * myQuestManager
Definition: CREMainWindow.h:79
type_definition ** types
int16_t bed_y
Definition: player.h:98
QAction * myOpenResources
Definition: CREMainWindow.h:50
Archetypes * archetypes()
Definition: AssetsManager.h:43
QAction * myOpenExperience
Definition: CREMainWindow.h:51
QAction * myReportArchetypes
Definition: CREMainWindow.h:70
QAction * myOpenArchetypes
Definition: CREMainWindow.h:44
QAction * mySaveQuests
Definition: CREMainWindow.h:58
QAction * myToolFaceMaker
Definition: CREMainWindow.h:75
size_t get_faces_count()
Definition: assets.cpp:288
void onReportMaterials()
QAction * myClearMapCache
Definition: CREMainWindow.h:76
archetype * get_next_archetype(archetype *current)
Definition: assets.cpp:249
QAction * myReportSummon
Definition: CREMainWindow.h:66
static int monsterFight(archetype *monster, archetype *skill, int level)
int done
Definition: readable.c:1592
sstring skill
Definition: recipe.h:26
#define QUERY_FLAG(xyz, p)
Definition: define.h:225
QAction * myToolCombatSimulator
Definition: CREMainWindow.h:74
EXTERN artifactlist * first_artifactlist
Definition: global.h:118
EXTERN const char *const attacktype_desc[NROFATTACKS]
Definition: attack.h:138
#define MAX_BUF
Definition: define.h:35
static void archetypeUse(const archt *item, CREMapInformationManager *store, AssetUseCallback callback)
QAction * myReportAlchemy
Definition: CREMainWindow.h:62
QAction * myReportSpells
Definition: CREMainWindow.h:64
void updateReports()
QMenu * mySaveMenu
Definition: CREMainWindow.h:41
object * object_create_arch(archetype *at)
Definition: arch.cpp:301
CREMapInformationManager * myMapManager
Definition: CREMainWindow.h:78
object * ob
Definition: player.h:158
const char * sstring
Definition: global.h:40
int exp
Definition: recipe.h:17
QAction * myReportPlayer
Definition: CREMainWindow.h:65
QAction * myReportAlchemyGraph
Definition: CREMainWindow.h:63
void browsingMap(const QString &path)
static QIcon getIcon(int faceNumber)
Definition: CREPixmap.cpp:30
archetype * determine_holy_arch(const object *god, const char *type)
Definition: gods.c:740
const char * localdir
Definition: global.h:246
mapstruct * has_been_loaded(const char *name)
Definition: map.c:88
size_t arch_names
Definition: recipe.h:12
DisplayMode
struct Settings settings
Definition: init.c:39
QAction * myOpenMaps
Definition: CREMainWindow.h:49
#define FLAG_APPLIED
Definition: define.h:235
#define NROFATTACKS
Definition: attack.h:17
void browsingFinished()
struct recipestruct * next
Definition: recipe.h:24
void delete_map(mapstruct *m)
Definition: map.c:1735
QAction * myOpenQuests
Definition: CREMainWindow.h:52
int64_t * levels
Definition: exp.c:26
static quest_definition * quests
Definition: quest.c:97
T * find(const Key &name)
#define FLAG_MONSTER
Definition: define.h:245
void monster_do_living(object *op)
Definition: monster.c:564
ScriptFileManager * myScriptManager
Definition: CREMainWindow.h:82
struct _materialtype * next
Definition: material.h:39
QAction * myOpenFaces
Definition: CREMainWindow.h:48
QAction * myReportMaterials
Definition: CREMainWindow.h:69
object * item
Definition: artifact.h:15
#define IS_COMBAT_SKILL(num)
Definition: skills.h:91
Definition: map.h:326
void onReportAlchemyGraph()
void onOpenResources()
QList< const Quest * > quests() const
QAction * mySaveFormulae
Definition: CREMainWindow.h:57
static QString monsterTable(archetype *monster, QList< archetype *> skills)
void fix_object(object *op)
Definition: living.c:1124
#define SP_SUMMON_GOLEM
Definition: spells.h:86
uint8_t * faces_sent
Definition: newserver.h:96
int level
Definition: readable.c:1589
struct recipestruct * items
Definition: recipe.h:40
recipelist * get_formulalist(int i)
Definition: recipe.c:96
void object_remove(object *op)
Definition: object.c:1588
struct artifactstruct * next
Definition: artifact.h:18
QHash< QString, int > & shopItems()
void onOpenRandomMaps()
void onReportSpellDamage()
void each(std::function< void(T *)> op)
void createActions()
void readDirectory(const QString &path, QHash< QString, QHash< QString, bool > > &states)