828 lines
30 KiB
C++
828 lines
30 KiB
C++
|
/*
|
||
|
Copyright (C) 2001-2006, William Joseph.
|
||
|
All Rights Reserved.
|
||
|
|
||
|
This file is part of GtkRadiant.
|
||
|
|
||
|
GtkRadiant 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 2 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
*/
|
||
|
|
||
|
#include "eclass_doom3.h"
|
||
|
|
||
|
#include "debugging/debugging.h"
|
||
|
|
||
|
#include <map>
|
||
|
|
||
|
#include "ifilesystem.h"
|
||
|
#include "iscriplib.h"
|
||
|
#include "iarchive.h"
|
||
|
#include "qerplugin.h"
|
||
|
|
||
|
#include "generic/callback.h"
|
||
|
#include "string/string.h"
|
||
|
#include "eclasslib.h"
|
||
|
#include "os/path.h"
|
||
|
#include "os/dir.h"
|
||
|
#include "stream/stringstream.h"
|
||
|
#include "moduleobservers.h"
|
||
|
#include "stringio.h"
|
||
|
|
||
|
class RawString {
|
||
|
const char *m_value;
|
||
|
public:
|
||
|
RawString(const char *value) : m_value(value)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
const char *c_str() const
|
||
|
{
|
||
|
return m_value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
inline bool operator<(const RawString &self, const RawString &other)
|
||
|
{
|
||
|
return string_less_nocase(self.c_str(), other.c_str());
|
||
|
}
|
||
|
|
||
|
typedef std::map<RawString, EntityClass *> EntityClasses;
|
||
|
EntityClasses g_EntityClassDoom3_classes;
|
||
|
EntityClass *g_EntityClassDoom3_bad = 0;
|
||
|
|
||
|
|
||
|
void EntityClassDoom3_clear()
|
||
|
{
|
||
|
for (EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) {
|
||
|
(*i).second->free((*i).second);
|
||
|
}
|
||
|
g_EntityClassDoom3_classes.clear();
|
||
|
}
|
||
|
|
||
|
// entityClass will be inserted only if another of the same name does not already exist.
|
||
|
// if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned.
|
||
|
EntityClass *EntityClassDoom3_insertUnique(EntityClass *entityClass)
|
||
|
{
|
||
|
return (*g_EntityClassDoom3_classes.insert(
|
||
|
EntityClasses::value_type(entityClass->name(), entityClass)).first).second;
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_forEach(EntityClassVisitor &visitor)
|
||
|
{
|
||
|
for (EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) {
|
||
|
visitor.visit((*i).second);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void printParseError(const char *message)
|
||
|
{
|
||
|
globalErrorStream() << message;
|
||
|
}
|
||
|
|
||
|
#define PARSE_RETURN_FALSE_IF_FAIL(expression) do { if (!( expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } } while (0)
|
||
|
|
||
|
bool EntityClassDoom3_parseToken(Tokeniser &tokeniser)
|
||
|
{
|
||
|
const char *token = tokeniser.getToken();
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(token != 0);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseToken(Tokeniser &tokeniser, const char *string)
|
||
|
{
|
||
|
const char *token = tokeniser.getToken();
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(token != 0);
|
||
|
return string_equal(token, string);
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseString(Tokeniser &tokeniser, const char *&s)
|
||
|
{
|
||
|
const char *token = tokeniser.getToken();
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(token != 0);
|
||
|
s = token;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseString(Tokeniser &tokeniser, CopiedString &s)
|
||
|
{
|
||
|
const char *token = tokeniser.getToken();
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(token != 0);
|
||
|
s = token;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseString(Tokeniser &tokeniser, StringOutputStream &s)
|
||
|
{
|
||
|
const char *token = tokeniser.getToken();
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(token != 0);
|
||
|
s << token;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseUnknown(Tokeniser &tokeniser)
|
||
|
{
|
||
|
//const char* name =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
|
||
|
//globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n";
|
||
|
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
|
||
|
tokeniser.nextLine();
|
||
|
|
||
|
std::size_t depth = 1;
|
||
|
for (;;) {
|
||
|
const char *token;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
|
||
|
if (string_equal(token, "}")) {
|
||
|
if (--depth == 0) {
|
||
|
tokeniser.nextLine();
|
||
|
break;
|
||
|
}
|
||
|
} else if (string_equal(token, "{")) {
|
||
|
++depth;
|
||
|
}
|
||
|
tokeniser.nextLine();
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
class Model {
|
||
|
public:
|
||
|
bool m_resolved;
|
||
|
CopiedString m_mesh;
|
||
|
CopiedString m_skin;
|
||
|
CopiedString m_parent;
|
||
|
typedef std::map<CopiedString, CopiedString> Anims;
|
||
|
Anims m_anims;
|
||
|
|
||
|
Model() : m_resolved(false)
|
||
|
{
|
||
|
}
|
||
|
};
|
||
|
|
||
|
typedef std::map<CopiedString, Model> Models;
|
||
|
|
||
|
Models g_models;
|
||
|
|
||
|
void Model_resolveInheritance(const char *name, Model &model)
|
||
|
{
|
||
|
if (model.m_resolved == false) {
|
||
|
model.m_resolved = true;
|
||
|
|
||
|
if (!string_empty(model.m_parent.c_str())) {
|
||
|
Models::iterator i = g_models.find(model.m_parent);
|
||
|
if (i == g_models.end()) {
|
||
|
globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n";
|
||
|
} else {
|
||
|
Model_resolveInheritance((*i).first.c_str(), (*i).second);
|
||
|
model.m_mesh = (*i).second.m_mesh;
|
||
|
model.m_skin = (*i).second.m_skin;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseModel(Tokeniser &tokeniser)
|
||
|
{
|
||
|
const char *name;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, name));
|
||
|
|
||
|
Model &model = g_models[name];
|
||
|
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
|
||
|
tokeniser.nextLine();
|
||
|
|
||
|
for (;;) {
|
||
|
const char *parameter;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, parameter));
|
||
|
if (string_equal(parameter, "}")) {
|
||
|
tokeniser.nextLine();
|
||
|
break;
|
||
|
} else if (string_equal(parameter, "inherit")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_parent));
|
||
|
tokeniser.nextLine();
|
||
|
} else if (string_equal(parameter, "remove")) {
|
||
|
//const char* remove =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
tokeniser.nextLine();
|
||
|
} else if (string_equal(parameter, "mesh")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_mesh));
|
||
|
tokeniser.nextLine();
|
||
|
} else if (string_equal(parameter, "skin")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_skin));
|
||
|
tokeniser.nextLine();
|
||
|
} else if (string_equal(parameter, "offset")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "("));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, ")"));
|
||
|
tokeniser.nextLine();
|
||
|
} else if (string_equal(parameter, "channel")) {
|
||
|
//const char* channelName =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "("));
|
||
|
for (;;) {
|
||
|
const char *end;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end));
|
||
|
if (string_equal(end, ")")) {
|
||
|
tokeniser.nextLine();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else if (string_equal(parameter, "anim")) {
|
||
|
CopiedString animName;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animName));
|
||
|
const char *animFile;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile));
|
||
|
model.m_anims.insert(Model::Anims::value_type(animName, animFile));
|
||
|
|
||
|
const char *token;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
|
||
|
|
||
|
while (string_equal(token, ",")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile));
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
|
||
|
}
|
||
|
|
||
|
if (string_equal(token, "{")) {
|
||
|
for (;;) {
|
||
|
const char *end;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end));
|
||
|
if (string_equal(end, "}")) {
|
||
|
tokeniser.nextLine();
|
||
|
break;
|
||
|
}
|
||
|
tokeniser.nextLine();
|
||
|
}
|
||
|
} else {
|
||
|
tokeniser.ungetToken();
|
||
|
}
|
||
|
} else {
|
||
|
globalErrorStream() << "unknown model parameter: " << makeQuoted(parameter) << "\n";
|
||
|
return false;
|
||
|
}
|
||
|
tokeniser.nextLine();
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
inline bool char_isSpaceOrTab(char c)
|
||
|
{
|
||
|
return c == ' ' || c == '\t';
|
||
|
}
|
||
|
|
||
|
inline bool char_isNotSpaceOrTab(char c)
|
||
|
{
|
||
|
return !char_isSpaceOrTab(c);
|
||
|
}
|
||
|
|
||
|
template<typename Predicate>
|
||
|
inline const char *string_find_if(const char *string, Predicate predicate)
|
||
|
{
|
||
|
for (; *string != 0; ++string) {
|
||
|
if (predicate(*string)) {
|
||
|
return string;
|
||
|
}
|
||
|
}
|
||
|
return string;
|
||
|
}
|
||
|
|
||
|
inline const char *string_findFirstSpaceOrTab(const char *string)
|
||
|
{
|
||
|
return string_find_if(string, char_isSpaceOrTab);
|
||
|
}
|
||
|
|
||
|
inline const char *string_findFirstNonSpaceOrTab(const char *string)
|
||
|
{
|
||
|
return string_find_if(string, char_isNotSpaceOrTab);
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool EntityClass_parse(EntityClass &entityClass, Tokeniser &tokeniser)
|
||
|
{
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, entityClass.m_name));
|
||
|
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
|
||
|
tokeniser.nextLine();
|
||
|
|
||
|
StringOutputStream usage(256);
|
||
|
StringOutputStream description(256);
|
||
|
CopiedString *currentDescription = 0;
|
||
|
StringOutputStream *currentString = 0;
|
||
|
|
||
|
for (;;) {
|
||
|
const char *key;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, key));
|
||
|
|
||
|
const char *last = string_findFirstSpaceOrTab(key);
|
||
|
CopiedString first(StringRange(key, last));
|
||
|
|
||
|
if (!string_empty(last)) {
|
||
|
last = string_findFirstNonSpaceOrTab(last);
|
||
|
}
|
||
|
|
||
|
if (currentString != 0 && string_equal(key, "\\")) {
|
||
|
tokeniser.nextLine();
|
||
|
*currentString << " ";
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, *currentString));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (currentDescription != 0) {
|
||
|
*currentDescription = description.c_str();
|
||
|
description.clear();
|
||
|
currentDescription = 0;
|
||
|
}
|
||
|
currentString = 0;
|
||
|
|
||
|
if (string_equal(key, "}")) {
|
||
|
tokeniser.nextLine();
|
||
|
break;
|
||
|
} else if (string_equal(key, "model")) {
|
||
|
const char *token;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
|
||
|
entityClass.fixedsize = true;
|
||
|
StringOutputStream buffer(256);
|
||
|
buffer << PathCleaned(token);
|
||
|
entityClass.m_modelpath = buffer.c_str();
|
||
|
} else if (string_equal(key, "editor_color")) {
|
||
|
const char *value;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
|
||
|
if (!string_empty(value)) {
|
||
|
entityClass.colorSpecified = true;
|
||
|
bool success = string_parse_vector3(value, entityClass.color);
|
||
|
ASSERT_MESSAGE(success, "editor_color: parse error");
|
||
|
}
|
||
|
} else if (string_equal(key, "editor_ragdoll")) {
|
||
|
//bool ragdoll = atoi(tokeniser.getToken()) != 0;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
} else if (string_equal(key, "editor_mins")) {
|
||
|
entityClass.sizeSpecified = true;
|
||
|
const char *value;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
|
||
|
if (!string_empty(value) && !string_equal(value, "?")) {
|
||
|
entityClass.fixedsize = true;
|
||
|
bool success = string_parse_vector3(value, entityClass.mins);
|
||
|
ASSERT_MESSAGE(success, "editor_mins: parse error");
|
||
|
}
|
||
|
} else if (string_equal(key, "editor_maxs")) {
|
||
|
entityClass.sizeSpecified = true;
|
||
|
const char *value;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
|
||
|
if (!string_empty(value) && !string_equal(value, "?")) {
|
||
|
entityClass.fixedsize = true;
|
||
|
bool success = string_parse_vector3(value, entityClass.maxs);
|
||
|
ASSERT_MESSAGE(success, "editor_maxs: parse error");
|
||
|
}
|
||
|
} else if (string_equal(key, "editor_usage")) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage));
|
||
|
currentString = &usage;
|
||
|
} else if (string_equal_n(key, "editor_usage", 12)) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage));
|
||
|
currentString = &usage;
|
||
|
} else if (string_equal(key, "editor_rotatable")
|
||
|
|| string_equal(key, "editor_showangle")
|
||
|
|| string_equal(key, "editor_showangles") // typo? in prey movables.def
|
||
|
|| string_equal(key, "editor_mover")
|
||
|
|| string_equal(key, "editor_model")
|
||
|
|| string_equal(key, "editor_material")
|
||
|
|| string_equal(key, "editor_combatnode")
|
||
|
|| (!string_empty(last) && string_equal(first.c_str(), "editor_gui"))
|
||
|
|| string_equal_n(key, "editor_copy", 11)) {
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
} else if (!string_empty(last) &&
|
||
|
(string_equal(first.c_str(), "editor_var") || string_equal(first.c_str(), "editor_string"))) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "string";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_float")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "string";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_snd")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "sound";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_bool")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "boolean";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_int")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "integer";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_model")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "model";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) && string_equal(first.c_str(), "editor_color")) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "color";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (!string_empty(last) &&
|
||
|
(string_equal(first.c_str(), "editor_material") || string_equal(first.c_str(), "editor_mat"))) {
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, last).second;
|
||
|
attribute.m_type = "shader";
|
||
|
currentDescription = &attribute.m_description;
|
||
|
currentString = &description;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
|
||
|
} else if (string_equal(key, "inherit")) {
|
||
|
entityClass.inheritanceResolved = false;
|
||
|
ASSERT_MESSAGE(entityClass.m_parent.empty(), "only one 'inherit' supported per entityDef");
|
||
|
const char *token;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
|
||
|
entityClass.m_parent.push_back(token);
|
||
|
}
|
||
|
// begin quake4-specific keys
|
||
|
else if (string_equal(key, "editor_targetonsel")) {
|
||
|
//const char* value =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
} else if (string_equal(key, "editor_menu")) {
|
||
|
//const char* value =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
} else if (string_equal(key, "editor_ignore")) {
|
||
|
//const char* value =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
}
|
||
|
// end quake4-specific keys
|
||
|
// begin ignore prey (unknown/unused?) entity keys
|
||
|
else if (string_equal(key, "editor_light")
|
||
|
|| string_equal(key, "editor_def def_debrisspawner")
|
||
|
|| string_equal(key, "editor_def def_drop")
|
||
|
|| string_equal(key, "editor_def def_guihand")
|
||
|
|| string_equal(key, "editor_def def_mine")) {
|
||
|
//const char* value =
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
|
||
|
}
|
||
|
// end ignore prey entity keys
|
||
|
else {
|
||
|
CopiedString tmp(key);
|
||
|
if (string_equal_n(key, "editor_", 7)) {
|
||
|
globalErrorStream() << "unsupported editor key " << makeQuoted(key);
|
||
|
}
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, key).second;
|
||
|
attribute.m_type = "string";
|
||
|
const char *value;
|
||
|
PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
|
||
|
if (string_equal(value, "}")) { // hack for quake4 powerups.def bug
|
||
|
globalErrorStream() << "entityDef " << makeQuoted(entityClass.m_name.c_str()) << " key "
|
||
|
<< makeQuoted(tmp.c_str()) << " has no value\n";
|
||
|
break;
|
||
|
} else {
|
||
|
attribute.m_value = value;
|
||
|
}
|
||
|
}
|
||
|
tokeniser.nextLine();
|
||
|
}
|
||
|
|
||
|
entityClass.m_comments = usage.c_str();
|
||
|
|
||
|
if (string_equal(entityClass.m_name.c_str(), "light")) {
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "light_radius").second;
|
||
|
attribute.m_type = "vector3";
|
||
|
attribute.m_value = "300 300 300";
|
||
|
}
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "light_center").second;
|
||
|
attribute.m_type = "vector3";
|
||
|
}
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "noshadows").second;
|
||
|
attribute.m_type = "boolean";
|
||
|
attribute.m_value = "0";
|
||
|
}
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "nospecular").second;
|
||
|
attribute.m_type = "boolean";
|
||
|
attribute.m_value = "0";
|
||
|
}
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "nodiffuse").second;
|
||
|
attribute.m_type = "boolean";
|
||
|
attribute.m_value = "0";
|
||
|
}
|
||
|
{
|
||
|
EntityClassAttribute &attribute = EntityClass_insertAttribute(entityClass, "falloff").second;
|
||
|
attribute.m_type = "real";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseEntityDef(Tokeniser &tokeniser)
|
||
|
{
|
||
|
EntityClass *entityClass = Eclass_Alloc();
|
||
|
entityClass->free = &Eclass_Free;
|
||
|
|
||
|
if (!EntityClass_parse(*entityClass, tokeniser)) {
|
||
|
eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
|
||
|
entityClass->free(entityClass);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
EntityClass *inserted = EntityClassDoom3_insertUnique(entityClass);
|
||
|
if (inserted != entityClass) {
|
||
|
globalErrorStream() << "entityDef " << entityClass->name()
|
||
|
<< " is already defined, second definition ignored\n";
|
||
|
eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
|
||
|
entityClass->free(entityClass);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parseBlock(Tokeniser &tokeniser, const char *blockType)
|
||
|
{
|
||
|
if (string_equal(blockType, "entityDef")) {
|
||
|
return EntityClassDoom3_parseEntityDef(tokeniser);
|
||
|
} else if (string_equal(blockType, "model")) {
|
||
|
return EntityClassDoom3_parseModel(tokeniser);
|
||
|
} else {
|
||
|
return EntityClassDoom3_parseUnknown(tokeniser);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool EntityClassDoom3_parse(TextInputStream &inputStream, const char *filename)
|
||
|
{
|
||
|
Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
|
||
|
|
||
|
tokeniser.nextLine();
|
||
|
|
||
|
for (;;) {
|
||
|
const char *blockType = tokeniser.getToken();
|
||
|
if (blockType == 0) {
|
||
|
return true;
|
||
|
}
|
||
|
CopiedString tmp(blockType);
|
||
|
if (!EntityClassDoom3_parseBlock(tokeniser, tmp.c_str())) {
|
||
|
globalErrorStream() << GlobalFileSystem().findFile(filename) << filename << ":"
|
||
|
<< (unsigned int) tokeniser.getLine() << ": " << tmp.c_str()
|
||
|
<< " parse failed, skipping rest of file\n";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
tokeniser.release();
|
||
|
}
|
||
|
|
||
|
|
||
|
void EntityClassDoom3_loadFile(const char *filename)
|
||
|
{
|
||
|
globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
|
||
|
|
||
|
StringOutputStream fullname(256);
|
||
|
fullname << "def/" << filename;
|
||
|
|
||
|
ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str());
|
||
|
if (file != 0) {
|
||
|
EntityClassDoom3_parse(file->getInputStream(), fullname.c_str());
|
||
|
file->release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EntityClass *EntityClassDoom3_findOrInsert(const char *name, bool has_brushes)
|
||
|
{
|
||
|
ASSERT_NOTNULL(name);
|
||
|
|
||
|
if (string_empty(name)) {
|
||
|
return g_EntityClassDoom3_bad;
|
||
|
}
|
||
|
|
||
|
EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name);
|
||
|
if (i != g_EntityClassDoom3_classes.end()
|
||
|
//&& string_equal((*i).first, name)
|
||
|
) {
|
||
|
return (*i).second;
|
||
|
}
|
||
|
|
||
|
EntityClass *e = EntityClass_Create_Default(name, has_brushes);
|
||
|
EntityClass *inserted = EntityClassDoom3_insertUnique(e);
|
||
|
ASSERT_MESSAGE(inserted == e, "");
|
||
|
return inserted;
|
||
|
}
|
||
|
|
||
|
const ListAttributeType *EntityClassDoom3_findListType(const char *name)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void EntityClass_resolveInheritance(EntityClass *derivedClass)
|
||
|
{
|
||
|
if (derivedClass->inheritanceResolved == false) {
|
||
|
derivedClass->inheritanceResolved = true;
|
||
|
EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str());
|
||
|
if (i == g_EntityClassDoom3_classes.end()) {
|
||
|
globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str())
|
||
|
<< " inherited by " << makeQuoted(derivedClass->m_name.c_str()) << "\n";
|
||
|
} else {
|
||
|
EntityClass *parentClass = (*i).second;
|
||
|
EntityClass_resolveInheritance(parentClass);
|
||
|
if (!derivedClass->colorSpecified) {
|
||
|
derivedClass->colorSpecified = parentClass->colorSpecified;
|
||
|
derivedClass->color = parentClass->color;
|
||
|
}
|
||
|
if (!derivedClass->sizeSpecified) {
|
||
|
derivedClass->sizeSpecified = parentClass->sizeSpecified;
|
||
|
derivedClass->mins = parentClass->mins;
|
||
|
derivedClass->maxs = parentClass->maxs;
|
||
|
derivedClass->fixedsize = parentClass->fixedsize;
|
||
|
}
|
||
|
|
||
|
for (EntityClassAttributes::iterator j = parentClass->m_attributes.begin();
|
||
|
j != parentClass->m_attributes.end(); ++j) {
|
||
|
EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class EntityClassDoom3 : public ModuleObserver {
|
||
|
std::size_t m_unrealised;
|
||
|
ModuleObservers m_observers;
|
||
|
public:
|
||
|
EntityClassDoom3() : m_unrealised(2)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void realise()
|
||
|
{
|
||
|
if (--m_unrealised == 0) {
|
||
|
globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n";
|
||
|
GlobalFileSystem().forEachFile("def/", "def", makeCallbackF(EntityClassDoom3_loadFile));
|
||
|
|
||
|
{
|
||
|
for (Models::iterator i = g_models.begin(); i != g_models.end(); ++i) {
|
||
|
Model_resolveInheritance((*i).first.c_str(), (*i).second);
|
||
|
}
|
||
|
}
|
||
|
{
|
||
|
for (EntityClasses::iterator i = g_EntityClassDoom3_classes.begin();
|
||
|
i != g_EntityClassDoom3_classes.end(); ++i) {
|
||
|
EntityClass_resolveInheritance((*i).second);
|
||
|
if (!string_empty((*i).second->m_modelpath.c_str())) {
|
||
|
Models::iterator j = g_models.find((*i).second->m_modelpath);
|
||
|
if (j != g_models.end()) {
|
||
|
(*i).second->m_modelpath = (*j).second.m_mesh;
|
||
|
(*i).second->m_skin = (*j).second.m_skin;
|
||
|
}
|
||
|
}
|
||
|
eclass_capture_state((*i).second);
|
||
|
|
||
|
StringOutputStream usage(256);
|
||
|
|
||
|
usage << "-------- NOTES --------\n";
|
||
|
|
||
|
if (!string_empty((*i).second->m_comments.c_str())) {
|
||
|
usage << (*i).second->m_comments.c_str() << "\n";
|
||
|
}
|
||
|
|
||
|
usage << "\n-------- KEYS --------\n";
|
||
|
|
||
|
for (EntityClassAttributes::iterator j = (*i).second->m_attributes.begin();
|
||
|
j != (*i).second->m_attributes.end(); ++j) {
|
||
|
const char *name = EntityClassAttributePair_getName(*j);
|
||
|
const char *description = EntityClassAttributePair_getDescription(*j);
|
||
|
if (!string_equal(name, description)) {
|
||
|
usage << EntityClassAttributePair_getName(*j) << " : "
|
||
|
<< EntityClassAttributePair_getDescription(*j) << "\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
(*i).second->m_comments = usage.c_str();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_observers.realise();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void unrealise()
|
||
|
{
|
||
|
if (++m_unrealised == 1) {
|
||
|
m_observers.unrealise();
|
||
|
EntityClassDoom3_clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void attach(ModuleObserver &observer)
|
||
|
{
|
||
|
m_observers.attach(observer);
|
||
|
}
|
||
|
|
||
|
void detach(ModuleObserver &observer)
|
||
|
{
|
||
|
m_observers.detach(observer);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
EntityClassDoom3 g_EntityClassDoom3;
|
||
|
|
||
|
void EntityClassDoom3_attach(ModuleObserver &observer)
|
||
|
{
|
||
|
g_EntityClassDoom3.attach(observer);
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_detach(ModuleObserver &observer)
|
||
|
{
|
||
|
g_EntityClassDoom3.detach(observer);
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_realise()
|
||
|
{
|
||
|
g_EntityClassDoom3.realise();
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_unrealise()
|
||
|
{
|
||
|
g_EntityClassDoom3.unrealise();
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_construct()
|
||
|
{
|
||
|
GlobalFileSystem().attach(g_EntityClassDoom3);
|
||
|
|
||
|
// start by creating the default unknown eclass
|
||
|
g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
|
||
|
|
||
|
EntityClassDoom3_realise();
|
||
|
}
|
||
|
|
||
|
void EntityClassDoom3_destroy()
|
||
|
{
|
||
|
EntityClassDoom3_unrealise();
|
||
|
|
||
|
g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad);
|
||
|
|
||
|
GlobalFileSystem().detach(g_EntityClassDoom3);
|
||
|
}
|
||
|
|
||
|
class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef {
|
||
|
};
|
||
|
|
||
|
class EntityClassDoom3API {
|
||
|
EntityClassManager m_eclassmanager;
|
||
|
public:
|
||
|
typedef EntityClassManager Type;
|
||
|
|
||
|
STRING_CONSTANT(Name, "doom3");
|
||
|
|
||
|
EntityClassDoom3API()
|
||
|
{
|
||
|
EntityClassDoom3_construct();
|
||
|
|
||
|
m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
|
||
|
m_eclassmanager.findListType = &EntityClassDoom3_findListType;
|
||
|
m_eclassmanager.forEach = &EntityClassDoom3_forEach;
|
||
|
m_eclassmanager.attach = &EntityClassDoom3_attach;
|
||
|
m_eclassmanager.detach = &EntityClassDoom3_detach;
|
||
|
m_eclassmanager.realise = &EntityClassDoom3_realise;
|
||
|
m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
|
||
|
}
|
||
|
|
||
|
~EntityClassDoom3API()
|
||
|
{
|
||
|
EntityClassDoom3_destroy();
|
||
|
}
|
||
|
|
||
|
EntityClassManager *getTable()
|
||
|
{
|
||
|
return &m_eclassmanager;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#include "modulesystem/singletonmodule.h"
|
||
|
#include "modulesystem/moduleregistry.h"
|
||
|
|
||
|
typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
|
||
|
typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
|
||
|
StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance());
|