本篇是游戏脚本的实现(1)的续篇。
脚本条目的创建
因为ENREY_RULE结构仅包含了行为和条目的规则,所以需要另外的结构数组去存储每个条目的数据。这些新的结构包括了在条目中所使用的文本、布尔值、多重选项,我们使用结构体ENTRY来表示。
//============================================================================
// structure that store all entries fact information.
//============================================================================
typedef struct ENTRY
{
long type; // type of blank entry (ENTRY_TEXT, ENTRY_BOOL, )
union
{
long io_value; // used for saving/loading
long length; // length of text (0 terminator)
long selection; // selection in choice
BOOL bool_value; // BOOL value
long long_value; // long balue
float float_value; // float value
};
char* text; // entry text buffer
ENTRY()
{
memset(this, 0, sizeof(*this));
}
~ENTRY()
{
delete[] text;
}
} *ENTRY_PTR;
在处理脚本条目的过程中,当一个脚本出现了许多条目时,最麻烦的问题也就接踵而来。脚本中的每个行为都要求一个相匹配的ENTRY_RULE结构,其依次包含了一定数量的ENTRY结构。为了更好地处理一个脚本的结构,还需要其他的结构来记录属于脚本行为的每个条目,我们将它命名为SCRPT。
//============================================================================
// structure that store all
//============================================================================
typedef struct SCRIPT
{
long action_index; // [0, number of actions - 1]
long num_entries; // number of entries in this action
ENTRY_PTR entries; // array of entries
SCRIPT* prev; // previous in linked list
SCRIPT* next; // next in linked list
SCRIPT()
{
memset(this, 0, sizeof(*this));
}
~SCRIPT()
{
delete[] entries;
delete next;
}
} *SCRIPT_PTR;
prev和next维护了整个脚本的连接列表,为了构造SCRIPT结构的连接列表,从代表脚本的第一个行为的根结构开始,然后使用next和prev将SCRIPT结构连接起来,如下图所示:
ACTION_TEMPLATE类的整合
理解了行为模板所使用的结构以及所容纳的脚本后,现在开始将它们整合到一起创建一个加载并处理脚本的类。
//============================================================================
// This class encapsulate script save and load.
//============================================================================
typedef class ACTION_TEMPLATE
{
private:
long m_num_actions; // number of actions in template
ACTION_PTR m_root_action; // list of template actions
public:
ACTION_TEMPLATE();
~ACTION_TEMPLATE();
BOOL load_actions(const char* filename);
void free();
long get_num_actions();
ACTION_PTR get_root_action();
ACTION_PTR get_action(long act_index);
SCRIPT_PTR create_script(long act_index);
long get_num_entries_rule(long act_index);
ENTRY_RULE_PTR get_entry_rule(long act_index, long entry_rule_index);
void expand_default_action_text(char* buffer, ACTION_PTR action);
BOOL expand_action_text(char* buffer, SCRIPT_PTR script);
} *ACTION_TEMPLATE_PTR;
实现:
//------------------------------------------------------------------------
// Get quoted line from file.
//------------------------------------------------------------------------
static BOOL _get_quoted_line(FILE* fp, char* data, long max_data_size)
{
int c;
long pos = 0;
// read until a quote is reached (or EOF)
while(1)
{
if((c = fgetc(fp)) == EOF)
return FALSE;
if(c == '"')
{
// read until next quot (or EOF)
while(1)
{
if((c = fgetc(fp)) == EOF)
return FALSE;
// return text when 2nd quote found
if(c == '"')
{
data[pos] = 0;
break;
}
// add acceptable text to line
if(c != 0x0a && c != 0x0d) // if character is not linefeed, not carriage.
{
if(pos < max_data_size-1)
data[pos++] = c;
}
}
break;
}
}
return TRUE;
}
//------------------------------------------------------------------------
// Get word from file.
//------------------------------------------------------------------------
static BOOL _get_word(FILE* fp, char* data, long max_data_size)
{
int c;
long pos = 0;
// reset word to empty
data[0] = 0;
// read until an acceptable character found
while(1)
{
if((c = fgetc(fp)) == EOF)
{
data[0] = 0;
return FALSE;
}
// check for start of word
if(c != 32 && c != 0x0a && c != 0x0d) // if character is not blank, not linefeed, not carriage.
{
data[pos++] = c;
// loop until end of word (or EOF)
while((c = fgetc(fp)) != EOF)
{
// break on acceptable word seperators
if(c == 32 || c == 0x0a || c == 0x0d)
break;
// add if enough room left
if(pos < max_data_size-1)
data[pos++] = c;
}
// add end of line to text
data[pos] = 0;
break;
}
}
return TRUE;
}
//------------------------------------------------------------------------
// Constructor, zero member data.
//------------------------------------------------------------------------
ACTION_TEMPLATE::ACTION_TEMPLATE()
{
memset(this, 0, sizeof(*this));
}
//------------------------------------------------------------------------
// Destructor, release allocated memory.
//------------------------------------------------------------------------
ACTION_TEMPLATE::~ACTION_TEMPLATE()
{
free();
}
//------------------------------------------------------------------------
// Release allocated memory.
//------------------------------------------------------------------------
void ACTION_TEMPLATE::free()
{
delete m_root_action;
m_num_actions = 0;
}
//------------------------------------------------------------------------
// Load an action template.
//------------------------------------------------------------------------
BOOL ACTION_TEMPLATE::load_actions(const char* filename)
{
const int size = 256;
// free previous action structures
free();
FILE* fp;
// open the action file
if((fp = fopen(filename, "rb")) == NULL)
return FALSE;
ACTION_PTR act_ptr = NULL;
// keep looping until end of file found
while(1)
{
char text[size];
// get next quoted action
if(! _get_quoted_line(fp, text, size))
break;
// quit if no action text
if(text[0] == 0)
break;
// allocate on action structure and append it to list
ACTION_PTR act = new ACTION;
act->next = NULL;
if(act_ptr == NULL)
m_root_action = act;
else
act_ptr->next = act;
act_ptr = act;
// copy action text
strcpy(act->text, text);
// store action index
act->index = m_num_actions;
// increase the number of actions loaded
m_num_actions++;
size_t text_len = strlen(text);
// count the number of entries in the action
for(size_t i = 0; i < text_len; i++)
{
if(text[i] == '~')
act->num_entries_rule++;
}
// allocated and read in entries (if any)
if(act->num_entries_rule != 0)
{
act->entries_rule = new ENTRY_RULE[act->num_entries_rule];
for(short entry_index = 0; entry_index < act->num_entries_rule; entry_index++)
{
ENTRY_RULE_PTR entry_rule = &act->entries_rule[entry_index];
// get type of entry
_get_word(fp, text, size);
if(!stricmp(text, "TEXT")) // TEXT type, nothing data.
{
entry_rule->type = ENTRY_TEXT;
}
else if(!stricmp(text, "INT")) // LONG type, get min and max values
{
entry_rule->type = ENTRY_INT;
// get min value
_get_word(fp, text, size);
entry_rule->long_min = atol(text);
// get max value
_get_word(fp, text, size);
entry_rule->long_max = atol(text);
}
else if(!stricmp(text, "FLOAT")) // FLOAT type, get min and max values
{
entry_rule->type = ENTRY_FLOAT;
// get min value
_get_word(fp, text, size);
entry_rule->float_min = (float) atof(text);
// get max value
_get_word(fp, text, size);
entry_rule->float_max = (float) atof(text);
}
else if(!stricmp(text, "BOOL")) // BOOL type, no options.
{
entry_rule->type = ENTRY_BOOL;
}
else if(!stricmp(text, "CHOICE")) // CHOICE type, get number of entries and entry's texts.
{
entry_rule->type = ENTRY_CHOICE;
// get the number of choices
_get_word(fp, text, size);
entry_rule->num_choices = atol(text);
entry_rule->choices = new char_ptr[entry_rule->num_choices];
// get each entry text
for(long choice_index = 0; choice_index < entry_rule->num_choices; choice_index++)
{
_get_quoted_line(fp, text, size);
entry_rule->choices[choice_index] = strdup(text);
}
}
}
}
}
fclose(fp);
return TRUE;
}
//------------------------------------------------------------------------
// Return number of actions in template.
//------------------------------------------------------------------------
long ACTION_TEMPLATE::get_num_actions()
{
return m_num_actions;
}
//------------------------------------------------------------------------
// Return root ACTION structure.
//------------------------------------------------------------------------
ACTION_PTR ACTION_TEMPLATE::get_root_action()
{
return m_root_action;
}
//------------------------------------------------------------------------
// Return specified ACTION structure.
//------------------------------------------------------------------------
ACTION_PTR ACTION_TEMPLATE::get_action(long act_index)
{
// return error if higher than number of actions
if(act_index >= m_num_actions)
return NULL;
ACTION_PTR act_ptr = m_root_action;
// scan list
while(act_ptr)
{
if(act_ptr->index == act_index)
return act_ptr;
act_ptr = act_ptr->next;
}
return NULL;
}
//------------------------------------------------------------------------
// Create script from specified action index.
//------------------------------------------------------------------------
SCRIPT_PTR ACTION_TEMPLATE::create_script(long act_index)
{
// make sure it is a valid action
if(act_index >= m_num_actions)
return NULL;
ACTION_PTR act_ptr;
// get pointer to action
if((act_ptr = get_action(act_index)) == NULL)
return NULL;
// create new SCRIPT structure
SCRIPT_PTR script = new SCRIPT;
script->action_index = act_index;
script->num_entries = act_ptr->num_entries_rule;
script->entries = new ENTRY[script->num_entries];
// set up each entry
for(long i = 0; i < script->num_entries; i++)
{
script->entries[i].type = act_ptr->entries_rule[i].type;
// set up entry data based on type
switch(script->entries[i].type)
{
case ENTRY_TEXT:
script->entries[i].text = NULL;
break;
case ENTRY_INT:
script->entries[i].long_value = act_ptr->entries_rule[i].long_min;
break;
case ENTRY_FLOAT:
script->entries[i].float_value = act_ptr->entries_rule[i].float_min;
break;
case ENTRY_BOOL:
script->entries[i].bool_value = TRUE;
break;
case ENTRY_CHOICE:
script->entries[i].selection = 0;
break;
}
}
return script;
}
//------------------------------------------------------------------------
// Return number of entries rule in the specified action.
//------------------------------------------------------------------------
long ACTION_TEMPLATE::get_num_entries_rule(long act_index)
{
// get pointer to specified action
ACTION_PTR act_ptr = get_action(act_index);
// return 0 if on error
if(act_ptr == NULL)
return 0;
return act_ptr->num_entries_rule;
}
//------------------------------------------------------------------------
// Return specified entry rule in specified action.
//------------------------------------------------------------------------
ENTRY_RULE_PTR ACTION_TEMPLATE::get_entry_rule(long act_index, long entry_rule_index)
{
ACTION_PTR act_ptr = get_action(act_index);
if(act_ptr == NULL || entry_rule_index >= act_ptr->num_entries_rule)
return NULL;
return &(act_ptr->entries_rule[entry_rule_index]);
}
//------------------------------------------------------------------------
// Expand action text using min/first/TRUE choice values.
//------------------------------------------------------------------------
void ACTION_TEMPLATE::expand_default_action_text(char* buffer, ACTION_PTR action)
{
// copy action text into buffer if no entries rule
if(action->num_entries_rule == 0)
{
strcpy(buffer, action->text);
return;
}
// expand entry types into action text
size_t buf_pos = 0;
long rule_index = 0;
size_t text_len = strlen(action->text);
const size_t mem_size = 256;
for(size_t i = 0; i < text_len; i++)
{
char memory[mem_size];
// expand the entry into text based on value, text, etc.
if(action->text[i] == '~')
{
if(action->entries_rule[rule_index].type == ENTRY_TEXT)
{
memcpy(&buffer[buf_pos], "(*TEXT*)", 8);
buf_pos += 8;
}
else if(action->entries_rule[rule_index].type == ENTRY_INT)
{
sprintf(memory, "(*%lu*)", action->entries_rule[rule_index].long_min);
memcpy(&buffer[buf_pos], memory, strlen(memory));
buf_pos += strlen(memory);
}
else if(action->entries_rule[rule_index].type == ENTRY_FLOAT)
{
sprintf(memory, "(*%lf*)", action->entries_rule[rule_index].float_min);
memcpy(&buffer[buf_pos], memory, strlen(memory));
buf_pos += strlen(memory);
}
else if(action->entries_rule[rule_index].type == ENTRY_BOOL)
{
memcpy(&buffer[buf_pos], "(*TRUE*)", 8);
buf_pos += 8;
}
else if(action->entries_rule[rule_index].type == ENTRY_CHOICE)
{
memcpy(&buffer[buf_pos], "(*", 2);
buf_pos += 2;
char* choice = action->entries_rule[rule_index].choices[0];
size_t choice_len = strlen(choice);
memcpy(&buffer[buf_pos], choice, choice_len);
buf_pos += choice_len;
memcpy(&buffer[buf_pos], "*)", 2);
buf_pos += 2;
}
rule_index++;
}
else
buffer[buf_pos++] = action->text[i];
}
buffer[buf_pos] = 0;
}
//------------------------------------------------------------------------
// Expand action text using selections.
//------------------------------------------------------------------------
BOOL ACTION_TEMPLATE::expand_action_text(char* buffer, SCRIPT_PTR script)
{
// get a pointer to the specified action
ACTION_PTR act_ptr = get_action(script->action_index);
if(act_ptr == NULL)
return FALSE;
// copy action text into buffer if no entries
if(act_ptr->num_entries_rule == 0)
{
strcpy(buffer, act_ptr->text);
return TRUE;
}
// expand entry types into action text
size_t buf_pos = 0;
size_t entry_index = 0;
char memory[256];
size_t memory_length;
size_t act_text_length = strlen(act_ptr->text);
for(size_t i = 0; i < act_text_length; i++)
{
// expand the entry into text based on values, text, etc.
if(act_ptr->text[i] == '~')
{
if(act_ptr->entries_rule[entry_index].type == ENTRY_TEXT &&
script->entries[entry_index].type == ENTRY_TEXT)
{
memcpy(&buffer[buf_pos], "(*", 2);
buf_pos += 2;
if(script->entries[entry_index].text)
{
for(long j = 0; j < 32; j++) // copy at most 32 charaters
{
if(script->entries[entry_index].text[j] == 0)
break;
buffer[buf_pos++] = script->entries[entry_index].text[j];
}
}
memcpy(&buffer[buf_pos], "*)", 2);
buf_pos += 2;
}
else if(act_ptr->entries_rule[entry_index].type == ENTRY_INT)
{
sprintf(memory, "(*%lu*)", script->entries[entry_index].long_value);
memory_length = strlen(memory);
memcpy(&buffer[buf_pos], memory, memory_length);
buf_pos += memory_length;
}
else if(act_ptr->entries_rule[entry_index].type == ENTRY_FLOAT)
{
sprintf(memory, "(*%lf*)", script->entries[entry_index].float_value);
memory_length = strlen(memory);
memcpy(&buffer[buf_pos], memory, memory_length);
buf_pos += memory_length;
}
else if(act_ptr->entries_rule[entry_index].type == ENTRY_BOOL)
{
if(script->entries[entry_index].bool_value)
memcpy(&buffer[buf_pos], "(*TRUE *)", 9);
else
memcpy(&buffer[buf_pos], "(*FALSE*)", 9);
buf_pos += 9;
}
else if(act_ptr->entries_rule[entry_index].type == ENTRY_CHOICE)
{
memcpy(&buffer[buf_pos], "(*", 2);
buf_pos += 2;
long sel = script->entries[entry_index].selection;
char* choice = act_ptr->entries_rule[entry_index].choices[sel];
size_t choice_len = strlen(choice);
memcpy(&buffer[buf_pos], choice, choice_len);
buf_pos += choice_len;
memcpy(&buffer[buf_pos], "*)", 2);
buf_pos += 2;
}
entry_index++;
}
else
buffer[buf_pos++] = act_ptr->text[i];
}
buffer[buf_pos] = 0;
return TRUE;
}