Log in

View Full Version : SpellGap : Native Utility to find unused spell ids


Taurinus2
01-25-2014, 11:44 PM
Greetings.

This is something I whipped up real quick in C++ to find unused spell ids in spells_us.txt.

It produces a list of the ids, as well as an sql file to verify the results with. The only requirements are "spells_us.txt" and a recent-ish compiler (C++ 11 compliance required). Just drop the executable once built into the same folder as your spell file and execute.

/*
main.cpp
implements entry point and all program logic.

SpellGap
Finds and records unused spell ids in spells_us.txt

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>

*/

#if defined(_MSC_VER)
#pragma warning (disable : 996)
#endif
/*This header is a non standard header that can be found at:
http://codereview.stackexchange.com/questions/13176/infix-iterator-code
*/
#include "infix_iterator.h"


#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

#include <fstream>
using std::ifstream;
using std::ofstream;

#include <cstdlib>

#include <vector>
using std::vector;

#include <algorithm>
using std::for_each;
using std::copy;
#include <iterator>

/*
assumes: the last valid id is the upper bound for ids
requires: spells_us.txt
*/

int main()
{
string spellFile = "spells_us.txt";
string spell_id_str;

cout << "Recording valid spell ids...\n";

ifstream infile(spellFile.c_str());

if (infile.is_open())
{
vector<int> spell_ids;

while (std::getline(infile, spell_id_str, '^').good())
{
int spell_id = atoi(spell_id_str.c_str());
spell_ids.push_back(spell_id);
// not interested in the other fields, discard them
string junk;
std::getline(infile, junk);
}

infile.close();
cout << "Done!\n";

cout << "Finding unused ids...\n";
int previd = 0;

vector<int> unused_ids;
// iterate over the valid id range, while pushing missing ids into 'unused_ids'
for_each(spell_ids.begin(), spell_ids.end(),
[&previd, &unused_ids](int& id)
{
// detect the gap and record it
if (id > previd + 1)
{
for (int i = previd + 1; i < id; i++)
{
unused_ids.push_back(i);
}
}

previd = id;
});
spell_ids.clear();
spell_ids.shrink_to_fit();

// write unused ids as a list to 'unused_spell_ids.txt'
cout << "Found " << unused_ids.size() << " unused ids. Writing them to file...\n";
ofstream outfile("unused_spell_ids.txt");
copy(unused_ids.begin(), unused_ids.end(), std::ostream_iterator<int>(outfile, "\n"));
cout << "Done!\n";
outfile.close();

cout << "Writing 'unused_ids_verify.sql' for verification...\n";
/* write a quick query to file to verify that we are not snagging valid ids*/
ofstream sqlfile("unused_ids_verify.sql");

sqlfile << "SELECT name FROM spells_new WHERE id=";
copy(unused_ids.begin(), unused_ids.end(), infix_ostream_iterator<int>(sqlfile, " OR id="));

sqlfile << ";\n";
sqlfile.close();
cout << "Done!\n";
cout << "Use mysql: 'source unused_ids_verify.sql;' to verify this process.\n";
cout << "Goodbye.\n";
}

else
{
cout << "spells_us.txt not found! Aborting...\n";
}

return 0;
}


It employs a non-standard header located here:

http://codereview.stackexchange.com/questions/13176/infix-iterator-code

Enjoy!

Kayen
01-26-2014, 02:20 AM
Good work.

Alternatively, SQL query to return gaps in spell file.

SELECT a.id+1 AS start, MIN(b.id) - 1 AS end
FROM spells_new AS a, spells_new AS b
WHERE a.id < b.id
GROUP BY a.id
HAVING start < MIN(b.id);

Kayen
GM Storm Haven

Dunge0nMastr
01-26-2014, 02:31 AM
Thanks for both! Will save us a ton of time lol.

Tabasco
01-26-2014, 09:36 AM
You can also look for name like '%test%' or name like '%placeholder%'

Taurinus2
01-30-2014, 01:36 PM
I've updated the source for those interested to now offer a command line parameter to filter out 'test' and 'placeholder' spells.

/*
main.cpp
implements entry point and all program logic.

SpellGap
Finds and records unused spell ids in spells_us.txt

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>

*/

#if defined(_MSC_VER)
#pragma warning (disable : 996)
#endif
/*This header is a non standard header that can be found at:
http://codereview.stackexchange.com/questions/13176/infix-iterator-code
*/
#include "infix_iterator.h"

#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

#include <fstream>
using std::ifstream;
using std::ofstream;

#include <cstdlib>

#include <vector>
using std::vector;

#include <algorithm>
using std::for_each;
using std::copy;
#include <iterator>

/*
assumes: the last valid id is the upper bound for ids
requires: spells_us.txt
*/

int main(int argc, char*argv[])
{
// filter out 'test' and 'placeholder' spells
bool useFilter = (argc >= 2 && ((strcmp(argv[1], "-f") == 0) || (strcmp(argv[1], "-F") == 0))) ? true : false;

string spellFile = "spells_us.txt";
string spell_id_str;

cout << "Recording valid spell ids...\n";

ifstream infile(spellFile.c_str());

if (infile.is_open())
{
vector<int> spell_ids;

while (std::getline(infile, spell_id_str, '^').good())
{
string spell_name;
std::getline(infile, spell_name, '^');


if (useFilter)
{
std::transform(spell_name.begin(), spell_name.end(), spell_name.begin(), ::toupper);

// conditional insert
if (spell_name.find("TEST") == string::npos && spell_name.find("PLACEHOLDER") == string::npos)
{
spell_ids.push_back(atoi(spell_id_str.c_str()));
}
}

else
{
// unconditional insert
spell_ids.push_back(atoi(spell_id_str.c_str()));
}

// not interested in the other fields, discard them
string junk;
std::getline(infile, junk);


}

infile.close();
cout << "Done!\n";

cout << "Finding unused ids...\n";
int previd = 0;

vector<int> unused_ids;
// iterate over the valid id range, while pushing missing ids into 'unused_ids'
for_each(spell_ids.begin(), spell_ids.end(),
[&previd, &unused_ids](int& id)
{
// detect the gap and record it

if (id > previd + 1)
{
for (int i = previd + 1; i < id; i++)
{
unused_ids.push_back(i);
}
}

previd = id;
});
spell_ids.clear();
spell_ids.shrink_to_fit();

// write unused ids as a list to 'unused_spell_ids.txt'
cout << "Found " << unused_ids.size() << " unused ids. Writing them to file...\n";
ofstream outfile("unused_spell_ids.txt");
copy(unused_ids.begin(), unused_ids.end(), std::ostream_iterator<int>(outfile, "\n"));
cout << "Done!\n";
outfile.close();

cout << "Writing 'unused_ids_verify.sql' for verification...\n";
/* write a quick query to file to verify that we are not snagging valid ids*/
ofstream sqlfile("unused_ids_verify.sql");

sqlfile << "SELECT name FROM spells_new WHERE id=";
copy(unused_ids.begin(), unused_ids.end(), infix_ostream_iterator<int>(sqlfile, " OR id="));

sqlfile << ";\n";
sqlfile.close();
cout << "Done!\n";
cout << "Use mysql: 'source unused_ids_verify.sql;' to verify this process.\n";
cout << "Goodbye.\n";
}

else
{
cout << "spells_us.txt not found! Aborting...\n";
}

return 0;
}


Enjoy.