//------------------------------------------------------------------------- /* Copyright (C) 1997, 2005 - 3D Realms Entertainment This file is part of Shadow Warrior version 1.2 Shadow Warrior 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. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Original Source: 1997 - Frank Maddin and Jim Norwood Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms */ //------------------------------------------------------------------------- #include "ns.h" // scriplib.c #include "build.h" #include "names2.h" #include "panel.h" #include "game.h" #include "sprite.h" #include "jsector.h" #include "parent.h" #include "sc_man.h" #include "razemenu.h" #include "quotemgr.h" #include "mapinfo.h" #include "hw_voxels.h" BEGIN_SW_NS ParentalStruct aVoxelArray[MAXTILES]; /* ============================================================================= PARSING STUFF ============================================================================= */ #define MAXTOKEN 255 static char* script_p, * scriptend_p; static char token[MAXTOKEN]; static int grabbed; static int scriptline; static bool endofscript; static bool tokenready; // only true if UnGetToken was just called /* ============== = = LoadScriptFile = ============== */ TArray LoadScriptFile(const char *filename) { FileReader fp; if (!(fp = fileSystem.OpenFileReader(filename)).isOpen()) { // If there's no script file, forget it. return TArray(); } auto scriptbuffer = fp.Read(); if (scriptbuffer.Size() != 0) { scriptbuffer.Push(0); scriptline = 1; endofscript = false; tokenready = false; } return scriptbuffer; } /* ============== = = GetToken = ============== */ void GetToken(bool crossline) { char *token_p; if (tokenready) // is a token already waiting? { tokenready = false; return; } if (script_p >= scriptend_p) { if (!crossline) Printf("Error: Line %i is incomplete\n",scriptline); endofscript = true; return; } // // skip space // skipspace: while (*script_p <= 32) { if (script_p >= scriptend_p) { if (!crossline) Printf("Error: Line %i is incomplete\n",scriptline); endofscript = true; return; } if (*script_p++ == '\n') { if (!crossline) Printf("Error: Line %i is incomplete\n",scriptline); scriptline++; } } if (script_p >= scriptend_p) { if (!crossline) Printf("Error: Line %i is incomplete\n", scriptline); endofscript = true; return; } if (*script_p == '#') // # is comment field { if (!crossline) Printf("Error: Line %i is incomplete\n", scriptline); while (*script_p++ != '\n') if (script_p >= scriptend_p) { endofscript = true; return; } goto skipspace; } // // copy token // token_p = token; while (*script_p > 32 && *script_p != '#') { *token_p++ = *script_p++; if (script_p == scriptend_p) break; ASSERT(token_p != &token[MAXTOKEN]); // Printf("Error: Token too large on line %i\n",scriptline); } *token_p = 0; } // Load all the voxel files using swvoxfil.txt script file // Script file format: // # - Comment // spritenumber (in artfile), voxel number, filename // Ex. 1803 0 medkit2.kvx // 1804 1 shotgun.kvx // etc.... void LoadKVXFromScript(const char* filename) { int lNumber = 0, lTile = 0; // lNumber is the voxel no. and lTile is the editart tile being // replaced. int grabbed = 0; // Number of lines parsed // zero out the array memory with -1's for pics not being voxelized memset(&aVoxelArray[0], -1, sizeof(struct TILE_INFO_TYPE) * MAXTILES); grabbed = 0; // Load the file auto buffer = LoadScriptFile(filename); if (!buffer.Size()) { return; } script_p = (char*)buffer.Data(); scriptend_p = (char*)&buffer.Last(); do { GetToken(true); // Crossing a line boundary on the end of line to first token // of a new line is permitted (and expected) if (endofscript) break; lTile = atol(token); GetToken(false); lNumber = atol(token); GetToken(false); // Load the voxel file into memory if (!voxDefine(lNumber,token)) { // Store the sprite and voxel numbers for later use aVoxelArray[lTile].Voxel = lNumber; // Voxel num } if (lNumber >= nextvoxid) // JBF: so voxels in the def file append to the list nextvoxid = lNumber + 1; grabbed++; ASSERT(grabbed < MAXSPRITES); } while (script_p < scriptend_p); script_p = nullptr; } /* * Here begins JonoF's modding enhancement stuff */ enum { CM_MAP, CM_EPISODE, CM_TITLE, CM_FILENAME, CM_SONG, CM_CDATRACK, CM_BESTTIME, CM_PARTIME, CM_SUBTITLE, CM_SKILL, CM_TEXT, CM_COOKIE, CM_GOTKEY, CM_NEEDKEY, CM_INVENTORY, CM_AMOUNT, CM_WEAPON, CM_AMMONAME, CM_MAXAMMO, CM_DAMAGEMIN, CM_DAMAGEMAX, CM_THEME, CM_SECRET, CM_QUIT, }; static const struct _tokset { const char *str; int tokn; } cm_tokens[] = { { "map", CM_MAP }, { "level", CM_MAP }, { "episode", CM_EPISODE }, { "skill", CM_SKILL }, { "cookie", CM_COOKIE }, { "fortune", CM_COOKIE }, { "gotkey", CM_GOTKEY }, { "inventory", CM_INVENTORY }, { "weapon", CM_WEAPON }, { "needkey", CM_NEEDKEY }, { "theme", CM_THEME }, { "secret", CM_SECRET }, { "quit", CM_QUIT }, }, cm_map_tokens[] = { { "title", CM_TITLE }, { "name", CM_TITLE }, { "description", CM_TITLE }, { "filename", CM_FILENAME }, { "file", CM_FILENAME }, { "fn", CM_FILENAME }, { "levelname", CM_FILENAME }, { "song", CM_SONG }, { "music", CM_SONG }, { "songname", CM_SONG }, { "cdatrack", CM_CDATRACK }, { "cdtrack", CM_CDATRACK }, { "besttime", CM_BESTTIME }, { "partime", CM_PARTIME }, }, cm_episode_tokens[] = { { "title", CM_TITLE }, { "name", CM_TITLE }, { "description", CM_TITLE }, { "subtitle", CM_SUBTITLE }, }, cm_skill_tokens[] = { { "title", CM_TITLE }, { "name", CM_TITLE }, { "description", CM_TITLE }, }, cm_inventory_tokens[] = { { "title", CM_TITLE }, { "name", CM_TITLE }, { "description", CM_TITLE }, { "amount", CM_AMOUNT }, }, cm_weapons_tokens[] = { { "title", CM_TITLE }, { "name", CM_TITLE }, { "description", CM_TITLE }, { "ammoname", CM_AMMONAME }, { "maxammo", CM_MAXAMMO }, { "mindamage", CM_DAMAGEMIN }, { "maxdamage", CM_DAMAGEMAX }, { "pickup", CM_AMOUNT }, { "weaponpickup",CM_WEAPON }, }, cm_theme_tokens[] = { { "song", CM_SONG }, { "music", CM_SONG }, { "cdatrack", CM_CDATRACK }, { "cdtrack", CM_CDATRACK }, }; #define cm_numtokens (sizeof(cm_tokens)/sizeof(cm_tokens[0])) #define cm_map_numtokens (sizeof(cm_map_tokens)/sizeof(cm_map_tokens[0])) #define cm_episode_numtokens (sizeof(cm_episode_tokens)/sizeof(cm_episode_tokens[0])) #define cm_skill_numtokens (sizeof(cm_skill_tokens)/sizeof(cm_skill_tokens[0])) #define cm_inventory_numtokens (sizeof(cm_inventory_tokens)/sizeof(cm_inventory_tokens[0])) #define cm_weapons_numtokens (sizeof(cm_weapons_tokens)/sizeof(cm_weapons_tokens[0])) #define cm_theme_numtokens (sizeof(cm_theme_tokens)/sizeof(cm_theme_tokens[0])) static int cm_transtok(const char *tok, const struct _tokset *set, const unsigned num) { unsigned i; for (i=0; ilevelNumber = mapno; curMap->cluster = mapno < 5 ? 1 : 2; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_map_tokens, cm_map_numtokens)) { case CM_FILENAME: { sc.MustGetString(); curMap->SetFileName(sc.String); break; } case CM_SONG: { sc.MustGetString(); curMap->music = sc.String; break; } case CM_TITLE: { sc.MustGetString(); curMap->SetName(sc.String); break; } case CM_BESTTIME: { sc.MustGetNumber(); curMap->designerTime = sc.Number; break; } case CM_PARTIME: { sc.MustGetNumber(); curMap->parTime = sc.Number; break; } case CM_CDATRACK: { sc.MustGetNumber(); curMap->cdSongId = sc.Number; break; } default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } break; } case CM_EPISODE: { int curep; sc.MustGetNumber(); curep = sc.Number; if (sc.ParseError) curep = -1; else if ((unsigned)curep > 2u) { sc.ScriptMessage("Episode number %d not in range 1-2\n", curep + 1); curep = -1; break; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_episode_tokens, cm_episode_numtokens)) { case CM_TITLE: { sc.MustGetString(); auto vol = MustFindVolume(curep); auto clust = MustFindCluster(curep); vol->name = clust->name = sc.String; break; } case CM_SUBTITLE: { sc.MustGetString(); if (curep != -1) { auto vol = MustFindVolume(curep); vol->subtitle = sc.String; } break; } default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } break; } case CM_SKILL: { int curskill; sc.MustGetNumber(); curskill = sc.Number; if (sc.ParseError) curskill = -1; if ((unsigned)--curskill >= 4u) { sc.ScriptMessage("Skill number %d not in range 1-4", curskill + 1); curskill = -1; break; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_skill_tokens, cm_skill_numtokens)) { case CM_TITLE: { sc.MustGetString(); if (curskill != -1) gSkillNames[curskill] = sc.String; break; } default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } break; } case CM_COOKIE: { int fc = 0; if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); if (fc < MAX_FORTUNES) { quoteMgr.InitializeQuote(QUOTE_COOKIE + fc, sc.String); } fc++; } break; } case CM_GOTKEY: { int fc = 0; if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); if (fc < MAX_KEYS) { quoteMgr.InitializeQuote(QUOTE_KEYMSG + fc, sc.String); } fc++; } break; } case CM_NEEDKEY: { int fc = 0; if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); if (fc < MAX_KEYS) { quoteMgr.InitializeQuote(QUOTE_DOORMSG + fc, sc.String); } fc++; } break; } case CM_INVENTORY: { int in; FString name; int amt = -1; sc.MustGetNumber(); in = sc.Number; if (sc.ParseError) in = -1; if ((unsigned)--in >= (unsigned)InvDecl_TOTAL) { sc.ScriptMessage("Inventory item number %d not in range 1-%d\n", in, InvDecl_TOTAL); in = -1; break; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_inventory_tokens, cm_inventory_numtokens)) { case CM_TITLE: sc.MustGetToken(TK_StringConst); name = sc.String; break; case CM_AMOUNT: sc.MustGetNumber(); amt = sc.Number; break; default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } if (in == -1) break; if (name.IsNotEmpty()) { quoteMgr.InitializeQuote(QUOTE_INVENTORY + in, name); } if (amt >= 0) { InventoryDecls[in].amount = amt; } break; } case CM_WEAPON: { FString name, ammo; int maxammo = -1, damagemin = -1, damagemax = -1, pickup = -1, wpickup = -1; int in,id; sc.MustGetNumber(); in = sc.Number; if (sc.ParseError) in = -1; if ((unsigned)--in >= (unsigned)SIZ(weaponmap)) { sc.ScriptMessage("Error: weapon number %d not in range 1-%d", in+1, (int)SIZ(weaponmap)); in = -1; break; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_weapons_tokens, cm_weapons_numtokens)) { case CM_TITLE: sc.MustGetToken(TK_StringConst); name = sc.String; break; case CM_AMMONAME: sc.MustGetToken(TK_StringConst); ammo = sc.String; break; case CM_MAXAMMO: sc.MustGetNumber(); maxammo = sc.Number; break; case CM_DAMAGEMIN: sc.MustGetNumber(); damagemin = sc.Number; break; case CM_DAMAGEMAX: sc.MustGetNumber(); damagemax = sc.Number; break; case CM_AMOUNT: sc.MustGetNumber(); pickup = sc.Number; break; case CM_WEAPON: sc.MustGetNumber(); wpickup = sc.Number; break; default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } if (in == -1) break; id = weaponmap[in].dmgid; if (weaponmap[in].editable & WM_DAMAGE) { if (damagemin >= 0) DamageData[id].damage_lo = damagemin; if (damagemax >= 0) DamageData[id].damage_hi = damagemax; } if (weaponmap[in].editable & WM_WEAP) { if (maxammo >= 0) DamageData[id].max_ammo = maxammo; if (name.IsNotEmpty()) { quoteMgr.InitializeQuote(QUOTE_WPNFIST + in, name); } if (wpickup >= 0) DamageData[id].weapon_pickup = wpickup; } if (weaponmap[in].editable & WM_AMMO) { if (ammo.IsNotEmpty()) { quoteMgr.InitializeQuote(QUOTE_AMMOFIST + in, ammo); } if (pickup >= 0) DamageData[id].ammo_pickup = pickup; } break; } case CM_THEME: { FString name; int trak = -1; int curtheme; sc.MustGetNumber(); curtheme = sc.Number; if (sc.ParseError) curtheme = -1; if ((unsigned)--curtheme >= 6u) { sc.ScriptMessage("Theme number %d not in range 1-6", curtheme+1); curtheme = -1; } if (sc.CheckString("{")) while (!sc.CheckString("}")) { sc.MustGetString(); switch (cm_transtok(sc.String, cm_theme_tokens, cm_theme_numtokens)) { case CM_SONG: sc.MustGetToken(TK_StringConst); name = sc.String; break; case CM_CDATRACK: sc.MustGetNumber(); trak = sc.Number; break; default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } if (curtheme == -1) break; if (name.IsNotEmpty()) { ThemeSongs[curtheme] = name; } if (trak >= 2) { ThemeTrack[curtheme] = trak; } break; } case CM_SECRET: case CM_QUIT: default: sc.ScriptError("Unknown keyword %s", sc.String); break; } } auto vol0 = MustFindVolume(1); auto vol1 = MustFindVolume(2); auto map1 = FindMapByLevelNum(1); auto map5 = FindMapByLevelNum(5); if (vol0 && map1) vol0->startmap = map1->labelName; if (vol1 && map5) vol1->startmap = map5->labelName; } END_SW_NS