/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see .
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/Main/editor/ObjCScript.cpp $
* $Revision: 1.1.1.1 $
* $Date: 2003-08-26 03:57:38 $
* $Author: kevinb $
*
* Script loading and saving from editor.
*
* $Log: not supported by cvs2svn $
*
* 29 10/08/98 4:24p Kevin
* Changed code to comply with memory library usage. Always use mem_malloc
* , mem_free and mem_strdup
*
* 28 6/23/98 3:04p Samir
* sped up script list generation.
*
* 27 5/04/98 3:22p Samir
* ability to select which scripts you want to generate scriptwiz info
* for.
*
* 26 4/21/98 12:16a Samir
* Hopefully if you change script definition, it's reflected as you edit
* it.
*
* 25 4/19/98 9:56p Chris
* AI system is integrated with OSIRIS. Path system is integrated with
* the AI system. Bugs will ensue.
*
* 24 4/15/98 5:56p Chris
* Made the AI system script friendly
*
* 23 3/16/98 11:19a Jason
* got scripts working with multiplayer
*
* 22 2/25/98 4:06p Samir
* Added function to generate a table of all scripts on disk.
*
* 21 1/22/98 6:22p Samir
* Parse script parameters now.
*
* 20 11/16/97 4:35p Samir
* Took out annoying mprintfs.
*
* 19 9/25/97 5:28p Samir
* Even newer script code due to more changes in ObjCScript.cpp
*
* 18 9/24/97 2:56p Samir
* New ScriptWizard functionality, without extra commenting in files.
*
* 17 9/22/97 5:59p Samir
* Changed ObjCScript system, so everything is broken, but it shouldn't
* crash the game.
*
* 16 9/12/97 4:14p Samir
* Fixed up list of events and strings.
*
* 15 9/10/97 1:57p Samir
* Redid FindScriptFile functions, adding scripts to script files involves
* specifying a type also.
*
* 14 9/09/97 1:04p Samir
* Added code to create new script and save level script name.
*
* 13 9/08/97 4:29p Samir
* Added EVT_DESTROYED support to editing code.
*
* 12 9/04/97 4:39p Matt
* Added includes needed as a result of removing includes from d3edit.h
*
* 11 8/21/97 6:00p Samir
* A whole lot of script editing stuff added for ScriptWizard.
*
* 10 8/18/97 12:14p Samir
* Added function to get the index of a script name in the script list.
*
* 9 8/15/97 6:32p Samir
* Use a more generic editor script interface instead of specifying
* default scripts, etc.
*
* 8 8/13/97 5:39p Samir
* Use CompileScript to compile a script now, and we compile default
* script at startup of editor.
*
* 7 8/12/97 3:23p Samir
* D3XReallocProgram has more arguments.
*
* 6 8/06/97 10:38a Samir
* Don't load in script obejct file. Don't need to.
*
* 5 8/04/97 6:45p Samir
* Fixed crash when freeing a null script.
*
* 4 8/04/97 4:13p Samir
* Added code to load and save the default script code.
*
* $NoKeywords: $
*/
#include "stdafx.h"
#include "ObjCScript.h"
#include "editor.h"
#include "cfile.h"
#include "ddio.h"
#include "d3x.h"
#include "manage.h"
#include "mission.h"
#include "osiris.h"
#include "gamefile.h"
#include "pserror.h"
#include "mem.h"
#include
#include
#include
#define MAX_SCRIPT_LINE_SIZE 800
// script name for current level
char Level_script_name[MAX_SCRFILENAME];
//Currently loaded script file
char *Default_script_source = NULL;
char *Level_script_source = NULL;
//Table of scripts within script file
tScriptName Script_names[MAX_SCRIPTS];
// INCREMENT MAX_SCREVTS in ObjCScript when you add a new event!
char *Script_evt_names[MAX_SCREVTS] = {
"EVT_AI_FRAME",
"EVT_AI_NOTIFY",
"EVT_AI_INIT",
"EVT_CREATED",
"EVT_COLLIDE",
"EVT_DAMAGED",
"EVT_INTERVAL",
"EVT_DESTROY",
"EVT_USE",
"EVT_CLIENT_AI",
"EVT_CLIENT_CREATED",
"EVT_CLIENT_COLLIDE",
"EVT_CLIENT_DAMAGED",
"EVT_CLIENT_INTERVAL",
"EVT_CLIENT_DESTROY",
};
ushort Script_evt_ids[MAX_SCREVTS] = {
EVT_AI_FRAME,
EVT_AI_NOTIFY,
EVT_AI_INIT,
EVT_CREATED,
EVT_COLLIDE,
EVT_DAMAGED,
EVT_INTERVAL,
EVT_DESTROY,
EVT_USE,
EVT_CLIENT_AI,
EVT_CLIENT_CREATED,
EVT_CLIENT_COLLIDE,
EVT_CLIENT_DAMAGED,
EVT_CLIENT_INTERVAL,
EVT_CLIENT_DESTROY
};
// function to find first free slot in script list.
int FindFreeScriptSlot();
char *GotoScriptInText(char *text, const char *script);
int GenerateScriptParamInfo(int id, const char *script_text);
bool NewScript(const char *filename)
{
char path[_MAX_PATH];
CFILE *file;
ddio_MakePath(path,LocalLevelsDir,filename,NULL);
// create the file
file = cfopen(path, "wt");
if (!file) return false;
cfclose(file);
return true;
}
//Loads the script into into memory (Don't forget to call FreeScript() to free the memory when done)
//Returns true on success
char *LoadScript(const char *filename)
{
char *source;
char file_path[256];
CFILE *file;
CString temp;
int size;
char buffer[MAX_SCRIPT_LINE_SIZE];
memset(buffer,0,MAX_SCRIPT_LINE_SIZE);
ddio_MakePath(file_path,LocalLevelsDir,filename,NULL);
// mprintf((0,"Loading script from %s\n",file_path));
if(!cfexist(file_path))
{
return false;
}
file=cfopen(file_path,"rt");
do
{
size=cf_ReadString(buffer,MAX_SCRIPT_LINE_SIZE-2,file);
buffer[size]='\xd';
buffer[size+1]='\xa';
buffer[size+2]='\0';
temp+=buffer;
memset(buffer,0,size);
}while(!cfeof(file));
char *tmp_script;
size=strlen(temp.GetBuffer(1));
tmp_script=(char *)mem_malloc(size+1);
ASSERT(tmp_script);
strcpy(tmp_script,temp.GetBuffer(1));
if(size>2) tmp_script[size-2]='\0';
temp=tmp_script;
if(tmp_script) mem_free(tmp_script);
cfclose(file);
source = (char *)mem_malloc(strlen(temp.GetBuffer(1))+1);
if(!source) return false;
strcpy(source,temp.GetBuffer(1));
return source;
}
void SaveScript(const char *filename, char *script)
{
CFILE *file;
char file_path[256];
ddio_MakePath(file_path,LocalLevelsDir, filename,NULL);
file=cfopen(file_path,"wt");
cf_WriteString(file,script);
// mprintf((0,"Saving script to %s\n",file_path));
cfclose(file);
}
void FreeScript(char *script)
{
mem_free(script);
}
// Generates a program from a script
bool CompileScript(tD3XProgram *program, char *script)
{
int nscr, nstr, d3xlen;
char *strbuf; // string buffer grabbed from compile
tD3XInstruction *ins; // temporary holder for code
tD3XPMap *map; // temporary holder for map
if (!script) {
mprintf((1, "Unable to compile null script!\n"));
return false;
}
if (program == NULL) {
mprintf((1, "You can't compile an uninitialized script!\n"));
return false;
}
if(!osi_Compile(script, &d3xlen, &ins, &nscr, &map, &nstr, &strbuf)) {
mprintf((1, "Script failed to compile.\n"));
return false;
}
D3XReallocProgram(program, d3xlen, nscr, nstr);
D3XLoadProgramFromComponents(program, ins, map, strbuf);
return true;
}
void SaveScriptCode(const char *filename, tD3XProgram *program)
{
CFILE *file;
char file_path[256];
// write out default script program
ddio_MakePath(file_path,LocalLevelsDir,filename,NULL);
file = cfopen(file_path, "wb");
D3XSaveProgram(file, program);
mprintf((0, "Saving %s.\n", filename));
cfclose(file);
}
//////////////////////////////////////////////////////////////////////////////
// LIST FUNCTIONS
inline int AddScriptToList(char *name, ushort type, bool custom)
{
int slot = FindFreeScriptSlot();
strcpy(Script_names[slot].name, name);
Script_names[slot].used = true;
Script_names[slot].type = type;
Script_names[slot].iscustom = custom;
Script_names[slot].free_parameters();
return slot;
}
// Resets all entries in script list to null.
void ResetScriptList()
{
int i;
for (i = 0; i < MAX_SCRIPTS; i++)
Script_names[i].used = false;
}
// This function generates ScriptWizard comment header information for script files
// if it doesn't already have it. This should be called whenever a script file is loaded and compiled
// via script wizard.
// returns true
bool GenerateScriptWizardInfo(tD3XProgram *prog, bool custom)
{
int i;
if (!prog) {
Int3();
return false;
}
for (i = 0; i < prog->nscripts; i++)
{
int slot;
slot = AddScriptToList(prog->map[i].name, prog->map[i].type, custom);
Script_names[slot].line = 0;
}
return true;
}
// This function actually tries to go throught the script source to find script names.
// to find a script name we use the following algorithmn
//
// find one line containing:
// trigger || object || level && script in succession
// next word will be the script name
// following script name will be '(' then look for endscript
// repeat.
bool GenerateScriptWizardInfo(char *script, bool custom)
{
char word[256];
bool inword;
int i, index, line;
int state; // 0 = nothing, 1 = typeword, 2 = scriptword
int slot, strsize;
ushort type=0xffff;
inword = false;
index = 0;
state = 0;
line = 1;
slot = 0;
if (script == NULL)
return false;
strsize = strlen(script);
for (i = 0; i < strsize; i++)
{
char ch;
ch = script[i];
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' && ch != '(' && ch != ')' && ch != ',') {
// no whitespace
if (!inword) inword = true;
if (inword) word[index++] = ch;
}
else {
// whitespace
// word is complete, check if a keyword
if (ch == '\n') line++;
word[index] = 0;
if (state==0) {
if (!strcmp(word, "object")) type = REF_OBJTYPE;
else if (!strcmp(word, "trigger")) type = REF_TRIGTYPE;
else if (!strcmp(word, "level")) type = REF_LEVELTYPE;
else type = 0xffff;
if (type != 0xffff) {
state = 1;
}
}
else if (state == 1) {
if (!strcmp(word, "script")) state = 2;
else state = 0;
}
else if (state == 2) {
// if we are in state 2, then we have PROBABLY FOUND a script name, saved_ptr defined.
if (inword) {
slot = AddScriptToList(word, type, custom);
Script_names[slot].line = line;
i += GenerateScriptParamInfo(slot, &script[i]);
state = 3;
}
}
else if (state == 3) {
// check for endscript, then we go to state 0
if (!strcmp(word, "endscript"))
state = 0;
}
index = 0;
inword = false;
}
}
return true;
}
// create paramameter table for script
int GenerateScriptParamInfo(int id, const char *script_text)
{
char word[256];
char saved_word[256];
bool inword = false;
int index = 0;
int state = 0;
int i, strsize = strlen(script_text);
for (i = 0; i < strsize; i++)
{
char ch;
ch = script_text[i];
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' && ch != '(' && ch != ')' && ch != ',') {
// no whitespace
if (!inword) inword = true;
if (inword) word[index++] = ch;
}
else {
// word is complete, check if a keyword
word[index] = 0;
// if we are in state 1, then we have PROBABLY FOUND a script name, make sure name matches
if (state == 0) {
// in parameter definition if we've reached a ')', reset state to start
if (Script_names[id].nparms == MAX_SCRPARAMS) {
OutrageMessageBox("Parameter parsing list size maxed.");
return i;
}
else if (ch == ')')
break;
else if (inword) {
strcpy(saved_word, word);
state = 1;
}
}
else if (state == 1) {
// in parameter name
if (inword) {
Script_names[id].add_parameter(saved_word, word);
state = 0; // read next parameter
}
if (ch == ')')
break;
}
index = 0;
inword = false;
}
}
return i;
}
char *GotoScriptInText(char *text, const char *script)
{
char word[256];
bool inword;
int i, index, line;
int state; // 0 = nothing, 1 = typeword, 2 = scriptword
int strsize = strlen(text);
inword = false;
index = 0;
state = 0;
line = 1;
for (i = 0; i < strsize; i++)
{
char ch;
ch = text[i];
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' && ch != '(') {
// no whitespace
if (!inword) inword = true;
if (inword) word[index++] = ch;
}
else {
// whitespace
if (ch == '\n') line++;
if (inword) {
// word is complete, check if a keyword
word[index] = 0;
if (!strcmp(word, "trigger") || !strcmp(word, "object") || !strcmp(word, "level")) {
if (state == 0) state = 1;
else state = 0; // type word out of sequence
}
else if (!strcmp(word, "script")) {
if (state == 1) state = 2;
else state = 0;
}
else {
// if we are in state 2, then we have PROBABLY FOUND a script name
if (state == 2) {
if (!strcmp(word, script)) return &text[i];
else state = 0;
}
else state = 0;
}
index = 0;
inword = false;
}
}
}
return NULL;
}
// These functions manage enumeration of all script files located in the script directory
static int hGameFile = -1, hInitialFile = -1;
static char TempStr[256];
char *StartScriptFileList()
{
char path[_MAX_PATH];
ddio_MakePath(path, LocalLevelsDir, "*.scr", NULL);
if (!ddio_FindFileStart(path, TempStr)) return NULL;
return TempStr;
}
char *GetNextScriptFile()
{
if (!ddio_FindNextFile(TempStr)) return NULL;
return TempStr;
}
void EndScriptFileList()
{
ddio_FindFileClose();
}
//////////////////////////////////////////////////////////////////////////////
// Script source management
int FindScriptIDFromName(const char *name)
{
int i;
for (i = 0; i < MAX_SCRIPTS; i++)
if (Script_names[i].used && strcmp(name, Script_names[i].name) == 0) return i;
return -1;
}
char *FindEventNameFromID(int id)
{
for (int i = 0; i < MAX_SCREVTS; i++)
if (id == Script_evt_ids[i]) return Script_evt_names[i];
return NULL;
}
int FindEventIDFromName(const char *name)
{
for (int i = 0; i < MAX_SCREVTS; i++)
if (strcmp(name, Script_evt_names[i]) == 0) return Script_evt_ids[i];
return 0;
}
// these functions add ScriptWizard formatting
#define SCRHDR_FORMAT "%s script %s()"
#define SCRIPT_FORMAT "// %s\r\n//\t\r\n%s\r\n\r\n\r\nendscript\r\n\r\n\r\n"
char *AddScriptBlockToScript(char *script, const char *newname, const char *type_str)
{
char *newsrc, *strline;
char newhdr[128];
char newblk[384];
int slot, line = 0, type = 0;
strline = script;
while (strline) { line++; strline = strchr(strline, '\n'); if (strline) strline++; }
if (!strcmp(type_str, "object")) type = REF_OBJTYPE;
else if (!strcmp(type_str, "trigger")) type = REF_TRIGTYPE;
else if (!strcmp(type_str, "level")) type = REF_LEVELTYPE;
sprintf(newhdr, SCRHDR_FORMAT, type_str, newname);
sprintf(newblk, SCRIPT_FORMAT, newname, newhdr);
newsrc = (char *)mem_malloc(strlen(script) + strlen(newblk) + 1);
if (!newsrc) {
mprintf((1, "Allocation failure in creating new script buffer.\n"));
return NULL;
}
sprintf(newsrc, "%s%s", script, newblk);
slot = AddScriptToList((char *)newname, type, false);
Script_names[slot].line = line;
return newsrc;
}
#define EVT_STATEMENT "\r\n\r\nevent '%s'\r\n//insert code here\r\ncomplete\r\n"
char *AddEventBlockToScript(char *script, const char *evtname, const char *scriptname)
{
// we must first find the requested script, then add the event BEFORE the next 'endscript'
char *txtstart, *newtxt;
char *preword;
char word[256];
bool inword;
int i, index;
int strsize;
inword = false;
index = 0;
txtstart = GotoScriptInText(script, scriptname);
if (!txtstart) return NULL;
preword = txtstart;
strsize = strlen(script);
for (i = 0; i < strsize; i++)
{
char ch;
ch = txtstart[i];
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
// no whitespace
if (!inword) inword = true;
if (inword) word[index++] = ch;
}
else {
// whitespace
if (inword) {
// word is complete, check if a keyword, save location of word if not a keyword
word[index] = 0;
if (!strcmp(word, "endscript")) {
// okay, we can now add the event block at preword.
int prelen = (int)(preword - script);
int postlen = (int)strlen(script) - prelen;
char *preblk = new char[prelen+1];
char *postblk = new char[postlen+1];
char evtblk[256];
strncpy(preblk, script, prelen);
preblk[prelen] = 0;
strncpy(postblk, preword, postlen);
postblk[postlen] = 0;
sprintf(evtblk, EVT_STATEMENT, evtname);
newtxt = (char *)mem_malloc(strlen(preblk)+strlen(postblk)+strlen(evtblk)+1);
sprintf(newtxt, "%s%s%s", preblk, evtblk, postblk);
delete[] postblk;
delete[] preblk;
return newtxt;
}
else preword = &txtstart[i];
index = 0;
inword = false;
}
}
}
return NULL;
}
// generates script list from all script files.
void GenerateScriptListFromAllFiles(int mask)
{
char *filename;
//@@ tD3XProgram *m_ScriptCode;
//@@ m_ScriptCode = D3XReallocProgram(NULL, 0, 0, 0);
// initialize script list.
ResetScriptList();
// compile all script files and place into script list.
filename = StartScriptFileList();
while (filename)
{
// compile script first. if we failed, then display a messagebox giving the warning
if (!strcmpi(filename, DEFAULT_SCRIPT_NAME) && (mask & DEFAULT_SCRIPT_MASK))
GenerateScriptListFromFile(filename);
else if ((mask & CUSTOM_SCRIPT_MASK) && strcmpi(filename, DEFAULT_SCRIPT_NAME))
GenerateScriptListFromFile(filename);
filename = GetNextScriptFile();
}
EndScriptFileList();
//@@ D3XFreeProgram(m_ScriptCode);
}
// generates script list from one file
void GenerateScriptListFromFile(const char *fname)
{
char *script;
script = LoadScript(fname);
if (script) {
if (strcmpi(fname, DEFAULT_SCRIPT_NAME) == 0)
GenerateScriptWizardInfo(script, false);
else
GenerateScriptWizardInfo(script, true);
FreeScript(script);
}
}
//@@
//@@// ****************************************************************************
//@@// INTERNAL FUNCTIONS
//@@// ****************************************************************************
//@@
//@@// function to find first free slot in default script list.
int FindFreeScriptSlot()
{
int i;
for (i = 0; i < MAX_SCRIPTS; i++)
if (!Script_names[i].used) return i;
return -1;
}
// other stuff
void tScriptName::add_parameter(const char *type, const char *parm)
{
ASSERT(nparms < MAX_SCRPARAMS);
paramtypes[nparms] = new char[strlen(type)+1];
parameters[nparms] = new char[strlen(parm)+1];
strcpy(parameters[nparms], parm);
strcpy(paramtypes[nparms], type);
nparms++;
}
void tScriptName::free_parameters()
{
for (int i = 0; i < nparms; i++)
{
if (parameters[i]) {
delete[] (parameters[i]);
parameters[i] = NULL;
}
if (paramtypes[i]) {
delete[] (paramtypes[i]);
paramtypes[i] = NULL;
}
}
nparms = 0;
}