本篇是游戲腳本的實現(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;
}