#include "quest.hpp"
#include "world.hpp"
Quest::Quest(short id, World *world)
{
char namebuf[4];
if (id < 0)
{
return;
}
this->id = id;
this->exists = true;
std::string filename = world->config["QuestDir"];
std::sprintf(namebuf, "%05i", id);
filename.append(namebuf);
filename.append(".eqf");
std::FILE *fh = std::fopen(filename.c_str(), "rt");
std::string buf;
if (!fh)
{
Console::Err("Could not load quest file %s", filename.c_str());
this->exists = false;
return;
}
std::fseek(fh, 0, SEEK_END);
long size = ftell(fh);
std::rewind(fh);
buf.resize(size);
std::fread(&buf[0], 1, size, fh);
std::fclose(fh);
Load(buf);
}
void Quest::Load(const std::string source)
{
bool inmain = false;
bool instate = false;
bool inquotes = false;
bool inparens = false;
bool incomment = false;
bool escape = false;
bool semicolon = false;
bool version_said = false;
bool questname_said = false;
int character = 0;
int quote_start = 0;
int quote_end = 0;
int paren_start = 0;
int paren_end = 0;
int comment_start = 0;
std::vector<int> args;
State *state;
Action *action;
Rule *rule;
std::string tempstring;
while (source[character] != '\0')
{
if (!incomment && !escape && (source[character] == '\"' || source[character] == '\''))
{
if (inquotes)
{
quote_end = tempstring.size();
inquotes = false;
}
else
{
quote_start = tempstring.size();
inquotes = true;
}
}
else if (source[character] == '\\')
{
if (source[character+1] == '\'' || source[character+1] == '\"')
escape = true;
}
else if (!inquotes && (source[character] == '/' && source[character+1] == '/'))
{
incomment = true;
comment_start = tempstring.size();
}
else if ((!incomment && !inquotes && ((source[character] == '\n' && !instate && !inmain) || source[character] == ' ')) || source[character] == '\t')
{
character++;
continue;
}
else if (incomment && source[character] == '\n')
{
incomment = false;
tempstring.erase(comment_start);
}
else if (!incomment && !inquotes && source[character] == '{')
{
if (tempstring.compare(0, 4, "main") == 0)
{
inmain = true;
}
else if (tempstring.compare(0, 5, "state") == 0)
{
instate = true;
state = new State();
if (tempstring.size() <= 6)
SyntaxError("No state name available");
state->name = tempstring.substr(5);
}
tempstring.clear();
}
else if (!incomment && !inquotes && source[character] == '}')
{
if (!inmain && !instate)
SyntaxError("Invalid brace: No starting brace");
if (inmain) inmain = false;
else if (instate)
{
this->states.push_back(state);
state->Release();
instate = false;
}
tempstring.clear();
}
else if (!incomment && !inquotes && source[character] == '(')
{
if (inparens)
SyntaxError("Nested parentheses not allowed");
inparens = true;
paren_start = tempstring.size();
}
else if (!incomment && !inquotes && source[character] == ')')
{
if (!inparens)
SyntaxError("Not in parentheses");
inparens = false;
paren_end = tempstring.size();
}
else if (!incomment && !inquotes && source[character] == ';')
{
semicolon = true;
}
else if (!incomment && !inquotes && source[character] == ',')
{
if (!inparens)
SyntaxError("Invalid comma outside of parentheses");
args.push_back(tempstring.size());
}
else
{
escape = false;
if ((source[character] >= 'A' && source[character] <= 'Z') && !inquotes)
tempstring += source[character] - 'A' + 'a';
else
tempstring += source[character];
}
if (inmain)
{
if (source[character] == '\n')
{
if (tempstring.compare(0, 9, "questname") == 0)
{
if (quote_start == 0 || quote_end == 0)
SyntaxError("No quotation marks around the questname");
this->name = tempstring.substr(quote_start, quote_end);
questname_said = true;
quote_start = quote_end = 0;
}
else if (tempstring.compare(0, 7, "version") == 0)
{
std::vector<std::string> parts = util::explode('.', tempstring);
if (parts.size() != 2)
SyntaxError("Invalid version format (try major.minor)");
this->version_major = util::to_int(parts[0]);
this->version_minor = util::to_int(parts[1]);
version_said = true;
}
tempstring.clear();
}
}
else if (instate)
{
if (source[character] == '\n')
{
if (tempstring.compare(0, 4, "desc") == 0)
{
if (quote_start == 0 || quote_end == 0)
SyntaxError("No quotation marks around the description");
state->description = tempstring.substr(quote_start, quote_end);
quote_start = quote_end = 0;
tempstring.clear();
}
else if (tempstring.compare(0, 6, "action") == 0)
{
if (semicolon)
{
if (paren_start == 0 || paren_end == 0)
SyntaxError("No parentheses in action");
action = new Action();
action->name = tempstring.substr(6, paren_start - 6);
if (args.size() > 0)
{
size_t start, end;
for (size_t i = 0; i <= args.size(); ++i)
{
start = (i == 0 ? paren_start : args[i - 1]);
end = (i == args.size() ? paren_end : args[i]) - start;
action->args.push_back(new util::variant(tempstring.substr(start, end)));
}
}
else
{
action->args.push_back(new util::variant(tempstring.substr(paren_start, paren_end-paren_start)));
}
if (action->name.compare("giveitem") == 0 && action->args.size() != 2)
SyntaxError("GiveItem requires two parameters");
else if (action->name.compare("removeitem") == 0 && action->args.size() != 2)
SyntaxError("RemoveItem requires two parameters");
else if (action->name.compare("giveexp") == 0 && action->args.size() != 1)
SyntaxError("GiveExp requires one parameter");
else if (action->name.compare("givekarma") == 0 && action->args.size() != 1)
SyntaxError("GiveKarma requires one parameter");
else if (action->name.compare("removekarma") == 0 && action->args.size() != 1)
SyntaxError("RemoveKarma requires one parameter");
else if (action->name.compare("showhint") == 0 && action->args.size() != 1)
SyntaxError("ShowHint requires one parameter");
else if (action->name.compare("playsound") == 0 && action->args.size() != 1)
SyntaxError("PlaySound requires one parameter");
else if (action->name.compare("setcoord") == 0 && action->args.size() != 3)
SyntaxError("SetCoord requires three parameterw");
else if (action->name.compare("setrace") == 0 && action->args.size() != 1)
SyntaxError("SetRace requires one parameter");
else if (action->name.compare("setclass") == 0 && action->args.size() != 1)
SyntaxError("SetClass requires one parameter");
else if (action->name.compare("setstate") == 0 && action->args.size() != 1)
SyntaxError("SetState requires one parameter");
else if (action->name.compare("quake") == 0 && (action->args.size() != 1 && action->args.size() != 2))
SyntaxError("Quake requires at least one parameter");
else if (action->name.compare("addnpctext") == 0 && action->args.size() != 2)
SyntaxError("AddNpcText requires two parameters");
else if (action->name.compare("addnpcchat") == 0 && action->args.size() != 2)
SyntaxError("AddNpcChat requires two parameters");
else if (action->name.compare("addnpcinput") == 0 && action->args.size() != 3)
SyntaxError("AddNpcInput requires three parameters");
state->actions.push_back(action);
action->Release();
paren_start = paren_end = 0;
tempstring.clear();
args.clear();
semicolon = false;
}
else if (paren_end != 0)
{
SyntaxError("Expected \';\'");
paren_start = paren_end = 0;
tempstring.clear();
args.clear();
}
}
else if (tempstring.compare(0, 4, "rule") == 0)
{
if (paren_start == 0 || paren_end == 0)
SyntaxError("No parentheses in rule");
rule = new Rule();
rule->name = tempstring.substr(4, paren_start - 4);
if (args.size() > 0)
{
size_t start, end;
for (size_t i = 0; i <= args.size(); ++i)
{
start = (i == 0 ? paren_start : args[i - 1]);
end = (i == args.size() ? paren_end : args[i]) - start;
rule->args.push_back(new util::variant(tempstring.substr(start, end)));
}
}
else
{
rule->args.push_back(new util::variant(tempstring.substr(paren_start, paren_end-paren_start)));
}
if (tempstring.compare(paren_end, 4, "goto") == 0)
{
rule->goto_state = util::trim(tempstring.substr(paren_end+4));
}
else
SyntaxError("Every rule is required to have a goto");
if (rule->name.compare("inputnpc") == 0 && rule->args.size() != 1)
SyntaxError("InputNpc requires one parameter");
else if (rule->name.compare("talkedtonpc") == 0 && rule->args.size() != 1)
SyntaxError("TalkedToNpc requires one parameter");
else if (rule->name.compare("gotitems") == 0 && rule->args.size() != 2)
SyntaxError("GotItems requires two parameters");
else if (rule->name.compare("lostitems") == 0 && rule->args.size() != 2)
SyntaxError("LostItems requires two parameters");
else if (rule->name.compare("killednpcs") == 0 && rule->args.size() != 2)
SyntaxError("KilledNpcs requires two parameters");
else if (rule->name.compare("killedplayers") == 0 && rule->args.size() != 1)
SyntaxError("KilledPlayers requires one parameter");
else if (rule->name.compare("entercoord") == 0 && rule->args.size() != 3)
SyntaxError("EnterCoord requires three parameters");
else if (rule->name.compare("leavecoord") == 0 && rule->args.size() != 3)
SyntaxError("LeaveCoord requires three parameters");
else if (rule->name.compare("entermap") == 0 && rule->args.size() != 1)
SyntaxError("EnterMap requires one parameter");
else if (rule->name.compare("leavemap") == 0 && rule->args.size() != 1)
SyntaxError("LeaveMap requires one parameter");
state->rules.push_back(rule);
rule->Release();
paren_start = paren_end = 0;
tempstring.clear();
args.clear();
}
else
tempstring.clear();
}
}
character++;
}
if (!questname_said)
SyntaxError("No questname variable");
if (!version_said)
SyntaxError("No version variable");
}
void Quest::Reload(World *world)
{
this->states.clear();
char namebuf[4];
this->exists = true;
std::string filename = world->config["QuestDir"];
std::sprintf(namebuf, "%05i", id);
filename.append(namebuf);
filename.append(".eqf");
std::FILE *fh = std::fopen(filename.c_str(), "rt");
std::string buf;
if (!fh)
{
Console::Err("Could not load quest file %s", filename.c_str());
this->exists = false;
return;
}
std::fseek(fh, 0, SEEK_END);
long size = ftell(fh);
std::rewind(fh);
buf.resize(size);
std::fread(&buf[0], 1, size, fh);
std::fclose(fh);
Load(buf);
}
State *Quest::GetState(std::string name)
{
UTIL_PTR_VECTOR_FOREACH(this->states, State, state)
{
if (state->name.compare(name) == 0)
{
return *state;
}
}
return 0;
}