Rewrite stats loading for FEATURE_STATS.

The same should be doable for all BASE_STATS.
master
Cyp 2010-12-26 01:23:12 +01:00
parent 6f71d8925e
commit 7481694e9f
5 changed files with 344 additions and 93 deletions

View File

@ -72,13 +72,7 @@ FEATURE_STATS* oilResFeature = NULL;
//specifies how far round (in tiles) a constructor droid sound look for more wreckage
#define WRECK_SEARCH 3
struct featureTypeMap
{
const char *typeStr;
FEATURE_TYPE type;
};
static const struct featureTypeMap map[] =
static const StringToEnum<FEATURE_TYPE> mapUnsorted_FEATURE_TYPE[] =
{
{ "PROPULSION_TYPE_HOVER WRECK", FEAT_HOVER },
{ "TANK WRECK", FEAT_TANK },
@ -93,6 +87,7 @@ static const struct featureTypeMap map[] =
{ "TREE", FEAT_TREE },
{ "SKYSCRAPER", FEAT_SKYSCRAPER }
};
static const StringToEnumMap<FEATURE_TYPE> map_FEATURE_TYPE(mapUnsorted_FEATURE_TYPE, ARRAY_SIZE(mapUnsorted_FEATURE_TYPE));
void featureInitVars(void)
@ -102,112 +97,54 @@ void featureInitVars(void)
oilResFeature = NULL;
}
static void featureType(FEATURE_STATS* psFeature, const char *pType)
FEATURE_STATS::FEATURE_STATS(LineView line)
: BASE_STATS(REF_FEATURE_START + line.line(), line.s(0))
, subType(line.e(7, map_FEATURE_TYPE))
, psImd(line.imdShape(6))
, baseWidth(line.u16(1))
, baseBreadth(line.u16(2))
, tileDraw(line.b(8))
, allowLOS(line.b(9))
, visibleAtStart(line.b(10))
, damageable(line.b(3))
, body(line.u32(5))
, armourValue(line.u32(4))
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(map); i++)
if (damageable && body == 0)
{
if (strcmp(pType, map[i].typeStr) == 0)
{
psFeature->subType = map[i].type;
return;
}
debug(LOG_ERROR, "The feature \"%s\", ref %d is damageable, but has no body points! The files need to be updated / fixed. Assigning 1 body point to feature.", pName, ref);
body = 1;
}
ASSERT(false, "featureType: Unknown feature type");
}
/* Load the feature stats */
BOOL loadFeatureStats(const char *pFeatureData, UDWORD bufferSize)
{
FEATURE_STATS *psFeature;
unsigned int i;
char featureName[MAX_STR_LENGTH], GfxFile[MAX_STR_LENGTH],
type[MAX_STR_LENGTH];
numFeatureStats = numCR(pFeatureData, bufferSize);
// Skip descriptive header
if (strncmp(pFeatureData,"Feature ",8)==0)
{
pFeatureData = strchr(pFeatureData,'\n') + 1;
numFeatureStats--;
}
asFeatureStats = (FEATURE_STATS*)malloc(sizeof(FEATURE_STATS) * numFeatureStats);
if (asFeatureStats == NULL)
{
debug( LOG_FATAL, "Feature Stats - Out of memory" );
abort();
return false;
}
psFeature = asFeatureStats;
TableView table(pFeatureData, bufferSize);
for (i = 0; i < numFeatureStats; i++)
asFeatureStats = new FEATURE_STATS[table.size()];
numFeatureStats = table.size();
for (unsigned i = 0; i < table.size(); ++i)
{
UDWORD Width, Breadth;
int damageable = 0, tileDraw = 0, allowLOS = 0, visibleAtStart = 0;
memset(psFeature, 0, sizeof(FEATURE_STATS));
featureName[0] = '\0';
GfxFile[0] = '\0';
type[0] = '\0';
//read the data into the storage - the data is delimeted using comma's
sscanf(pFeatureData, "%[^','],%d,%d,%d,%d,%d,%[^','],%[^','],%d,%d,%d",
featureName, &Width, &Breadth,
&damageable, &psFeature->armourValue, &psFeature->body,
GfxFile, type, &tileDraw, &allowLOS,
&visibleAtStart);
psFeature->damageable = damageable;
psFeature->tileDraw = tileDraw;
psFeature->allowLOS = allowLOS;
psFeature->visibleAtStart = visibleAtStart;
// These are now only 16 bits wide - so we need to copy them
psFeature->baseWidth = Width;
psFeature->baseBreadth = Breadth;
psFeature->pName = allocateName(featureName);
if (!psFeature->pName)
asFeatureStats[i] = FEATURE_STATS(LineView(table, i));
if (table.isError())
{
debug(LOG_ERROR, "%s", table.getError().toUtf8().constData());
return false;
}
if (psFeature->damageable && psFeature->body == 0)
{
debug(LOG_ERROR, "The feature %s, ref %d, is damageable, but has no body points! The files need to be updated / fixed. " \
"Assigning 1 body point to feature.", psFeature->pName, psFeature->ref);
psFeature->body = 1;
}
//determine the feature type
featureType(psFeature, type);
//and the oil resource - assumes only one!
if (psFeature->subType == FEAT_OIL_RESOURCE)
if (asFeatureStats[i].subType == FEAT_OIL_RESOURCE)
{
oilResFeature = psFeature;
oilResFeature = &asFeatureStats[i];
}
//get the IMD for the feature
psFeature->psImd = (iIMDShape *) resGetData("IMD", GfxFile);
if (psFeature->psImd == NULL)
{
debug( LOG_ERROR, "Cannot find the feature PIE for record %s", getName( psFeature->pName ) );
return false;
}
psFeature->ref = REF_FEATURE_START + i;
//increment the pointer to the start of the next record
pFeatureData = strchr(pFeatureData,'\n') + 1;
//increment the list to the start of the next storage block
psFeature++;
}
return true;
@ -216,11 +153,13 @@ BOOL loadFeatureStats(const char *pFeatureData, UDWORD bufferSize)
/* Release the feature stats memory */
void featureStatsShutDown(void)
{
if(numFeatureStats)
for (unsigned i = 0; i < numFeatureStats; ++i)
{
free(asFeatureStats);
asFeatureStats = NULL;
free(asFeatureStats[i].pName);
}
delete[] asFeatureStats;
asFeatureStats = NULL;
numFeatureStats = 0;
}
/** Deals with damage to a feature

View File

@ -60,6 +60,9 @@ typedef enum _feature_type
/* Stats for a feature */
struct FEATURE_STATS : public BASE_STATS
{
FEATURE_STATS() {}
FEATURE_STATS(LineView line);
FEATURE_TYPE subType; ///< type of feature
iIMDShape* psImd; ///< Graphic for the feature

View File

@ -24,6 +24,7 @@
*
*/
#include <string.h>
#include <algorithm>
#include "lib/framework/frame.h"
#include "lib/framework/strres.h"
@ -123,6 +124,228 @@ static void updateMaxECMStats(UWORD maxValue);
static void updateMaxBodyStats(UWORD maxBody, UWORD maxPower, UWORD maxArmour);
static void updateMaxConstStats(UWORD maxValue);
BASE_STATS::BASE_STATS(unsigned ref, std::string const &str)
: ref(ref)
, pName(allocateName(str.c_str()))
{}
TableView::TableView(char const *buffer, unsigned size)
: buffer(buffer)
{
size = std::min<unsigned>(size, UINT32_MAX - 1); // Shouldn't be a problem...
char const *bufferEnd = buffer + size;
bufferEnd = std::find(buffer, bufferEnd, '\0'); // Truncate buffer at first null character, if any.
// Split into lines.
char const *lineNext = buffer;
while (lineNext != bufferEnd)
{
char const *lineBegin = lineNext;
char const *lineEnd = std::find(lineBegin, bufferEnd, '\n');
lineNext = lineEnd + (lineEnd != bufferEnd);
// Remove stuff that isn't data.
lineEnd = std::find(lineBegin, lineEnd, '#'); // Remove comments, if present.
while (lineBegin != lineEnd && uint8_t(*(lineEnd - 1)) <= ' ')
{
--lineEnd; // Remove trailing whitespace, if present.
}
while (lineBegin != lineEnd && uint8_t(*lineBegin) <= ' ')
{
++lineBegin; // Remove leading whitespace, if present.
}
if (lineBegin == lineEnd)
{
continue; // Remove empty lines.
}
// Split into cells.
size_t firstCell = cells.size();
char const *cellNext = lineBegin;
while (cellNext != lineEnd)
{
char const *cellBegin = cellNext;
char const *cellEnd = std::find(cellBegin, lineEnd, ',');
cellNext = cellEnd + (cellEnd != lineEnd);
cells.push_back(cellBegin - buffer);
}
cells.push_back(lineEnd - buffer + 1);
lines.push_back(std::make_pair(firstCell, cells.size() - firstCell));
}
}
void LineView::setError(unsigned index, char const *error)
{
if (!table.parseError.isEmpty())
{
return; // Already have an error.
}
if (index < numCells)
{
char const *cellBegin = table.buffer + cells[index];
char const *cellEnd = table.buffer + (cells[index + 1] - 1);
char cellDesc[150];
ssprintf(cellDesc, "Line %u, column %u \"%*s\": ", lineNumber, index, std::min<unsigned>(100, cellEnd - cellBegin), cellBegin);
table.parseError = QString::fromUtf8((std::string(cellDesc) + error).c_str());
}
else
{
char cellDesc[50];
ssprintf(cellDesc, "Line %u, column %u: ", lineNumber, index);
table.parseError = QString::fromUtf8((std::string(cellDesc) + error).c_str());
}
}
bool LineView::checkRange(unsigned index)
{
if (index < numCells)
{
return true;
}
setError(index, "Not enough cells.");
return false;
}
int64_t LineView::i(unsigned index, int min, int max)
{
int errorReturn = std::min(std::max(0, min), max); // On error, return 0 if possible.
if (!checkRange(index))
{
return errorReturn;
}
char const *cellBegin = table.buffer + cells[index];
char const *cellEnd = table.buffer + (cells[index + 1] - 1);
if (cellBegin != cellEnd)
{
bool negative = false;
switch (*cellBegin++)
{
case '-':
negative = true;
break;
case '+':
break;
default:
--cellBegin;
break;
}
int64_t absolutePart = 0;
while (cellBegin != cellEnd && absolutePart < int64_t(1000000000)*1000000000)
{
unsigned digit = *cellBegin - '0';
if (digit > 9)
{
break;
}
absolutePart = absolutePart*10 + digit;
++cellBegin;
}
if (cellBegin == cellEnd)
{
int64_t result = negative? -absolutePart : absolutePart;
if (result >= min && result <= max)
{
return result; // Maybe should just have copied the string to null-terminate it, and used scanf...
}
setError(index, "Integer out of range.");
return errorReturn;
}
}
setError(index, "Expected integer.");
return errorReturn;
}
float LineView::f(unsigned index, float min, float max)
{
float errorReturn = std::min(std::max(0.f, min), max); // On error, return 0 if possible.
std::string const &str = s(index);
if (!str.empty())
{
int parsed = 0;
float result;
sscanf(str.c_str(), "%f%n", &result, &parsed);
if ((unsigned)parsed == str.size())
{
if (result >= min && result <= max)
{
return result;
}
setError(index, "Float out of range.");
return errorReturn;
}
}
setError(index, "Expected float.");
return errorReturn;
}
std::string const &LineView::s(unsigned index)
{
if (!checkRange(index))
{
table.returnString.clear();
return table.returnString;
}
char const *cellBegin = table.buffer + cells[index];
char const *cellEnd = table.buffer + (cells[index + 1] - 1);
table.returnString.assign(cellBegin, cellEnd);
return table.returnString;
}
iIMDShape *LineView::imdShape(unsigned int index)
{
std::string const &str = s(index);
iIMDShape *result = (iIMDShape *)resGetData("IMD", str.c_str());
if (result == NULL)
{
setError(index, "Expected PIE shape.");
}
return result;
}
static inline bool stringToEnumFindFunction(std::pair<char const *, unsigned> const &a, char const *b)
{
return strcmp(a.first, b) < 0;
}
unsigned LineView::eu(unsigned int index, std::vector<std::pair<char const *, unsigned> > const &map)
{
typedef std::vector<std::pair<char const *, unsigned> > M;
std::string const &str = s(index);
M::const_iterator i = std::lower_bound(map.begin(), map.end(), str.c_str(), stringToEnumFindFunction);
if (i != map.end() && strcmp(i->first, str.c_str()) == 0)
{
return i->second;
}
// Didn't find it, give error and return 0.
if (table.parseError.isEmpty())
{
std::string values = "Expected one of";
for (i = map.begin(); i != map.end(); ++i)
{
values += std::string(" \"") + i->first + '"';
}
values = values + '.';
setError(index, "Expected one of");
}
return 0;
}
/*******************************************************************************
* Generic stats macros/functions
*******************************************************************************/

View File

@ -23,6 +23,8 @@
#ifndef __INCLUDED_SRC_STATS_H__
#define __INCLUDED_SRC_STATS_H__
#include <utility>
#include "objectdef.h"
/**************************************************************************************

View File

@ -26,6 +26,84 @@
#include "lib/ivis_opengl/ivisdef.h"
#include <vector>
#include <algorithm>
#include "lib/framework/utf.h" // For QString.
static inline bool stringToEnumSortFunction(std::pair<char const *, unsigned> const &a, std::pair<char const *, unsigned> const &b) { return strcmp(a.first, b.first) < 0; }
template <typename Enum>
struct StringToEnum
{
operator std::pair<char const *, unsigned>() const { return std::make_pair(string, value); }
char const * string;
Enum value;
};
template <typename Enum>
struct StringToEnumMap : public std::vector<std::pair<char const *, unsigned> >
{
typedef std::vector<std::pair<char const *, unsigned> > V;
StringToEnumMap(StringToEnum<Enum> const entries[], unsigned numEntries) : V(entries, entries + numEntries) { std::sort(V::begin(), V::end(), stringToEnumSortFunction); }
};
/// Read-only view of file data in "A,1,2\nB,3,4" format as a 2D array-like object. Note — does not copy the file data.
class TableView
{
public:
TableView(char const *buffer, unsigned size);
bool isError() const { return parseError.isEmpty(); }
QString getError() const { return parseError; }
unsigned size() const { return lines.size(); }
private:
friend class LineView;
char const * buffer;
std::vector<uint32_t> cells;
std::vector<std::pair<uint32_t, uint32_t> > lines;
QString parseError;
std::string returnString;
};
/// Read-only view of a line of file data in "A,1,2" format as an array-like object. Note — does not copy the file data.
class LineView
{
public:
LineView(class TableView &table, unsigned lineNumber) : table(table), cells(&table.cells[table.lines.at(lineNumber).first]), numCells(table.lines.at(lineNumber).second), lineNumber(lineNumber) {} ///< This LineView is only valid for the lifetime of the TableView.
void setError(unsigned index, char const *error); ///< Only the first error is saved.
unsigned size() const { return numCells; }
unsigned line() const { return lineNumber; }
bool b(unsigned index) { return i(index, 0, 1); }
int64_t i(unsigned index, int min, int max);
uint32_t i8(unsigned index) { return i(index, INT8_MIN, INT8_MAX); }
uint32_t u8(unsigned index) { return i(index, 0, UINT8_MAX); }
uint32_t i16(unsigned index) { return i(index, INT16_MIN, INT16_MAX); }
uint32_t u16(unsigned index) { return i(index, 0, UINT16_MAX); }
uint32_t i32(unsigned index) { return i(index, INT32_MIN, INT32_MAX); }
uint32_t u32(unsigned index) { return i(index, 0, UINT32_MAX); }
float f(unsigned index, float min = -1.e30f, float max = 1.e30f);
std::string const &s(unsigned index);
template <typename Enum>
Enum e(unsigned index, StringToEnumMap<Enum> const &map) { return (Enum)eu(index, map); }
iIMDShape *imdShape(unsigned index);
private:
unsigned eu(unsigned index, std::vector<std::pair<char const *, unsigned> > const &map);
bool checkRange(unsigned index);
class TableView & table;
unsigned const * cells;
unsigned numCells;
unsigned lineNumber;
};
/*
if any types are added BEFORE 'COMP_BODY' - then Save/Load Game will have to be
altered since it loops through the components from 1->MAX_COMP
@ -206,6 +284,12 @@ typedef enum TRAVEL_MEDIUM
/* Stats common to all stats structs */
struct BASE_STATS
{
BASE_STATS() : ref(0), pName(NULL) {} ///< Only initialised here when using new/delete! TODO Use new/delete only, not malloc()/free().
BASE_STATS(unsigned ref, std::string const &str); ///< Only initialised here when using new/delete! TODO Use new/delete only, not malloc()/free(). TODO Then pName could be a QString...
//Gah, too soon to add destructors to BASE_STATS, thanks to local temporaries that are copied with memcpy()... --- virtual ~BASE_STATS() { free(pName); } ///< pName is only freed here when using new/delete! TODO Use new/delete only, not malloc()/free().
//So this one isn't needed for now, maybe not ever. --- BASE_STATS(BASE_STATS const &stats) : ref(stats.ref), pName(strdup(stats.pName)) {} // TODO Not needed when pName is a QString...
//So this one isn't needed for now, maybe not ever. --- BASE_STATS const &operator =(BASE_STATS const &stats) { ref = stats.ref; free(pName); pName = strdup(stats.pName); return *this; } // TODO Not needed when pName is a QString...
UDWORD ref; /**< Unique ID of the item */
char *pName; /**< pointer to the text id name (i.e. short language-independant name) */
};