38 #include "AssetsManager.h"
53 myArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
54 myArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
81 statusBar()->showMessage(tr(
"Ready"));
85 setWindowTitle(tr(
"Crossfire Resource Editor"));
90 addDockWidget(Qt::RightDockWidgetArea,
myChanges);
100 for (
int idx = 0; idx < count; idx++) {
114 if (QMessageBox::question(
this, tr(
"Discard changes?"), tr(
"You have unsaved changes, really discard them?")) != QMessageBox::Yes) {
122 auto windows =
myArea->subWindowList();
124 for (
int idx = 0; idx < windows.size(); idx++) {
126 auto widget = windows[idx]->widget();
128 if (crew !=
nullptr) {
139 myArea->closeAllSubWindows();
141 QMainWindow::closeEvent(event);
150 template <
typename F>
154 connect(action, &QAction::triggered, [
this, functor] {
156 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
157 QProgressDialog prog(this);
158 prog.setLabelText(tr(
"Waiting for maps browing to finish..."));
159 prog.setWindowModality(Qt::ApplicationModal);
161 prog.setVisible(true);
163 while (!myMapManager->browseFinished())
165 if (prog.wasCanceled()) {
166 QApplication::restoreOverrideCursor();
169 QApplication::processEvents();
172 QApplication::restoreOverrideCursor();
177 connect(action, &QAction::triggered, functor);
183 auto action =
new QAction(title,
this);
184 action->setStatusTip(statusTip);
195 myClearMapCache->setStatusTip(tr(
"Force a refresh of all map information at next start."));
210 auto add = [
this] (
int index,
const QString &name,
const QString &tip) {
211 QAction* action =
new QAction(name,
this);
212 action->setStatusTip(tip);
213 action->setData(
static_cast<int>(index));
214 connect(action, &QAction::triggered, [
this, index] () {
doResourceWindow(index); });
217 add(-1, tr(
"Assets"), tr(
"Display all defined assets, except the experience table."));
225 QAction* exit =
myOpenMenu->addAction(tr(
"&Exit"));
226 exit->setStatusTip(tr(
"Close the application."));
227 connect(exit, SIGNAL(triggered()),
this, SLOT(close()));
238 QMenu* reportMenu = menuBar()->addMenu(tr(
"&Reports"));
239 reportMenu->addAction(
createAction(tr(
"Faces and animations report"), tr(
"Show faces and animations which are used by multiple archetypes, or not used."), [
this] {
onReportDuplicate(); }));
244 reportMenu->addAction(
createAction(tr(
"Player vs monsters"), tr(
"Compute statistics related to player vs monster combat."), [=] {
onReportPlayer(); },
true));
245 reportMenu->addAction(
createAction(tr(
"Summoned pets statistics"), tr(
"Display wc, hp, speed and other statistics for summoned pets."), [=] {
onReportSummon(); }));
246 reportMenu->addAction(
createAction(tr(
"Shop specialization"), tr(
"Display the list of shops and their specialization for items."), [=] {
onReportShops(); },
true));
247 reportMenu->addAction(
createAction(tr(
"Quest solved by players"), tr(
"Display quests the players have solved."), [=] {
onReportQuests(); },
true));
249 reportMenu->addAction(
createAction(tr(
"Unused archetypes"), tr(
"Display all archetypes which seem unused."), [=] {
onReportArchetypes(); },
true));
265 myWindows = menuBar()->addMenu(tr(
"&Windows"));
267 auto store =
createAction(tr(
"Restore windows positions at launch"), tr(
"If enabled then opened windows are automatically opened again when the application starts"));
268 store->setCheckable(
true);
271 connect(store, &QAction::triggered, [store] {
276 myWindows->addAction(
createAction(tr(
"Close current window"), tr(
"Close the currently focused window"), [
this] {
myArea->closeActiveSubWindow(); }));
277 myWindows->addAction(
createAction(tr(
"Close all windows"), tr(
"Close all opened windows"), [=] {
myArea->closeAllSubWindows(); }));
281 auto sep =
new QAction(
this);
282 sep->setSeparator(
true);
285 auto helpMenu = menuBar()->addMenu(tr(
"&Help"));
287 help->setShortcut(Qt::Key_F1);
288 helpMenu->addAction(help);
290 helpMenu->addAction(
createAction(tr(
"About"), tr(
"About CRE"), [=] () { QMessageBox::about(
this, tr(
"About CRE"), tr(
"Crossfire Resource Editor")); }));
293 auto show =
createAction(tr(
"Show changes after updating"), tr(
"If checked, then show latest changes at first startup after an update"));
294 show->setCheckable(
true);
296 helpMenu->addAction(show);
297 connect(show, &QAction::triggered, [&] (
bool checked) {
302 helpMenu->addAction(
createAction(tr(
"Changes"), tr(
"Display CRE changes"), [=] () {
myChanges->setVisible(
true); }));
313 resources->setAttribute(Qt::WA_DeleteOnClose);
316 auto widget =
myArea->addSubWindow(resources);
317 widget->setAttribute(Qt::WA_DeleteOnClose);
318 if (position.isEmpty()) {
319 if (
myArea->subWindowList().size() == 1) {
320 widget->setWindowState(Qt::WindowMaximized);
323 widget->restoreGeometry(position);
331 auto widget =
myArea->addSubWindow(experience);
332 if (!position.isEmpty()) {
333 widget->restoreGeometry(position);
347 getManager()->facesets()->each([&fs, &select,
this] (face_sets *f)
349 QAction *a =
new QAction(f->fullname, fs);
350 a->setCheckable(
true);
351 a->setData(f->prefix);
354 if (select == f->prefix)
383 statusBar()->showMessage(tr(
"Finished browsing maps."), 5000);
401 QHash<QString, QStringList> faces, anims;
404 getManager()->archetypes()->each([&faces, &anims] (
const auto arch)
411 if (arch->clone.animation == NULL)
413 if (arch->clone.face) {
414 faces[QString::fromLatin1(arch->clone.face->name)].append(QString(arch->name) +
" (arch)");
415 sstring key = object_get_value(&arch->clone,
"identified_face");
418 faces[QString(key)].append(QString(arch->name) +
" (arch)");
424 anims[arch->clone.animation->name].append(QString(arch->name) +
" (arch)");
425 sstring key = object_get_value(&arch->clone,
"identified_animation");
428 anims[QString(key)].append(QString(arch->name) +
" (arch)");
434 getManager()->animations()->each([&faces] (
const auto anim)
437 for (
int i = 0; i < anim->num_animations; i++)
440 if (!done.contains(QString::fromLatin1(anim->faces[i]->name)))
442 faces[QString::fromLatin1(anim->faces[i]->name)].append(QString(anim->name) +
" (animation)");
443 done.append(QString::fromLatin1(anim->faces[i]->name));
450 for (list = first_artifactlist; list != NULL; list = list->next)
452 for (
auto art : list->items)
454 if (art->item->animation == 0)
456 if (art->item->face) {
457 faces[QString::fromLatin1(art->item->face->name)].append(QString(art->item->name) +
" (art)");
458 sstring key = object_get_value(art->item,
"identified_face");
461 faces[QString(key)].append(QString(art->item->name) +
" (art)");
467 anims[art->item->animation->name].append(QString(art->item->name) +
" (art)");
468 sstring key = object_get_value(art->item,
"identified_animation");
471 anims[QString(key)].append(QString(art->item->name) +
" (arch)");
477 getManager()->quests()->each([&] (
auto quest) {
478 if (quest->face !=
nullptr)
480 faces[quest->face->name].append(QString(quest->quest_code) +
" (quest)");
484 getManager()->messages()->each([&faces] (
const GeneralMessage *message)
486 if (message->face !=
nullptr)
488 faces[message->face->name].append(QString(message->identifier) +
" (message)");
492 for (
const auto map : myMapManager->allMaps())
494 for (
const auto face : map->faces())
496 faces[face].append(QString(map->path()) +
" (map)");
498 for (
const auto animation : map->animations())
500 anims[animation].append(map->path() +
" (map)");
504 QString report(
"<p><strong>Warning:</strong> this list doesn't take into account faces for all artifacts, especially the 'animation_suffix' ones.</p><h1>Faces used multiple times:</h1><ul>");
506 QStringList keys = faces.keys();
508 foreach(QString name, keys)
510 if (faces[name].size() <= 1 || name.compare(
"blank.111") == 0)
514 report +=
"<li>" + name +
": ";
515 report += faces[name].join(
", ");
521 report +=
"<h1>Unused faces:</h1><ul>";
522 getManager()->faces()->each([&faces, &report] (
const auto face)
524 if (faces[face->name].size() > 0)
526 report += QString(
"<li>") + face->name +
"</li>";
530 report +=
"<h1>Animations used multiple times:</h1><ul>";
533 foreach(QString name, keys)
535 if (anims[name].size() <= 1)
539 report +=
"<li>" + name +
": ";
540 report += anims[name].join(
", ");
545 report +=
"<h1>Unused animations:</h1><ul>";
546 getManager()->animations()->each([&anims, &report] (
const auto anim)
548 if (anims[anim->name].size() > 0 || !strcmp(anim->name,
"###none"))
550 report += QString(
"<li>") + anim->name +
"</li>";
555 report +=
"<h1>Objects having a face not part of their animation:</h1><ul>";
557 getManager()->archetypes()->each([&report] (archetype *arch) {
559 if (arch->clone.animation == NULL || arch->clone.face == NULL) {
562 bool included =
false;
563 for (
int f = 0; f < arch->clone.animation->num_animations && !included; f++) {
564 if (arch->clone.animation->faces[f] == arch->clone.face) {
569 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);
580 QList<QStringList> damage;
582 object* caster = create_archetype(
"orc");
584 getManager()->archetypes()->each([&caster, &spell, &damage] (archetype *arch)
586 if (arch->clone.type == SPELL && arch->clone.subtype == SP_BULLET)
588 spell.append(arch->clone.name);
590 for (int l = 0; l < settings.max_level; l++)
593 int dm = arch->clone.stats.dam + SP_level_dam_adjust(caster, &arch->clone);
594 int cost = SP_level_spellpoint_cost(caster, &arch->clone, SPELL_HIGHEST);
595 dam.append(tr(
"%1 [%2]").arg(dm).arg(cost));
601 object_free_drop_inventory(caster);
603 QString report(
"<table><thead><tr><th>level</th>");
605 for (
int i = 0; i < spell.size(); i++)
607 report +=
"<th>" + spell[i] +
"</th>";
610 report +=
"</tr></thead><tbody>";
612 for (
int l = 0; l < settings.max_level; l++)
614 report +=
"<tr><td>" + QString::number(l) +
"</td>";
615 for (
int s = 0; s < spell.size(); s++)
616 report +=
"<td>" + damage[s][l] +
"</td>";
620 report +=
"</tbody></table>";
626 static QString
alchemyTable(
const QString& skill, QStringList& noChance, std::vector<std::pair<QString, int>> &allIngredients)
630 QHash<int, QStringList> recipes;
632 const recipelist* list;
633 const recipe* recipe;
635 for (
int ing = 1; ; ing++)
637 list = get_formulalist(ing);
641 for (recipe = list->items; recipe; recipe = recipe->next)
643 if (skill == recipe->skill)
645 if (recipe->arch_names == 0)
649 archetype* arch = find_archetype(recipe->arch_name[0]);
655 if (strcmp(recipe->title,
"NONE") == 0)
657 if (arch->clone.title == NULL)
658 name = arch->clone.name;
660 name = QString(
"%1 %2").arg(arch->clone.name, arch->clone.title);
664 name = QString(
"%1 of %2").arg(arch->clone.name, recipe->title);
667 QStringList ingredients;
668 for (
const linked_char* ingred = recipe->ingred; ingred != NULL; ingred = ingred->next)
670 ingredients.append(ingred->name);
671 const char* name = ingred->name;
672 if (isdigit(ingred->name[0])) {
673 name = strchr(ingred->name,
' ') + 1;
675 auto ing = std::find_if(allIngredients.begin(), allIngredients.end(), [&] (
auto i) { return i.first == name; } );
676 if (ing != allIngredients.end()) {
679 allIngredients.push_back(std::make_pair(name, 1));;
683 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(
", ")));
686 if (recipe->chance == 0) {
687 noChance.append(name);
696 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);
697 report +=
"</tr></thead><tbody>";
699 QList<int> difficulties = recipes.keys();
701 foreach(
int difficulty, difficulties)
703 QStringList line = recipes[difficulty];
705 report += line.join(
"\n");
708 report +=
"</tbody></table>";
713 static void doIngredients(
const std::vector<std::pair<QString, int>> &allIngredients,
const QString &criteria, QString &report) {
714 report += QString(
"<h1>All items used as ingredients (%1)</h1>").arg(criteria);
716 for (
auto ing : allIngredients) {
717 report += QString(
"<li>%1 (%2 recipes)</li>").arg(ing.first).arg(ing.second);
726 getManager()->archetypes()->each([&skills] (
const auto arch)
728 if (arch->clone.type == SKILL)
729 skills.append(arch->clone.name);
733 QString report(
"<h1>Alchemy formulae</h1>");
734 QStringList noChance;
735 std::vector<std::pair<QString, int>> allIngredients;
737 foreach(
const QString skill, skills)
739 report +=
alchemyTable(skill, noChance, allIngredients);
743 report += tr(
"<h1>Formulae with chance of 0</h1>");
744 report +=
"<table><th>";
745 foreach(
const QString& name, noChance) {
746 report +=
"<tr><td>" + name +
"</td></tr>";
748 report +=
"</th></table>";
750 std::sort(allIngredients.begin(), allIngredients.end(), [] (
auto i1,
auto i2) {
751 return i1.first.toLower() < i2.first.toLower();
755 std::sort(allIngredients.begin(), allIngredients.end(), [] (
auto i1,
auto i2) {
756 if (i1.second == i2.second) {
757 return i1.first.toLower() < i2.first.toLower();
759 return i1.second > i2.second;
769 QString output = QFileDialog::getSaveFileName(
this, tr(
"Destination file"),
"", tr(
"Dot files (*.dot);;All files (*.*)"));
770 if (output.isEmpty()) {
775 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
776 QMessageBox::critical(
this,
"Write error", tr(
"Unable to write to %1").arg(output));
779 QTextStream out(&file);
781 out <<
"digraph alchemy {\n";
783 QVector<const recipe*> recipes;
784 QHash<const recipe*, QString>
names;
785 QHash<QString, QVector<const recipe*> > products;
787 for (
int ing = 1; ; ing++)
789 const recipelist* list = get_formulalist(ing);
793 for (
const recipe* recipe = list->items; recipe; recipe = recipe->next)
795 QString product(
"???");
796 for (
size_t idx = 0; idx < recipe->arch_names; idx++) {
797 auto arch = getManager()->archetypes()->find(recipe->arch_name[idx]);
801 if (recipe->title && strcmp(recipe->title,
"NONE")) {
802 product = tr(
"%1 of %2").arg(arch->clone.name, recipe->title);
804 product = arch->clone.name;
806 products[product].append(recipe);
808 names[recipe] = product;
809 recipes.append(recipe);
813 QHash<const recipe*, bool> added;
815 foreach (
const recipe* rec, recipes) {
816 for (linked_char* ing = rec->ingred; ing; ing = ing->next) {
817 const char* name = ing->name;
818 if (isdigit(name[0]) && strchr(name,
' ')) {
819 name = strchr(name,
' ') + 1;
821 QHash<QString, QVector<const recipe*> >::iterator item = products.find(name);
822 if (item != products.end()) {
824 out << tr(
"alchemy_%1 [label=\"%2\"]\n").arg(recipes.indexOf(rec)).arg(
names[rec]);
827 for (
auto r = item->begin(); r != item->end(); r++) {
829 out << tr(
"alchemy_%1 [label=\"%2\"]\n").arg(recipes.indexOf(*r)).arg(
names[*r]);
832 out << tr(
"alchemy_%1 -> alchemy_%2\n").arg(recipes.indexOf(*r)).arg(recipes.indexOf(rec));
839 foreach (
const recipe* rec, recipes) {
844 out <<
"graph [labelloc=\"b\" labeljust=\"c\" label=\"Alchemy graph, formulae producing ingredients of other formulae";
846 out << tr(
" (%1 formulae not displayed)").arg(ignored);
857 QString report = QString(
"<h2>%1</h2><table><thead><tr><th>Spell</th><th>Level</th><th>Path</th>").arg(skill);
858 report +=
"</tr></thead><tbody>";
860 QHash<int, QStringList> spells;
862 getManager()->archetypes()->each([&skill, &spells, &one] (
const archetype *spell) {
863 if (spell->clone.type == SPELL && spell->clone.skill == skill)
865 auto buf = stringbuffer_finish(describe_spellpath_attenuation(nullptr, spell->clone.path_attuned, nullptr));
866 spells[spell->clone.level].append(QString(
"<tr><td>%1</td><td>%2</td><td>%3</td></tr>").arg(spell->clone.name).arg(spell->clone.level).arg(buf));
875 QList<int> levels = spells.keys();
877 foreach(
int level, levels)
879 spells[level].sort();
880 report += spells[level].join(
"\n");
883 report +=
"</tbody></table>";
892 getManager()->archetypes()->each([&skills] (
const archetype *arch)
894 if (arch->clone.type == SKILL)
895 skills.append(arch->clone.name);
900 QString report(
"<h1>Spell list</h1>");
902 foreach(
const QString skill, skills)
920 static int monsterFight(archetype* monster, archetype* skill,
int level)
922 int limit = 50, result = 1;
925 memset(&pl, 0,
sizeof(player));
926 memset(&sock, 0,
sizeof(sock));
927 strncpy(pl.savebed_map,
"/HallOfSelection", MAX_BUF);
931 std::unique_ptr<uint8_t[]> faces(
new uint8_t[get_faces_count()]);
932 sock.faces_sent = faces.get();
934 archetype *dwarf_player_arch = find_archetype(
"dwarf_player");
935 if (dwarf_player_arch == NULL) {
938 object* obfirst = object_create_arch(dwarf_player_arch);
939 obfirst->level = level;
940 obfirst->contr = &pl;
942 object* obskill = object_create_arch(skill);
943 obskill->level = level;
944 SET_FLAG(obskill, FLAG_APPLIED);
945 object_insert_in_ob(obskill, obfirst);
946 archetype *skill_arch = find_archetype((skill->clone.subtype == SK_TWO_HANDED_WEAPON) ?
"sword_3" :
"sword");
947 if (skill_arch == NULL) {
950 object* sword = object_create_arch(skill_arch);
951 SET_FLAG(sword, FLAG_APPLIED);
952 object_insert_in_ob(sword, obfirst);
954 obfirst->stats.hp = obfirst->stats.maxhp;
956 object* obsecond = object_create_arch(monster);
957 tag_t tagfirst = obfirst->count;
958 tag_t tagsecond = obsecond->count;
961 mapstruct* test_map = get_empty_map(50, 50);
963 obfirst = object_insert_in_map_at(obfirst, test_map, NULL, 0, 0, 0);
964 obsecond = object_insert_in_map_at(obsecond, test_map, NULL, 0, 1 + monster->tail_x, monster->tail_y);
966 if (!obsecond || object_was_destroyed(obsecond, tagsecond))
968 qDebug() <<
"second removed??";
971 while (limit-- > 0 && obfirst->stats.hp >= 0 && obsecond->stats.hp >= 0)
973 if (obfirst->weapon_speed_left > 0) {
974 --obfirst->weapon_speed_left;
975 do_some_living(obfirst);
977 move_player(obfirst, 3);
978 if (object_was_destroyed(obsecond, tagsecond))
982 if (object_was_destroyed(obfirst, tagfirst) || (obfirst->map != test_map))
989 if (obsecond->speed_left > 0) {
990 --obsecond->speed_left;
991 monster_do_living(obsecond);
993 attack_ob(obfirst, obsecond);
995 if (object_was_destroyed(obfirst, tagfirst) || (obfirst->map != test_map))
1002 obfirst->weapon_speed_left += obfirst->weapon_speed;
1003 if (obfirst->weapon_speed_left > 1.0)
1004 obfirst->weapon_speed_left = 1.0;
1005 if (obsecond->speed_left <= 0)
1006 obsecond->speed_left += FABS(obsecond->speed);
1009 if (!object_was_destroyed(obfirst, tagfirst))
1011 object_remove(obfirst);
1012 object_free(obfirst, FREE_OBJ_FREE_INVENTORY);
1014 if (!object_was_destroyed(obsecond, tagsecond))
1016 object_remove(obsecond);
1017 object_free(obsecond, FREE_OBJ_FREE_INVENTORY);
1019 delete_map(test_map);
1034 static int monsterFight(archetype* monster, archetype* skill,
int level,
int count)
1054 qDebug() <<
"monsterFight:" << monster->clone.name << skill->clone.name;
1055 int ret, min = settings.max_level + 1, half = settings.max_level + 1, count = 5, level;
1056 int first = 1, max = settings.max_level;
1058 while (first != max)
1060 level = (max + first) / 2;
1071 if (ret > (count / 2) && (level < half))
1088 mapstruct* hos = has_been_loaded(
"/HallOfSelection");
1091 hos->reset_time = 1;
1092 hos->in_memory = MAP_IN_MEMORY;
1101 if (min == settings.max_level + 1)
1102 return "<td colspan=\"2\">-</td>";
1103 return "<td>" + QString::number(min) +
"</td><td>" + ((half != 0) ? QString::number(half) :
"") +
"</td>";
1114 QString line =
"<tr>";
1116 line +=
"<td>" + QString(monster->clone.name) +
"</td>";
1117 line +=
"<td>" + QString::number(monster->clone.level) +
"</td>";
1118 line +=
"<td>" + QString::number(monster->clone.speed) +
"</td>";
1119 line +=
"<td>" + QString::number(monster->clone.stats.wc) +
"</td>";
1120 line +=
"<td>" + QString::number(monster->clone.stats.dam) +
"</td>";
1121 line +=
"<td>" + QString::number(monster->clone.stats.ac) +
"</td>";
1122 line +=
"<td>" + QString::number(monster->clone.stats.hp) +
"</td>";
1123 line +=
"<td>" + QString::number(monster->clone.stats.Con) +
"</td>";
1125 foreach(archetype* skill, skills)
1140 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1143 QMap<QString, archetype*> monsters;
1144 QList<archetype*> skills;
1146 getManager()->archetypes()->each([&
names, &monsters, &skills] (archetype *arch)
1148 if (QUERY_FLAG(&arch->clone, FLAG_MONSTER) && arch->clone.stats.hp > 0 && arch->head == NULL)
1150 QString name(QString(arch->clone.name).toLower());
1151 if (monsters.contains(name))
1156 name = QString(arch->clone.name).toLower() +
"_" + QString::number(suffix);
1158 } while (monsters.contains(name));
1161 monsters[name] = arch;
1163 if (arch->clone.type == SKILL && IS_COMBAT_SKILL(arch->clone.subtype))
1165 if (strcmp(arch->name,
"skill_missile_weapon") == 0 || strcmp(arch->name,
"skill_throwing") == 0)
1167 skills.append(arch);
1171 names = monsters.keys();
1174 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;
1175 report +=
"<table border=\"1\"><tbody>\n";
1177 report +=
"<th rowspan=\"2\">Monster</th>";
1178 report +=
"<th rowspan=\"2\">level</th>";
1179 report +=
"<th rowspan=\"2\">speed</th>";
1180 report +=
"<th rowspan=\"2\">wc</th>";
1181 report +=
"<th rowspan=\"2\">dam</th>";
1182 report +=
"<th rowspan=\"2\">ac</th>";
1183 report +=
"<th rowspan=\"2\">hp</th>";
1184 report +=
"<th rowspan=\"2\">regen</th>";
1187 foreach(archetype* skill, skills)
1189 report +=
"<th colspan=\"2\">" + QString(skill->clone.name) +
"</th>";
1190 line +=
"<th>fv</th><th>hv</th>";
1192 report +=
"</tr>\n" + line +
"</tr>\n";
1195 foreach(
const QString name,
names)
1202 report +=
"</tbody></table>\n";
1205 QApplication::restoreOverrideCursor();
1209 static QString
reportSummon(
const archetype* summon,
const object* other, QString name)
1212 int level, wc_adj = 0;
1214 const object* spell = &summon->clone;
1215 sstring rate = object_get_value(spell,
"wc_increase_rate");
1217 wc_adj = atoi(rate);
1222 QString ac(
"<tr><td>ac</td>");
1223 QString hp(
"<tr><td>hp</td>");
1224 QString dam(
"<tr><td>dam</td>");
1225 QString speed(
"<tr><td>speed</td>");
1226 QString wc(
"<tr><td>wc</td>");
1227 int ihp, idam, iwc, diff;
1230 for (level = 1; level < 120; level += 10)
1232 if (level < spell->level)
1237 speed +=
"<td></td>";
1242 diff = level - spell->level;
1244 ihp = other->stats.hp + spell->duration + (spell->duration_modifier != 0 ? (diff / spell->duration_modifier) : 0);
1245 idam = (spell->stats.dam ? spell->stats.dam : other->stats.dam) + (spell->dam_modifier != 0 ? (diff / spell->dam_modifier) : 0);
1246 fspeed = MIN(1.0, FABS(other->speed) + .02 * (spell->range_modifier != 0 ? (diff / spell->range_modifier) : 0));
1247 iwc = other->stats.wc;
1249 iwc -= (diff / wc_adj);
1251 ac +=
"<td>" + QString::number(other->stats.ac) +
"</td>";
1252 hp +=
"<td>" + QString::number(ihp) +
"</td>";
1253 dam +=
"<td>" + QString::number(idam) +
"</td>";
1254 speed +=
"<td>" + QString::number(fspeed) +
"</td>";
1255 wc +=
"<td>" + QString::number(iwc) +
"</td>";
1258 report +=
"<tr><td colspan=\"13\"><strong>" + name +
"</strong></td></tr>\n";
1260 report += ac +
"</tr>\n" + hp +
"</tr>\n" + dam +
"</tr>\n" + speed +
"</tr>\n" + wc +
"</tr>\n\n";
1267 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1271 QString report(tr(
"<h1>Summoned pet statistics</h1>\n")), line;
1272 report +=
"<table border=\"1\">\n<thead>\n";
1274 report +=
"<th rowspan=\"2\">Spell</th>";
1275 report +=
"<th colspan=\"12\">Level</th>";
1276 report +=
"</tr>\n";
1279 for (level = 1; level < 120; level += 10)
1281 report +=
"<th>" + QString::number(level) +
"</th>";
1283 report +=
"</tr>\n</thead>\n<tbody>\n";
1285 QMap<QString, QString> spells;
1287 getManager()->archetypes()->each([&spells] (archetype *summon)
1289 if (summon->clone.type != SPELL || summon->clone.subtype != SP_SUMMON_GOLEM)
1291 if (summon->clone.other_arch != NULL)
1293 spells[summon->clone.name] = reportSummon(summon, &summon->clone.other_arch->clone, QString(summon->clone.name));
1298 getManager()->archetypes()->each([&summon, &spells] (archetype *god)
1300 if (god->clone.type != GOD)
1303 QString name(QString(summon->clone.name) +
" (" + QString(god->name) +
")");
1304 archetype* holy = determine_holy_arch(&god->clone, summon->clone.race);
1308 spells[name] =
reportSummon(summon, &holy->clone, name);
1312 QStringList keys = spells.keys();
1314 foreach(QString key, keys)
1316 report += spells[key];
1319 report +=
"</tbody>\n</table>\n";
1322 QApplication::restoreOverrideCursor();
1326 static QString
buildShopReport(
const QString& title,
const QStringList&
types,
const QList<CREMapInformation*>& maps, QStringList& items)
1328 QString report(
"<h2>" + title +
"</h2>");
1329 report +=
"<table border=\"1\">\n<thead>\n";
1331 report +=
"<th>Shop</th>";
1332 report +=
"<th>Greed</th>";
1333 report +=
"<th>Race</th>";
1334 report +=
"<th>Min</th>";
1335 report +=
"<th>Max</th>";
1336 foreach (QString item,
types)
1338 report +=
"<th>" + item +
"</th>";
1339 items.removeAll(item);
1341 report +=
"</tr>\n</thead><tbody>";
1353 line +=
"<td>" + map->
name() +
" " + map->
path() +
"</td>";
1354 line +=
"<td>" + QString::number(map->
shopGreed()) +
"</td>";
1355 line +=
"<td>" + map->
shopRace() +
"</td>";
1356 line +=
"<td>" + (map->
shopMin() != 0 ? QString::number(map->
shopMin()) :
"") +
"</td>";
1357 line +=
"<td>" + (map->
shopMax() != 0 ? QString::number(map->
shopMax()) :
"") +
"</td>";
1359 foreach(
const QString item,
types)
1364 line +=
"<td></td>";
1366 line +=
"<td>" + QString::number(map->
shopItems()[
"*"]) +
"</td>";
1370 line +=
"<td>" + QString::number(map->
shopItems()[item]) +
"</td>";
1378 report +=
"</tbody></table>";
1384 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1386 QString report(tr(
"<h1>Shop information</h1>\n"));
1392 QStringList add = map->
shopItems().keys();
1393 foreach(
const QString item, add)
1395 if (!items.contains(item))
1403 part <<
"weapon" <<
"weapon improver" <<
"bow" <<
"arrow";
1407 part <<
"armour" <<
"armour improver" <<
"boots" <<
"bracers" <<
"cloak" <<
"girdle" <<
"gloves" <<
"helmet" <<
"shield";
1411 part <<
"amulet" <<
"potion" <<
"power_crystal" <<
"ring" <<
"rod" <<
"scroll" <<
"skillscroll" <<
"spellbook" <<
"wand";
1415 part <<
"container" <<
"food" <<
"key" <<
"lamp" <<
"skill tool" <<
"special key";
1418 if (!items.isEmpty())
1426 QApplication::restoreOverrideCursor();
1430 void readDirectory(
const QString& path, QHash<QString, QHash<QString, bool> >& states)
1433 QStringList subdirs = dir.entryList(QStringList(
"*"), QDir::Dirs | QDir::NoDotAndDotDot);
1434 foreach(QString subdir, subdirs)
1439 QStringList quests = dir.entryList(QStringList(
"*.quest"), QDir::Files);
1440 foreach(QString file, quests)
1442 qDebug() <<
"read quest:" << path << file;
1443 QString name = file.left(file.length() - 6);
1444 QFile read(path + QDir::separator() + file);
1445 read.open(QFile::ReadOnly);
1446 QTextStream stream(&read);
1449 while (!(line = stream.readLine(0)).isNull())
1451 if (line.startsWith(
"quest "))
1459 if (line ==
"end_quest")
1461 states[code][name] = completed;
1465 if (line.startsWith(
"state "))
1467 if (line ==
"completed 1")
1475 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1477 QHash<QString, QHash<QString, bool> > states;
1478 QString directory(settings.localdir);
1479 directory += QDir::separator();
1480 directory += settings.playerdir;
1484 getManager()->quests()->each([&] (
const auto quest) {
1485 codes.append(quest->quest_code);
1488 QString report(
"<html><body>\n<h1>Quests</h1>\n");
1490 QStringList keys = states.keys();
1493 foreach(QString key, keys)
1495 codes.removeAll(key);
1496 const auto quest = getManager()->quests()->get(key.toStdString());
1497 report +=
"<h2>Quest: " + (quest != NULL ? quest->quest_title : (key +
" ???")) +
"</h2>\n";
1499 QHash<QString, bool> done = states[key];
1500 QStringList players = done.keys();
1504 foreach(QString player, players)
1511 report +=
"<strong>" + player +
"</strong>";
1519 report +=
"<p>" + tr(
"%1 completed out of %2 (%3%)").arg(completed).arg(players.size()).arg(completed * 100 / players.size()) +
"</p>\n";
1522 if (codes.length() > 0)
1526 report +=
"<h2>Quests never done</h2>\n<p>\n";
1527 foreach(QString code, codes)
1531 const auto quest = getManager()->quests()->find(code.toStdString());
1532 report += (quest != NULL ? quest->quest_title : (code +
" ???"));
1537 report +=
"</body>\n</html>\n";
1540 QApplication::restoreOverrideCursor();
1546 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1551 report +=
"<h1>Materials</h1>";
1552 report +=
"<table><tr><th>Name</th><th>Description</th></tr>";
1553 for (
const auto &mat : materials) {
1554 report += tr(
"<tr><td>%1</td><td>%2</td></tr>").arg(mat->name, mat->description);
1556 report +=
"</table>";
1558 for (
int s = 0; s < 2; s++) {
1559 QString name(s == 0 ?
"Saves" :
"Resistances");
1560 report += tr(
"<h1>%1</h1>").arg(name);
1561 report += tr(
"<tr><th rowspan='2'>Name</th><th colspan='%1'>%2</th></tr>").arg(NROFATTACKS).arg(name);
1563 for (
int r = 0; r < NROFATTACKS; r++) {
1564 report +=
"<th>" + QString(attacktype_desc[r]) +
"</th>";
1568 for (
auto const &mat : materials) {
1569 report += tr(
"<tr><td>%1</td>").arg(mat->name);
1570 for (
int r = 0; r < NROFATTACKS; r++) {
1571 int8_t val = (s == 0 ? mat->save[r] : mat->mod[r]);
1572 report += tr(
"<td>%1</td>").arg(val == 0 ? QString() : QString::number(val));
1576 report +=
"</table>";
1579 report +=
"</html>";
1582 QApplication::restoreOverrideCursor();
1588 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1593 report +=
"<h1>Apparently unused archetypes</h1>";
1594 report +=
"<h3>Warning: this list contains skills, items on style maps, and other things which are actually used.</h3>";
1595 report +=
"<table>";
1596 report +=
"<tr><th>Image</th><th>Archetype name</th><th>Item name</th><th>Type</th></tr>";
1598 getManager()->archetypes()->each([
this, &report] (
const archetype* arch)
1600 if (arch->head || arch->clone.type == PLAYER || arch->clone.type == MAP || arch->clone.type == EVENT_CONNECTOR)
1602 if (strstr(arch->name,
"hpbar") !=
nullptr)
1616 QByteArray byteArray;
1617 QBuffer buffer(&byteArray);
1618 image.save(&buffer,
"PNG");
1619 QString iconBase64 = QString::fromLatin1(byteArray.toBase64().data());
1620 auto td = get_typedata(arch->clone.type);
1621 report += tr(
"<tr><td><img src='data:image/png;base64,%1'></td><td>%2</td><td>%3</td><td>%4</td></tr>")
1622 .arg(iconBase64, arch->name, arch->clone.name, td ? td->name : tr(
"unknown: %1").arg(arch->clone.type));
1626 report +=
"</table>";
1627 report +=
"</html>";
1630 QApplication::restoreOverrideCursor();
1636 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1642 std::set<std::string> faces, facesets, fields;
1644 for (
auto item : all)
1646 faces.insert(item.first);
1647 for (
auto fs : item.second)
1649 facesets.insert(fs.first);
1650 for (
auto items : fs.second)
1652 fields.insert(items.first);
1657 getManager()->facesets()->each([&facesets] (
const face_sets *fs)
1659 facesets.erase(fs->prefix);
1661 getManager()->faces()->each([&faces] (
const Face *face)
1667 if (facesets.empty())
1669 report +=
"<h1>No invalid faceset</h1>\n";
1673 report +=
"<h1>Invalid faceset found</h1>\n";
1674 report +=
"<p>The faceset of the license file doesn't match any defined facesets.</p>";
1676 for (
auto fs : facesets)
1678 report +=
"<li>" + QString(fs.c_str()) +
"</li>\n";
1680 report +=
"</ul>\n";
1685 report +=
"<h1>No invalid face name</h1>\n";
1689 report +=
"<h1>Invalid face names found</h1>\n";
1690 report +=
"<p>The face name from the license file doesn't match any defined face.</p>";
1692 for (
auto f : faces)
1694 report +=
"<li>" + QString(f.c_str()) +
"</li>\n";
1696 report +=
"</ul>\n";
1699 report +=
"<h1>All fields used in license descriptions</h1>\n";
1701 for (
auto f : fields)
1703 report +=
"<li>" + QString(f.c_str()) +
"</li>\n";
1705 report +=
"</ul>\n";
1707 report +=
"</html>";
1709 QApplication::restoreOverrideCursor();
1715 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1721 auto groupCmp = [] (
const std::string &left,
const std::string &right) {
return left < right; };
1724 int name = left->
name().compare(right->name());
1729 return left->
path().compare(right->path()) < 0;
1731 std::map<std::string, std::vector<CREMapInformation *>, decltype(groupCmp)> groups(groupCmp);
1733 for (
auto map : maps)
1735 if (!map->resetGroup().isEmpty())
1737 groups[map->resetGroup().toStdString()].push_back(map);
1743 report +=
"<h1>No reset group defined</h1>\n";
1747 for (
auto group : groups)
1749 report +=
"<h1>" + QString(group.first.c_str()) +
" (" + QString::number(group.second.size()) +
" maps)</h1>\n";
1751 std::sort(group.second.begin(), group.second.end(), mapCmp);
1752 for (
auto map : group.second)
1754 report += tr(
"<li>%1 (%2)</li>").arg(map->name(), map->path());
1756 report +=
"</ul>\n";
1760 report +=
"</html>";
1762 QApplication::restoreOverrideCursor();
1798 QMessageBox confirm;
1799 confirm.setWindowTitle(tr(
"Crossfire Resource Editor"));
1800 confirm.setText(tr(
"Really clear map cache?"));
1801 confirm.setInformativeText(tr(
"This will force cache rebuild at next application start."));
1802 confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1803 confirm.setDefaultButton(QMessageBox::No);
1804 confirm.setIcon(QMessageBox::Question);
1805 if (confirm.exec() == QMessageBox::Yes)
1823 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1825 assets_collect(settings.datadir, ASSETS_ALL);
1827 QApplication::restoreOverrideCursor();
1828 QMessageBox::information(
this,
"Reload complete",
"Assets reload complete, you may need to change the selected item to see updated versions.");
1834 QString dir = QFileDialog::getExistingDirectory(
this, tr(
"Please select the 'sounds' directory"), settings.
soundsDirectory());
1843 auto windows =
myArea->subWindowList();
1844 bool hasWindows = !windows.empty();
1846 while (
myWindows->actions().size() > 6) {
1850 if (a->isSeparator()) {
1851 a->setVisible(hasWindows);
1852 }
else if (a !=
myWindows->actions()[0]) {
1853 a->setEnabled(hasWindows);
1857 for (
int i = 0; i < windows.size(); ++i) {
1858 QMdiSubWindow *mdiSubWindow = windows.at(i);
1860 QString title(mdiSubWindow->widget()->windowTitle());
1862 title = tr(
"&%1 %2").arg(i + 1).arg(title);
1864 title = tr(
"%1 %2").arg(i + 1).arg(title);
1866 QAction *action =
myWindows->addAction(title, mdiSubWindow, [
this, mdiSubWindow] () {
1867 myArea->setActiveSubWindow(mdiSubWindow);
1869 action->setCheckable(
true);
1870 action ->setChecked(mdiSubWindow ==
myArea->activeSubWindow());
1875 auto reg = get_region_by_name(map->
region().toLocal8Bit().data());