1911 lines
47 KiB
C++
1911 lines
47 KiB
C++
// SOF2NPCViewer.cpp : implementation file
|
|
//
|
|
// ( Note: this file is actually for both SOF2 NPCs and JK2 Bots now )
|
|
|
|
#include "stdafx.h"
|
|
#include "includes.h"
|
|
#include "modview.h"
|
|
#include "files.h"
|
|
#include "r_common.h"
|
|
#include "generic_stuff.h"
|
|
#include "GenericParser2.h"
|
|
#include "script.h" // so we can access ModView script keyword #defines
|
|
#include "modviewtreeview.h" // for GetString()
|
|
//
|
|
#include "SOF2NPCViewer.h"
|
|
|
|
|
|
#include "stl.h"
|
|
/*
|
|
#include "disablewarnings.h"
|
|
#pragma warning( push, 3 )
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
#pragma warning( pop )
|
|
using namespace std;
|
|
#include "disablewarnings.h"
|
|
*/
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
bool gbDoingGallery = false;
|
|
CString gstrGalleryOutputDir;
|
|
CString gstrGallerySequenceToLock;
|
|
CString *gpFeedback = NULL;
|
|
LPCSTR gpsGameDir = NULL;
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CSOF2NPCViewer dialog
|
|
|
|
|
|
CSOF2NPCViewer::CSOF2NPCViewer(bool bSOF2Mode, CString *pFeedback, LPCSTR psGameDir, CWnd* pParent /*=NULL*/)
|
|
: CDialog(CSOF2NPCViewer::IDD, pParent)
|
|
{
|
|
//{{AFX_DATA_INIT(CSOF2NPCViewer)
|
|
// NOTE: the ClassWizard will add member initialization here
|
|
//}}AFX_DATA_INIT
|
|
|
|
gpFeedback = pFeedback;
|
|
gpsGameDir = psGameDir;
|
|
|
|
m_bSOF2Mode = bSOF2Mode;
|
|
|
|
Gallery_Done();
|
|
}
|
|
|
|
|
|
void CSOF2NPCViewer::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(CSOF2NPCViewer)
|
|
// NOTE: the ClassWizard will add DDX and DDV calls here
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CSOF2NPCViewer, CDialog)
|
|
//{{AFX_MSG_MAP(CSOF2NPCViewer)
|
|
ON_BN_CLICKED(IDREFRESH, OnRefresh)
|
|
ON_LBN_DBLCLK(IDC_LIST_NPCS, OnDblclkListNpcs)
|
|
ON_LBN_SELCHANGE(IDC_LIST_NPCS, OnSelchangeListNpcs)
|
|
ON_BN_CLICKED(IDGALLERY, OnGallery)
|
|
ON_BN_CLICKED(IDVALIDATE, OnValidate)
|
|
ON_BN_CLICKED(IDC_BUTTON_GENERATE_LIST, OnButtonGenerateList)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CSOF2NPCViewer message handlers
|
|
|
|
BOOL CSOF2NPCViewer::OnInitDialog()
|
|
{
|
|
CDialog::OnInitDialog();
|
|
|
|
// CG: The following block was added by the ToolTips component.
|
|
{
|
|
// Create the ToolTip control.
|
|
m_tooltip.Create(this);
|
|
m_tooltip.Activate(TRUE);
|
|
|
|
// TODO: Use one of the following forms to add controls:
|
|
// m_tooltip.AddTool(GetDlgItem(IDC_<name>), <string-table-id>);
|
|
// m_tooltip.AddTool(GetDlgItem(IDC_<name>), "<text>");
|
|
|
|
m_tooltip.AddTool(GetDlgItem(IDOK), "Close");
|
|
m_tooltip.AddTool(GetDlgItem(IDREFRESH), "Refresh data from source files");
|
|
m_tooltip.AddTool(GetDlgItem(IDVALIDATE), "Validate");
|
|
m_tooltip.AddTool(GetDlgItem(IDGALLERY), "Generate gallery images");
|
|
}
|
|
|
|
if (m_bSOF2Mode)
|
|
{
|
|
// SOF2...
|
|
//
|
|
NPC_ScanFiles(false);
|
|
NPC_FillList();
|
|
}
|
|
else
|
|
{
|
|
// JK2...
|
|
//
|
|
BOT_ScanFiles(false);
|
|
BOT_FillList();
|
|
|
|
GetDlgItem(IDVALIDATE)->EnableWindow(false);
|
|
}
|
|
|
|
// my machine only... :-)
|
|
//
|
|
// GetDlgItem(IDGALLERY)->EnableWindow(!stricmp(scGetUserName(),"scork"));
|
|
|
|
return TRUE; // return TRUE unless you set the focus to a control
|
|
// EXCEPTION: OCX Property Pages should return FALSE
|
|
}
|
|
|
|
void CSOF2NPCViewer::OnRefresh()
|
|
{
|
|
if (m_bSOF2Mode)
|
|
{
|
|
// SOF2...
|
|
//
|
|
NPC_ScanFiles(true);
|
|
NPC_FillList();
|
|
}
|
|
else
|
|
{
|
|
// JK2...
|
|
//
|
|
BOT_ScanFiles(true);
|
|
BOT_FillList();
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
// fields from "ext_data/sof2.item"...
|
|
//
|
|
string strName; // name "US SOCOM"
|
|
string strBoltPoint; // boltpoint "*hand_r"
|
|
string strHolstModel; // holstmodel "models\characters\bolt_ons\holster.glm"
|
|
// holstsurf "gun"
|
|
|
|
// fields from "ext_data/sof2.wpn"
|
|
|
|
string strModel; // model "models/weapons/ussocom/ussocom.glm"
|
|
|
|
} NPC_Weapon_t;
|
|
|
|
|
|
typedef struct
|
|
{
|
|
// fields from "ext_data/sof2.item"...
|
|
//
|
|
string strName; // name "chem_taylor"
|
|
vector <string> vSurfaces_Off; // offsurf1 "head_bck_lwr_l"
|
|
vector <string> vSurfaces_On; // onsurf1 "fhead_bck_lwr_l_off"
|
|
string strModel; // model "models\characters\bolt_ons\backpack_radio.glm"
|
|
|
|
} NPC_Item_t;
|
|
|
|
typedef map <string,NPC_Weapon_t> TheNPCWeapons_t;
|
|
typedef map <string,NPC_Item_t> TheNPCItems_t;
|
|
|
|
typedef struct
|
|
{
|
|
string strName; // Name "US SOCOM"
|
|
string strBoltPoint; // Bolt "*hip_r"
|
|
|
|
} NPC_INV_Weapon_t;
|
|
|
|
typedef struct
|
|
{
|
|
string strName; // Name "ponytail"
|
|
string strBoltPoint; // Bolt "*hip_r"
|
|
|
|
} NPC_INV_Item_t;
|
|
|
|
typedef vector <NPC_INV_Weapon_t> NPC_INV_Weapons_t;
|
|
typedef vector <NPC_INV_Item_t> NPC_INV_Items_t;
|
|
|
|
typedef struct
|
|
{
|
|
NPC_INV_Weapons_t Weapons;
|
|
NPC_INV_Items_t Items;
|
|
|
|
} NPC_INV_t;
|
|
|
|
typedef struct
|
|
{
|
|
string strFile; // SkinFile "nurse_w1" or [File "marine_camo2"]
|
|
|
|
// optional weapons and items only available in this skin...
|
|
//
|
|
NPC_INV_t Inventory;
|
|
|
|
} NPC_Skin_t;
|
|
|
|
typedef map <string, NPC_Skin_t> NPC_Skins_t;
|
|
|
|
typedef struct
|
|
{
|
|
string strName; // Name "NPC_Taylor_Jungle"
|
|
string strComments; // comments "Madeline Taylor in Jungle Gear"
|
|
string strModel; // Model "models/characters/female_pants/female_pants.glm"
|
|
|
|
// Team "The Shop"
|
|
// Rank "Private"
|
|
// Occupation "ScriptGuy"
|
|
// Health "90 100"
|
|
|
|
// optional weapons and items at the global level...
|
|
//
|
|
NPC_INV_t Inventory;
|
|
NPC_Skins_t Skins;
|
|
|
|
} NPC_CharacterTemplate_t;
|
|
|
|
typedef map <string,NPC_CharacterTemplate_t> NPC_CharacterTemplates_t;
|
|
|
|
typedef struct
|
|
{
|
|
// Skeleton "female_pants.skl"
|
|
string strParentTemplate; // ParentTemplate "NPC_Base_Female"
|
|
|
|
} NPC_GroupInfo_t;
|
|
|
|
typedef struct
|
|
{
|
|
NPC_GroupInfo_t NPC_GroupInfo;
|
|
NPC_CharacterTemplates_t NPC_CharacterTemplates;
|
|
|
|
} NPCFile_t;
|
|
|
|
|
|
typedef map <string, NPCFile_t> NPCFiles_t;
|
|
|
|
NPCFiles_t TheNPCFiles;
|
|
TheNPCWeapons_t TheNPCWeapons;
|
|
TheNPCItems_t TheNPCItems;
|
|
|
|
|
|
|
|
// text defines for ext_data/sof2.item...
|
|
//
|
|
#define sKEYWORD_ITEM_WEAPON "weapon"
|
|
#define sKEYWORD_ITEM_ITEM "item"
|
|
#define sKEYWORD_ITEM_NAME "name"
|
|
#define sKEYWORD_ITEM_MODEL "model"
|
|
#define sKEYWORD_ITEM_BOLTPOINT "boltpoint"
|
|
#define sKEYWORD_ITEM_HOLSTMODEL "holstmodel"
|
|
#define sKEYWORD_ITEM_ONSURF "onsurf" // "onsurf", "onsurf1", "onsurf2" etc
|
|
#define sKEYWORD_ITEM_OFFSURF "offsurf" // "offsurf", "offsurf1", "offsurf2" etc
|
|
|
|
|
|
// confusingly, this actually means load both weapons & items, but parse them from the file "ext_data/sof2.item"
|
|
//
|
|
// return true to continue processing, else false because of an error and the user said "don't continue"...
|
|
//
|
|
static bool NPC_LoadItems(void)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
char sFileName[MAX_QPATH];
|
|
|
|
Com_sprintf( sFileName, sizeof( sFileName ), "ext_data/sof2.item");
|
|
StatusMessage( va("Scanning file: \"%s\"...", sFileName));
|
|
|
|
char *pText = NULL;
|
|
int iTotalBytesLoaded = ri.FS_ReadFile( sFileName, (void **)&pText );
|
|
|
|
if ( pText )
|
|
{
|
|
CGenericParser2 Parser;
|
|
char *psDataPtr = pText; // because ptr gets advanced, so we supply a clone that GP1 can alter
|
|
if (Parser.Parse(&psDataPtr, true))
|
|
{
|
|
CGPGroup *pFileGroup = Parser.GetBaseParseGroup();
|
|
if (pFileGroup)
|
|
{
|
|
CGPGroup *pSubGroup = pFileGroup->GetSubGroups();
|
|
while (pSubGroup)
|
|
{
|
|
string strParseGroupName = pSubGroup->GetName();
|
|
|
|
if (strParseGroupName == sKEYWORD_ITEM_WEAPON)
|
|
{
|
|
// read weapon fields...
|
|
//
|
|
CGPGroup *&pWeaponGroup = pSubGroup; // saves search/replace during GP1->GP conversion
|
|
string strName = pWeaponGroup->FindPairValue(sKEYWORD_ITEM_NAME, "");
|
|
if (!strName.empty())
|
|
{
|
|
// insert into map...
|
|
//
|
|
TheNPCWeapons[strName].strName = strName; // looks pointless, but init's the map entry
|
|
//
|
|
// now parse other fields...
|
|
//
|
|
string
|
|
str = pWeaponGroup->FindPairValue(sKEYWORD_ITEM_BOLTPOINT, "");
|
|
if (!str.empty())
|
|
{
|
|
TheNPCWeapons[strName].strBoltPoint = str;
|
|
}
|
|
|
|
str = pWeaponGroup->FindPairValue(sKEYWORD_ITEM_HOLSTMODEL, "");
|
|
if (!str.empty())
|
|
{
|
|
TheNPCWeapons[strName].strHolstModel = str;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (strParseGroupName == sKEYWORD_ITEM_ITEM)
|
|
{
|
|
// read item fields...
|
|
//
|
|
CGPGroup *&pItemGroup = pSubGroup;
|
|
|
|
string strName = pItemGroup->FindPairValue(sKEYWORD_ITEM_NAME, "");
|
|
if (!strName.empty())
|
|
{
|
|
// insert into map...
|
|
//
|
|
TheNPCItems[strName].strName = strName; // looks pointless, but init's the map entry
|
|
//
|
|
// now parse other fields...
|
|
//
|
|
string
|
|
str = pItemGroup->FindPairValue(sKEYWORD_ITEM_MODEL, "");
|
|
if (!str.empty())
|
|
{
|
|
TheNPCItems[strName].strModel = str;
|
|
}
|
|
|
|
//
|
|
// parse fields with non-precise names...
|
|
//
|
|
CGPValue *pValue = pSubGroup->GetPairs();
|
|
while (pValue)
|
|
{
|
|
string strKey = pValue->GetName();
|
|
string strValue = pValue->GetTopValue();
|
|
|
|
if (!strncmp(strKey.c_str(), sKEYWORD_ITEM_ONSURF, strlen(sKEYWORD_ITEM_ONSURF)))
|
|
{
|
|
TheNPCItems[strName].vSurfaces_On.push_back(strValue);
|
|
}
|
|
|
|
if (!strncmp(strKey.c_str(), sKEYWORD_ITEM_OFFSURF, strlen(sKEYWORD_ITEM_OFFSURF)))
|
|
{
|
|
TheNPCItems[strName].vSurfaces_Off.push_back(strValue);
|
|
}
|
|
|
|
pValue = pValue->GetNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
pSubGroup = (CGPGroup *)pSubGroup->GetNext();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("{} - Brace mismatch error in file \"%s\"!",sFileName));
|
|
}
|
|
|
|
ri.FS_FreeFile( pText );
|
|
bReturn = true;
|
|
}
|
|
else
|
|
{
|
|
bReturn = GetYesNo(va("Unable to open file \"%s\", continue anyway?",sFileName));
|
|
}
|
|
StatusMessage(NULL);
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
|
|
#define sKEYWORD_WPN_WEAPON "weapon"
|
|
#define sKEYWORD_WPN_NAME "name"
|
|
#define sKEYWORD_WPN_MODEL "model"
|
|
|
|
// this parses weapon info from the file "ext_data/sof2.wpn"
|
|
//
|
|
// return true to continue processing, else false because of an error and the user said "don't continue"...
|
|
//
|
|
static bool NPC_LoadWeapons(void)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
char sFileName[MAX_QPATH];
|
|
|
|
Com_sprintf( sFileName, sizeof( sFileName ), "ext_data/sof2.wpn");
|
|
StatusMessage( va("Scanning file: \"%s\"...", sFileName));
|
|
|
|
char *pText = NULL;
|
|
int iTotalBytesLoaded = ri.FS_ReadFile( sFileName, (void **)&pText );
|
|
|
|
if ( pText )
|
|
{
|
|
CGenericParser2 Parser;
|
|
char *psDataPtr = pText;
|
|
if (Parser.Parse(&psDataPtr, true))
|
|
{
|
|
CGPGroup *pFileGroup = Parser.GetBaseParseGroup();
|
|
if (pFileGroup)
|
|
{
|
|
CGPGroup *pSubGroup = pFileGroup->GetSubGroups();
|
|
|
|
while (pSubGroup)
|
|
{
|
|
string strParseGroupName = pSubGroup->GetName();
|
|
|
|
if (strParseGroupName == sKEYWORD_WPN_WEAPON)
|
|
{
|
|
// read weapon fields...
|
|
//
|
|
CGPGroup *&pWeaponGroup = pSubGroup;
|
|
|
|
string strName = pWeaponGroup->FindPairValue(sKEYWORD_WPN_NAME, "");
|
|
if (!strName.empty())
|
|
{
|
|
// insert into map...
|
|
//
|
|
TheNPCWeapons[strName].strName = strName; // looks pointless, but init's the map entry
|
|
//
|
|
// now parse other fields...
|
|
//
|
|
string
|
|
str = pWeaponGroup->FindPairValue(sKEYWORD_WPN_MODEL, "");
|
|
if (!str.empty())
|
|
{
|
|
TheNPCWeapons[strName].strModel = str;
|
|
}
|
|
}
|
|
}
|
|
pSubGroup = pSubGroup->GetNext();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("{} - Brace mismatch error in file \"%s\"!",sFileName));
|
|
}
|
|
|
|
ri.FS_FreeFile( pText );
|
|
bReturn = true;
|
|
}
|
|
else
|
|
{
|
|
bReturn = GetYesNo(va("Unable to open file \"%s\", continue anyway?",sFileName));
|
|
}
|
|
StatusMessage(NULL);
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
|
|
#define sKEYWORD_NPC_GROUPINFO "GroupInfo"
|
|
#define sKEYWORD_NPC_CHARACTERTEMPLATE "CharacterTemplate"
|
|
#define sKEYWORD_NPC_PARENTTEMPLATE "ParentTemplate"
|
|
#define sKEYWORD_NPC_NAME "Name"
|
|
#define sKEYWORD_NPC_MODEL "Model"
|
|
#define sKEYWORD_NPC_COMMENTS "comments"
|
|
#define sKEYWORD_NPC_SKINFILE "SkinFile"
|
|
#define sKEYWORD_NPC_INVENTORY "Inventory"
|
|
#define sKEYWORD_NPC_ITEM "Item"
|
|
#define sKEYWORD_NPC_WEAPON "Weapon"
|
|
#define sKEYWORD_NPC_BOLT "Bolt"
|
|
#define sKEYWORD_NPC_SKIN "Skin"
|
|
#define sKEYWORD_NPC_FILE "File"
|
|
|
|
static bool NPC_ParseInventory(NPC_INV_t &Inventory, CGPGroup *pParseGroup)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
CGPGroup *pSubGroup = pParseGroup->GetSubGroups();
|
|
while (pSubGroup)
|
|
{
|
|
string strSubGroupName = pSubGroup->GetName();
|
|
|
|
if (strSubGroupName == sKEYWORD_NPC_WEAPON)
|
|
{
|
|
// read weapon fields...
|
|
//
|
|
NPC_INV_Weapon_t Weapon;
|
|
|
|
CGPValue *pValue = pSubGroup->GetPairs();
|
|
while (pValue)
|
|
{
|
|
string strKey = pValue->GetName();
|
|
string strValue = pValue->GetTopValue();
|
|
|
|
if (strKey == sKEYWORD_NPC_NAME)
|
|
{
|
|
Weapon.strName = strValue;
|
|
}
|
|
else
|
|
if (strKey == sKEYWORD_NPC_BOLT)
|
|
{
|
|
Weapon.strBoltPoint = strValue;
|
|
}
|
|
|
|
pValue = pValue->GetNext();
|
|
}
|
|
|
|
Inventory.Weapons.push_back( Weapon );
|
|
bReturn = true;
|
|
}
|
|
else
|
|
if (strSubGroupName == sKEYWORD_NPC_ITEM)
|
|
{
|
|
// read item fields...
|
|
//
|
|
NPC_INV_Item_t Item;
|
|
|
|
CGPValue *pValue = pSubGroup->GetPairs();
|
|
while (pValue)
|
|
{
|
|
string strKey = pValue->GetName();
|
|
string strValue = pValue->GetTopValue();
|
|
|
|
if (strKey == sKEYWORD_NPC_NAME)
|
|
{
|
|
Item.strName = strValue;
|
|
}
|
|
else
|
|
if (strKey == sKEYWORD_NPC_BOLT)
|
|
{
|
|
Item.strBoltPoint = strValue;
|
|
}
|
|
|
|
pValue = pValue->GetNext();
|
|
}
|
|
|
|
Inventory.Items.push_back( Item );
|
|
bReturn = true;
|
|
}
|
|
|
|
pSubGroup = pSubGroup->GetNext();
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
//
|
|
static bool NPC_ParseNPCFiles(void)
|
|
{
|
|
char **ppsNPCFiles;
|
|
int iNPCFiles;
|
|
|
|
// scan for NPC files...
|
|
//
|
|
#define sNPC_DIR va("%snpcs",gpsGameDir)
|
|
|
|
ppsNPCFiles = //ri.FS_ListFiles( "shaders", ".shader", &iSkinFiles );
|
|
Sys_ListFiles( sNPC_DIR, // const char *directory,
|
|
".npc", // const char *extension,
|
|
NULL, // char *filter,
|
|
&iNPCFiles, // int *numfiles,
|
|
qfalse // qboolean wantsubs
|
|
);
|
|
|
|
if ( ppsNPCFiles && iNPCFiles )
|
|
{
|
|
#define MAX_NPC_FILES 100 // any old large-ish size for array declaration...
|
|
//
|
|
if ( iNPCFiles > MAX_NPC_FILES )
|
|
{
|
|
WarningBox(va("%d NPC files found, capping to %d\n\n(tell me if this ever happens -Ste)", iNPCFiles, MAX_NPC_FILES ));
|
|
|
|
iNPCFiles = MAX_NPC_FILES;
|
|
}
|
|
|
|
// load and parse skin files...
|
|
//
|
|
char *buffers[MAX_NPC_FILES];
|
|
long iTotalBytesLoaded = 0;
|
|
for ( int i=0; i<iNPCFiles; i++ )
|
|
{
|
|
char sFileName[MAX_QPATH];
|
|
|
|
string strThisNPCFile(ppsNPCFiles[i]);
|
|
string strThisNPCFileNoExt(Filename_WithoutExt(strThisNPCFile.c_str())); // laziness
|
|
|
|
Com_sprintf( sFileName, sizeof( sFileName ), "%s/%s", "npcs", strThisNPCFile.c_str() );
|
|
StatusMessage( va("Scanning NPC file %d/%d: \"%s\"...",i+1,iNPCFiles,sFileName));
|
|
|
|
iTotalBytesLoaded += ri.FS_ReadFile( sFileName, (void **)&buffers[i] );
|
|
|
|
if ( !buffers[i] ) {
|
|
ri.Error( ERR_DROP, "Couldn't load %s", sFileName );
|
|
}
|
|
|
|
char *psDataPtr = buffers[i];
|
|
|
|
CGenericParser2 Parser;
|
|
if (Parser.Parse(&psDataPtr, true))
|
|
{
|
|
CGPGroup *pFileGroup = Parser.GetBaseParseGroup();
|
|
if (pFileGroup)
|
|
{
|
|
CGPGroup *pSubGroup = pFileGroup->GetSubGroups();
|
|
while (pSubGroup)
|
|
{
|
|
string strParseGroupName = pSubGroup->GetName();
|
|
|
|
if (strParseGroupName == sKEYWORD_NPC_GROUPINFO)
|
|
{
|
|
// only want one field from this group...
|
|
//
|
|
string str = pSubGroup->FindPairValue(sKEYWORD_NPC_PARENTTEMPLATE, "");
|
|
if (!str.empty())
|
|
{
|
|
TheNPCFiles[strThisNPCFileNoExt].NPC_GroupInfo.strParentTemplate = str;
|
|
}
|
|
}
|
|
else
|
|
if (strParseGroupName == sKEYWORD_NPC_CHARACTERTEMPLATE)
|
|
{
|
|
NPC_CharacterTemplate_t CharacterTemplate;
|
|
//
|
|
// see which of the pairs in here interest us...
|
|
//
|
|
CGPValue *pValue = pSubGroup->GetPairs();
|
|
while (pValue)
|
|
{
|
|
string strKey = pValue->GetName();
|
|
string strValue = pValue->GetTopValue();
|
|
|
|
if (!stricmp(strKey.c_str(), sKEYWORD_NPC_NAME))
|
|
{
|
|
//OutputDebugString(va("%s\n",strValue.c_str()));
|
|
CharacterTemplate.strName = strValue;
|
|
}
|
|
else
|
|
if (!stricmp(strKey.c_str(), sKEYWORD_NPC_MODEL))
|
|
{
|
|
CharacterTemplate.strModel = strValue;
|
|
}
|
|
else
|
|
if (!stricmp(strKey.c_str(), sKEYWORD_NPC_COMMENTS))
|
|
{
|
|
CharacterTemplate.strComments = strValue;
|
|
}
|
|
else
|
|
if (!stricmp(strKey.c_str(), sKEYWORD_NPC_SKINFILE))
|
|
{
|
|
NPC_Skin_t Skin;
|
|
Skin.strFile = strValue;
|
|
CharacterTemplate.Skins[ Skin.strFile ] = Skin;
|
|
}
|
|
pValue = pValue->GetNext();
|
|
}
|
|
|
|
// scan for whichever subgroups we're interested in...
|
|
//
|
|
CGPGroup *pSubGroupTemplate = pSubGroup->GetSubGroups();
|
|
while (pSubGroupTemplate)
|
|
{
|
|
string strSubGroupName = pSubGroupTemplate->GetName();
|
|
|
|
if (strSubGroupName == sKEYWORD_NPC_INVENTORY)
|
|
{
|
|
NPC_INV_t Inventory;
|
|
|
|
if (NPC_ParseInventory(Inventory,pSubGroupTemplate))
|
|
{
|
|
CharacterTemplate.Inventory = Inventory;
|
|
}
|
|
}
|
|
else
|
|
if (strSubGroupName == sKEYWORD_NPC_SKIN)
|
|
{
|
|
// must have a "File" pair-entry...
|
|
//
|
|
string strFile = pSubGroupTemplate->FindPairValue(sKEYWORD_NPC_FILE,"");
|
|
if (!strFile.empty())
|
|
{
|
|
NPC_Skin_t Skin;
|
|
Skin.strFile = strFile;
|
|
|
|
// now look for an optional Inventory subgroup...
|
|
//
|
|
CGPGroup *pSubGroupInv = pSubGroupTemplate->FindSubGroup(sKEYWORD_NPC_INVENTORY);
|
|
if (pSubGroupInv)
|
|
{
|
|
NPC_INV_t Inventory;
|
|
|
|
if (NPC_ParseInventory(Inventory,pSubGroupInv))
|
|
{
|
|
Skin.Inventory = Inventory;
|
|
}
|
|
}
|
|
|
|
CharacterTemplate.Skins[ Skin.strFile ] = Skin;
|
|
}
|
|
}
|
|
pSubGroupTemplate = pSubGroupTemplate->GetNext();
|
|
}
|
|
|
|
// finally...
|
|
//
|
|
TheNPCFiles[strThisNPCFileNoExt].NPC_CharacterTemplates[CharacterTemplate.strName] = CharacterTemplate;
|
|
}
|
|
|
|
pSubGroup = pSubGroup->GetNext();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ri.Error( ERR_DROP, "{} - Brace mismatch error in file \"%s\"!",sFileName);
|
|
}
|
|
}
|
|
StatusMessage(NULL);
|
|
|
|
// free loaded files...
|
|
//
|
|
for ( i=0; i<iNPCFiles; i++ )
|
|
{
|
|
ri.FS_FreeFile( buffers[i] );
|
|
}
|
|
|
|
// ... and file list...
|
|
//
|
|
Sys_FreeFileList( ppsNPCFiles );
|
|
}
|
|
else
|
|
{
|
|
WarningBox(va("WARNING: no NPC files found in '%s'\n",sNPC_DIR ));
|
|
}
|
|
|
|
return !!TheNPCFiles.size();
|
|
}
|
|
|
|
|
|
/*
|
|
// Dark Side bots
|
|
// Currently, all DS bots carry red lightsabers
|
|
name "SW-967"
|
|
model Swamptrooper
|
|
color1 0
|
|
personality /botfiles/Swamptrooper.jkb
|
|
//Swamptrooper is aligned with no one
|
|
|
|
*/
|
|
typedef struct BOTFile_s
|
|
{
|
|
string strName; // name "SW-967"
|
|
string strModel; // model Swamptrooper // (will be 1:1 dir name match)
|
|
int iColor1; // color1 0 // lightsaber color, FWIW (dark = 0, light = 4 so far)
|
|
string strComment; ////Swamptrooper is aligned with no one
|
|
|
|
BOTFile_s()
|
|
{
|
|
iColor1 = 0;
|
|
}
|
|
|
|
} BOTFile_t;
|
|
|
|
typedef map <string, BOTFile_t> BOTFiles_t;
|
|
|
|
BOTFiles_t TheBOTFiles;
|
|
|
|
|
|
static void BOT_ScanSkins(BOTFile_t &Bot, set <string> &SkinVariants)
|
|
{
|
|
char **ppsFiles;
|
|
int iFiles;
|
|
|
|
// scan for NPC files...
|
|
//
|
|
#define sBOTSKINS_DIR va("%smodels/players/%s",gpsGameDir, Bot.strModel.c_str())
|
|
|
|
ppsFiles = //ri.FS_ListFiles( "shaders", ".shader", &iSkinFiles );
|
|
Sys_ListFiles( sBOTSKINS_DIR, // const char *directory,
|
|
".skin", // const char *extension,
|
|
NULL, // char *filter,
|
|
&iFiles, // int *numfiles,
|
|
qfalse // qboolean wantsubs
|
|
);
|
|
|
|
if ( ppsFiles && iFiles )
|
|
{
|
|
for ( int i=0; i<iFiles; i++ )
|
|
{
|
|
CString strThisFile( Filename_WithoutExt(ppsFiles[i]) );
|
|
|
|
if (!strnicmp(strThisFile,"model_",6))
|
|
{
|
|
string s = (LPCSTR)(strThisFile.Mid(6));
|
|
SkinVariants.insert(s);
|
|
}
|
|
}
|
|
StatusMessage(NULL);
|
|
|
|
// free file list...
|
|
//
|
|
Sys_FreeFileList( ppsFiles );
|
|
}
|
|
}
|
|
|
|
|
|
void CSOF2NPCViewer::BOT_ScanFiles(bool bForceRefresh)
|
|
{
|
|
if (TheBOTFiles.size() && !bForceRefresh)
|
|
return;
|
|
|
|
CWaitCursor wait;
|
|
|
|
TheBOTFiles.clear();
|
|
|
|
CString strFileName( va("%sbotfiles\\bots.txt",gpsGameDir) );
|
|
CString strBOTFile;
|
|
if (TextFile_Read(strBOTFile, strFileName, false, true))
|
|
{
|
|
int iLoc;
|
|
|
|
bool bStopHere = false;
|
|
while ( !bStopHere && (iLoc = strBOTFile.Find("{")) != -1) /*}*/
|
|
{
|
|
/*{*/
|
|
int iLoc2 = strBOTFile.Find("}",iLoc);
|
|
|
|
if (iLoc2 != -1)
|
|
{
|
|
CString strThisBot( strBOTFile.Left(iLoc2) );
|
|
strThisBot = strThisBot.Mid(iLoc+1);
|
|
|
|
strBOTFile = strBOTFile.Mid(iLoc2+1);
|
|
|
|
BOTFile_t Bot;
|
|
|
|
// scan this bot file on a line by line basis...
|
|
//
|
|
/*
|
|
{
|
|
name "IW-322"
|
|
model Imperial_worker
|
|
color1 0
|
|
personality /botfiles/Imperial_worker.jkb
|
|
//Imperial_worker is aligned with no one
|
|
}
|
|
*/
|
|
|
|
while (!strThisBot.IsEmpty())
|
|
{
|
|
CString strLine;
|
|
|
|
iLoc = strThisBot.FindOneOf("\n\r");
|
|
if (iLoc != -1)
|
|
{
|
|
strLine = strThisBot.Left(iLoc);
|
|
strThisBot = strThisBot.Mid(iLoc+1);
|
|
}
|
|
else
|
|
{
|
|
strLine = strThisBot;
|
|
strThisBot.Empty();
|
|
}
|
|
|
|
strLine.TrimLeft();
|
|
strLine.TrimRight();
|
|
|
|
if (!strLine.IsEmpty())
|
|
{
|
|
if (!strnicmp(strLine,"name", 4))
|
|
{
|
|
CString str(strLine.Mid(4));
|
|
str.TrimLeft();
|
|
str.Replace("\"","");
|
|
|
|
if (!str.CompareNoCase("@STOPHERE"))
|
|
{
|
|
bStopHere = true;
|
|
break;
|
|
}
|
|
|
|
Bot.strName = (LPCSTR) str;
|
|
//OutputDebugString(va("Bot name: \"%s\"\n",(LPCSTR) str));
|
|
}
|
|
else
|
|
if (!strnicmp(strLine,"model",5))
|
|
{
|
|
CString str(strLine.Mid(5));
|
|
str.TrimLeft();
|
|
str.Replace("\"","");
|
|
|
|
Bot.strModel = (LPCSTR) str;
|
|
//OutputDebugString(va("Bot model: \"%s\"\n",(LPCSTR) str));
|
|
}
|
|
else
|
|
if (!strnicmp(strLine,"color1",6))
|
|
{
|
|
Bot.iColor1 = atoi(&((LPCSTR)strLine)[6]);
|
|
|
|
//OutputDebugString(va("Bot color1: %d\n",Bot.iColor1));
|
|
}
|
|
else
|
|
if (!strnicmp(strLine,"//",2))
|
|
{
|
|
CString str( strLine.Mid(2) );
|
|
str.TrimLeft();
|
|
|
|
Bot.strComment = (LPCSTR) str;
|
|
|
|
//OutputDebugString(va("Bot comment: \"%s\"\n",Bot.strComment.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bStopHere && !Bot.strModel.empty())
|
|
{
|
|
TheBOTFiles[ Bot.strModel.c_str() ] = Bot;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("Mismatching braces in \"%s\"!",(LPCSTR)strFileName));
|
|
strBOTFile.Empty();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ErrorBox(va("Failed to open file \"%s\"!",(LPCSTR)strFileName));
|
|
}
|
|
}
|
|
|
|
void CSOF2NPCViewer::BOT_FillList(void)
|
|
{
|
|
// ListBoxLookups.clear();
|
|
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
pListBox->ResetContent();
|
|
|
|
for (BOTFiles_t::iterator itBots = TheBOTFiles.begin(); itBots != TheBOTFiles.end(); ++itBots)
|
|
{
|
|
BOTFile_t &Bot = (*itBots).second;
|
|
|
|
pListBox->InsertString(-1, Bot.strModel.c_str());
|
|
/*
|
|
string strListEntry( NPC_CreateListEntry( pTemplate->strName.c_str(), pSkin->strFile.c_str() ) );
|
|
pListBox->InsertString(-1, strListEntry.c_str());
|
|
|
|
ListBoxLookups[ strListEntry ] = NPC_DataLookup; // hehehehe, STL rules for some stuff
|
|
*/
|
|
|
|
}
|
|
}
|
|
|
|
|
|
CStatic *pStatic = (CStatic *)GetDlgItem(IDC_STATIC_COMMENT);
|
|
if (pStatic)
|
|
{
|
|
pStatic->SetWindowText("");
|
|
}
|
|
}
|
|
|
|
|
|
// builds up all NPC structures...
|
|
//
|
|
void CSOF2NPCViewer::NPC_ScanFiles(bool bForceRefresh)
|
|
{
|
|
if (TheNPCFiles.size() && !bForceRefresh)
|
|
return;
|
|
|
|
CWaitCursor wait;
|
|
|
|
TheNPCFiles.clear();
|
|
TheNPCWeapons.clear();
|
|
TheNPCItems.clear();
|
|
|
|
// scan weapons and items in first...
|
|
//
|
|
bool bUserWantsToCancel = true;
|
|
if (NPC_LoadItems())
|
|
{
|
|
if (NPC_LoadWeapons())
|
|
{
|
|
if (NPC_ParseNPCFiles())
|
|
{
|
|
bUserWantsToCancel = false;
|
|
/*
|
|
// quick debug thing...
|
|
//
|
|
for (TheNPCWeapons_t::iterator itW = TheNPCWeapons.begin(); itW != TheNPCWeapons.end(); ++itW)
|
|
{
|
|
NPC_Weapon_t &Weapon = (*itW).second;
|
|
|
|
OutputDebugString(va("Weapon: \"%s\"\n",Weapon.strName.c_str()));
|
|
OutputDebugString(va(" strModel: \"%s\"\n",Weapon.strModel.c_str()));
|
|
OutputDebugString(va(" strBoltPoint: \"%s\"\n",Weapon.strBoltPoint.c_str()));
|
|
OutputDebugString(va(" strHolstModel: \"%s\"\n",Weapon.strHolstModel.c_str()));
|
|
OutputDebugString("\n");
|
|
}
|
|
|
|
for (TheNPCItems_t::iterator itI = TheNPCItems.begin(); itI != TheNPCItems.end(); ++itI)
|
|
{
|
|
NPC_Item_t &Item = (*itI).second;
|
|
|
|
OutputDebugString(va("Item: \"%s\"\n",Item.strName.c_str()));
|
|
OutputDebugString(va(" strModel: \"%s\"\n",Item.strModel.c_str()));
|
|
if (Item.vSurfaces_On.size())
|
|
{
|
|
for (int i=0; i<Item.vSurfaces_On.size(); i++)
|
|
{
|
|
OutputDebugString(va("On: \"%s\"\n",Item.vSurfaces_On[i].c_str()));
|
|
}
|
|
}
|
|
if (Item.vSurfaces_Off.size())
|
|
{
|
|
for (int i=0; i<Item.vSurfaces_Off.size(); i++)
|
|
{
|
|
OutputDebugString(va("Off: \"%s\"\n",Item.vSurfaces_Off[i].c_str()));
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUserWantsToCancel)
|
|
{
|
|
OnCancel();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
{
|
|
string strNPCFileNameBase;
|
|
string strTemplateName;
|
|
string strSkinName;
|
|
|
|
} NPC_DataLookup_t;
|
|
|
|
typedef map<string, NPC_DataLookup_t> ListBoxLookups_t;
|
|
ListBoxLookups_t ListBoxLookups;
|
|
|
|
// subroutined so that the ModView Script generator can generate a 100%-compatible string for doing a data lookup...
|
|
//
|
|
static LPCSTR NPC_CreateListEntry(LPCSTR psTemplateName, LPCSTR psSkinName)
|
|
{
|
|
#define MIN_NAME_LEN 40
|
|
static string strListEntry;
|
|
strListEntry = va("%s\t ( %s )", String_EnsureMinLength(psTemplateName, MIN_NAME_LEN), psSkinName);
|
|
return strListEntry.c_str();
|
|
}
|
|
|
|
// applies NPC structures to GUI picker...
|
|
//
|
|
void CSOF2NPCViewer::NPC_FillList()
|
|
{
|
|
ListBoxLookups.clear();
|
|
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
pListBox->ResetContent();
|
|
|
|
for (NPCFiles_t::iterator itFiles = TheNPCFiles.begin(); itFiles != TheNPCFiles.end(); ++itFiles)
|
|
{
|
|
string strNPCFileNameBase = (*itFiles).first;
|
|
NPCFile_t *pNPCFile = &(*itFiles).second;
|
|
|
|
NPC_GroupInfo_t *pNPC_GroupInfo = &pNPCFile->NPC_GroupInfo;
|
|
|
|
for (NPC_CharacterTemplates_t::iterator itTemplate = pNPCFile->NPC_CharacterTemplates.begin(); itTemplate != pNPCFile->NPC_CharacterTemplates.end(); ++itTemplate)
|
|
{
|
|
NPC_CharacterTemplate_t *pTemplate = &(*itTemplate).second;
|
|
|
|
for (NPC_Skins_t::iterator itSkin = pTemplate->Skins.begin(); itSkin != pTemplate->Skins.end(); ++itSkin)
|
|
{
|
|
NPC_Skin_t *pSkin = &(*itSkin).second;
|
|
|
|
string strListEntry( NPC_CreateListEntry( pTemplate->strName.c_str(), pSkin->strFile.c_str() ) );
|
|
pListBox->InsertString(-1, strListEntry.c_str());
|
|
|
|
NPC_DataLookup_t NPC_DataLookup;
|
|
NPC_DataLookup.strNPCFileNameBase = strNPCFileNameBase;
|
|
NPC_DataLookup.strTemplateName = pTemplate->strName;
|
|
NPC_DataLookup.strSkinName = pSkin->strFile;
|
|
|
|
ListBoxLookups[ strListEntry ] = NPC_DataLookup; // hehehehe, STL rules for some stuff
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CStatic *pStatic = (CStatic *)GetDlgItem(IDC_STATIC_COMMENT);
|
|
if (pStatic)
|
|
{
|
|
pStatic->SetWindowText("");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// turns a listbox caption into data ptrs...
|
|
//
|
|
static bool NPC_DataLookup(LPCSTR psCaption, NPCFile_t &NPCFile, NPC_CharacterTemplate_t &Template, NPC_Skin_t &Skin)
|
|
{
|
|
ListBoxLookups_t::iterator it = ListBoxLookups.find(psCaption);
|
|
if (it != ListBoxLookups.end())
|
|
{
|
|
NPC_DataLookup_t &DataLookUp = (*it).second;
|
|
|
|
// NPCFile...
|
|
//
|
|
NPCFiles_t::iterator itFile = TheNPCFiles.find( DataLookUp.strNPCFileNameBase );
|
|
if (itFile != TheNPCFiles.end())
|
|
{
|
|
NPCFile = (*itFile).second;
|
|
|
|
// CharacterTemplate...
|
|
//
|
|
NPC_CharacterTemplates_t::iterator itTemplate = NPCFile.NPC_CharacterTemplates.find( DataLookUp.strTemplateName );
|
|
if (itTemplate != NPCFile.NPC_CharacterTemplates.end())
|
|
{
|
|
Template = (*itTemplate).second;
|
|
|
|
// Skin...
|
|
//
|
|
NPC_Skins_t::iterator itSkin = Template.Skins.find( DataLookUp.strSkinName );
|
|
if (itSkin != Template.Skins.end())
|
|
{
|
|
Skin = (*itSkin).second;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CSOF2NPCViewer::OnSelchangeListNpcs()
|
|
{
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
int iSelected = pListBox->GetCurSel();
|
|
|
|
if (iSelected != LB_ERR)
|
|
{
|
|
CString strCaption;
|
|
|
|
pListBox->GetText(iSelected, strCaption);
|
|
|
|
// work out comment...
|
|
//
|
|
CStatic *pStatic = (CStatic *)GetDlgItem(IDC_STATIC_COMMENT);
|
|
if (pStatic)
|
|
{
|
|
LPCSTR psStaticText = NULL;
|
|
if (m_bSOF2Mode)
|
|
{
|
|
// SOF2...
|
|
//
|
|
NPCFile_t NPCFile;
|
|
NPC_CharacterTemplate_t Template;
|
|
NPC_Skin_t Skin;
|
|
if (NPC_DataLookup(strCaption, NPCFile, Template, Skin) && !Template.strComments.empty())
|
|
{
|
|
psStaticText = va("( Comment: \"%s\" )",Template.strComments.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// JK2...
|
|
//
|
|
BOTFiles_t::iterator itBotFile = TheBOTFiles.find( (LPCSTR) strCaption);
|
|
if (itBotFile != TheBOTFiles.end())
|
|
{
|
|
// psStaticText = va("( Comment: \"%s\" Model: \"%s\" )",(*itBotFile).second.strComment.c_str(), (*itBotFile).second.strModel.c_str());
|
|
psStaticText = va("( Comment: \"%s\" )",(*itBotFile).second.strComment.c_str(), (*itBotFile).second.strModel.c_str());
|
|
}
|
|
}
|
|
|
|
pStatic->SetWindowText( psStaticText ? psStaticText : "( No comment available )" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
static
|
|
void NPC_CreateModViewScript_ParseInventory(CString &strCaptionForErrorPrinting,
|
|
NPC_INV_t &Inventory,
|
|
vector <string> &vSurfaces_On,
|
|
vector <string> &vSurfaces_Off,
|
|
vector < pair< string, string> > &vBoltData
|
|
)
|
|
{
|
|
// items...
|
|
//
|
|
NPC_INV_Items_t &Items = Inventory.Items;
|
|
for (int iItem = 0; iItem < Items.size(); iItem++)
|
|
{
|
|
LPCSTR psName = Items[iItem].strName.c_str();
|
|
|
|
string strBoltPoint = Items[iItem].strBoltPoint.c_str();
|
|
string strBoltModel;
|
|
|
|
TheNPCItems_t::iterator itItem = TheNPCItems.find(psName);
|
|
if (itItem != TheNPCItems.end())
|
|
{
|
|
NPC_Item_t &Item = (*itItem).second;
|
|
|
|
vSurfaces_On.insert (vSurfaces_On.begin(), Item.vSurfaces_On.begin(), Item.vSurfaces_On.end());
|
|
vSurfaces_Off.insert(vSurfaces_Off.begin(),Item.vSurfaces_Off.begin(),Item.vSurfaces_Off.end());
|
|
|
|
strBoltModel = Item.strModel;
|
|
}
|
|
else
|
|
{
|
|
WarningBox(va("Unable to find item entry \"%s\"\n\nCharacter: \"%s\"\n\n( Try hitting 'Refresh', and if still missing, tell Joe K )",psName,(LPCSTR)strCaptionForErrorPrinting));
|
|
}
|
|
|
|
if (!strBoltPoint.empty() && !strBoltModel.empty())
|
|
{
|
|
vBoltData.push_back( pair<string,string>(String_ForwardSlash(strBoltModel.c_str()),strBoltPoint) );
|
|
}
|
|
}
|
|
|
|
// weapons...
|
|
//
|
|
NPC_INV_Weapons_t &InvWeapons = Inventory.Weapons;
|
|
for (int iWeapon = 0; iWeapon < InvWeapons.size(); iWeapon++)
|
|
{
|
|
NPC_INV_Weapon_t &InvWeapon = InvWeapons[iWeapon];
|
|
|
|
string strName = InvWeapon.strName;
|
|
string strBoltPoint = InvWeapon.strBoltPoint; // will be blank if a held weapon, else filled in for holstered
|
|
string strBoltModel;
|
|
|
|
TheNPCWeapons_t::iterator itWeapon = TheNPCWeapons.find(strName);
|
|
if (itWeapon != TheNPCWeapons.end())
|
|
{
|
|
NPC_Weapon_t &Weapon = (*itWeapon).second;
|
|
|
|
// some slightly interesting logic here, ok, first, let's see if it was a holstered weapon or not...
|
|
//
|
|
if (!strBoltPoint.empty())
|
|
{
|
|
// holstered weapon...
|
|
//
|
|
strBoltModel = Weapon.strHolstModel;
|
|
}
|
|
else
|
|
{
|
|
// currently-held weapon...
|
|
//
|
|
strBoltPoint = Weapon.strBoltPoint;
|
|
strBoltModel = Weapon.strModel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WarningBox(va("Unable to find weapon entry \"%s\"\n\nCharacter: \"%s\"\n\n( Try hitting 'Refresh', and if still missing, tell Joe )",strName.c_str(),(LPCSTR)strCaptionForErrorPrinting));
|
|
}
|
|
|
|
if (!strBoltPoint.empty() && !strBoltModel.empty())
|
|
{
|
|
vBoltData.push_back( pair<string,string>(String_ForwardSlash(strBoltModel.c_str()),strBoltPoint) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is pretty quick and dirty for now, it'll fail if you have two identical models loaded, each of which has
|
|
// something bolted to it (which the real script-writer compensates for correctly).
|
|
//
|
|
// Oh well, I can tackle that if it ever arises...
|
|
//
|
|
static bool NPC_CreateModViewScript(CString &strCaptionForErrorPrinting, CString &strScript, NPCFile_t &NPCFile, NPC_CharacterTemplate_t &Template, NPC_Skin_t &Skin)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
CTextPool *pTextPool = new CTextPool(40960); // any old number, it'll expand internally
|
|
|
|
CGenericParser2 OutputParser; // .. this should be interesting...
|
|
OutputParser.SetWriteable(true); // itu?
|
|
|
|
CGPGroup *pModelGroup = OutputParser.GetBaseParseGroup()->AddGroup( sSCRIPTKEYWORD_LOADMODEL, &pTextPool );
|
|
if (pModelGroup)
|
|
{
|
|
if (gpsGameDir)
|
|
{
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_BASEDIR, gpsGameDir, &pTextPool);
|
|
}
|
|
string strParentName( Template.strName );
|
|
//
|
|
// name "average_sleeves"... (this field is more of a label really, for bolting purposes). See comment at top.
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_NAME, strParentName.c_str(), &pTextPool);
|
|
|
|
|
|
// modelfile "models/characters/average_sleeves/average_sleeves.glm"
|
|
//
|
|
string strModel( Template.strModel );
|
|
if (strModel.empty())
|
|
{
|
|
string strParentTemplate = NPCFile.NPC_GroupInfo.strParentTemplate;
|
|
//fixme: need to look this up!!!!
|
|
|
|
// need to find which NPC file has this template in...
|
|
//
|
|
for (NPCFiles_t::iterator itNPC = TheNPCFiles.begin(); itNPC != TheNPCFiles.end(); ++itNPC)
|
|
{
|
|
NPCFile_t &_NPCFile = (*itNPC).second;
|
|
NPC_CharacterTemplates_t::iterator itTemplate = _NPCFile.NPC_CharacterTemplates.find(strParentTemplate);
|
|
if (itTemplate != _NPCFile.NPC_CharacterTemplates.end())
|
|
{
|
|
// found the template, so adopt the model name from there...
|
|
//
|
|
NPC_CharacterTemplate_t &_Template = (*itTemplate).second;
|
|
strModel = _Template.strModel;
|
|
}
|
|
}
|
|
|
|
if (strModel.empty())
|
|
{
|
|
ErrorBox(va("Unable to work out model name for character template \"%s\"!",Template.strName.c_str()));
|
|
return false;
|
|
}
|
|
}
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_MODELFILE, strModel.c_str(), &pTextPool);
|
|
|
|
// skinfile "col_rebel_h1.g2skin"
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_SKINFILE, va("%s.g2skin",Skin.strFile.c_str()), &pTextPool);
|
|
|
|
|
|
// ethnic "white"
|
|
// (currently the NPC file system has no provision for this (unlike MVS files :-)
|
|
// but i stilll have to have one or it won't apply the skin, so...
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_ETHNIC, "white", &pTextPool);
|
|
|
|
// now build up lists of surfaces on and off. This is going to be messy, since they do theirs as part of
|
|
// the inventory data (sigh)...
|
|
//
|
|
vector <string> vSurfaces_On;
|
|
vector <string> vSurfaces_Off;
|
|
vector < pair< string, string> > vBoltData; // <model name,boltpoint>
|
|
//
|
|
// check global inventory first...
|
|
//
|
|
NPC_CreateModViewScript_ParseInventory(strCaptionForErrorPrinting, Template.Inventory, vSurfaces_On, vSurfaces_Off, vBoltData);
|
|
//
|
|
// now check the inventory inside this skin...
|
|
//
|
|
NPC_CreateModViewScript_ParseInventory(strCaptionForErrorPrinting, Skin.Inventory, vSurfaces_On, vSurfaces_Off, vBoltData);
|
|
|
|
/*
|
|
surfaces_on
|
|
{
|
|
name0 "scarf_off"
|
|
name1 "head_side_r_avmedhat_off"
|
|
name2 "head_side_l_avmedhat_off"
|
|
}
|
|
*/
|
|
if (vSurfaces_On.size())
|
|
{
|
|
CGPGroup *pSurfaceGroup = pModelGroup->AddGroup( sSCRIPTKEYWORD_SURFACES_ON, &pTextPool );
|
|
if (pSurfaceGroup)
|
|
{
|
|
for (int iSurface = 0; iSurface < vSurfaces_On.size(); iSurface++)
|
|
{
|
|
pSurfaceGroup->AddPair(va(sSCRIPTKEYWORD_NAME "%d",iSurface), vSurfaces_On[iSurface].c_str(), &pTextPool);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
surfaces_off
|
|
{
|
|
name0 "head_side_r"
|
|
name1 "head_side_l"
|
|
name2 "head_bck_uppr_r"
|
|
name3 "head_bck_uppr_l"
|
|
}
|
|
*/
|
|
if (vSurfaces_Off.size())
|
|
{
|
|
CGPGroup *pSurfaceGroup = pModelGroup->AddGroup( sSCRIPTKEYWORD_SURFACES_OFF, &pTextPool);
|
|
if (pSurfaceGroup)
|
|
{
|
|
for (int iSurface = 0; iSurface < vSurfaces_Off.size(); iSurface++)
|
|
{
|
|
pSurfaceGroup->AddPair(va(sSCRIPTKEYWORD_NAME "%d",iSurface), vSurfaces_Off[iSurface].c_str(), &pTextPool);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
boltmodel
|
|
{
|
|
name "ak74world"
|
|
modelfile "models/weapons/ak74/world/ak74world.glm"
|
|
parent "average_sleeves"
|
|
bolttosurface "*hand_r"
|
|
}
|
|
*/
|
|
if (vBoltData.size())
|
|
{
|
|
for (int iBoltOn=0; iBoltOn<vBoltData.size(); iBoltOn++)
|
|
{
|
|
CGPGroup *pBoltGroup = pModelGroup->AddGroup( sSCRIPTKEYWORD_BOLTMODEL, &pTextPool );
|
|
if (pBoltGroup)
|
|
{
|
|
string strModelName = vBoltData[iBoltOn].first.c_str();
|
|
string strBoltPoint = vBoltData[iBoltOn].second.c_str();
|
|
|
|
pBoltGroup->AddPair(sSCRIPTKEYWORD_NAME, Filename_WithoutPath(Filename_WithoutExt(strModelName.c_str())),&pTextPool);
|
|
pBoltGroup->AddPair(sSCRIPTKEYWORD_MODELFILE, strModelName.c_str(),&pTextPool);
|
|
pBoltGroup->AddPair(sSCRIPTKEYWORD_PARENT, strParentName.c_str(),&pTextPool);
|
|
pBoltGroup->AddPair(sSCRIPTKEYWORD_BOLTTOSURFACE, strBoltPoint.c_str(),&pTextPool);
|
|
}
|
|
}
|
|
}
|
|
|
|
// now here's hoping Rick's code works...
|
|
//
|
|
|
|
// create the text pool...
|
|
//
|
|
CTextPool *pOutputTextPool = new CTextPool(40960); // any old number, will expand if necessary
|
|
OutputParser.Write(pOutputTextPool);
|
|
|
|
strScript = pOutputTextPool->GetPool(); // feed script back to main program as text
|
|
|
|
CleanTextPool(pOutputTextPool);
|
|
bReturn = true;
|
|
}
|
|
|
|
CleanTextPool(pTextPool);
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
|
|
// this is pretty quick and dirty for now, it'll fail if you have two identical models loaded, each of which has
|
|
// something bolted to it (which the real script-writer compensates for correctly).
|
|
//
|
|
// Oh well, I can tackle that if it ever arises...
|
|
//
|
|
static bool BOT_CreateModViewScript(CString &strCaptionForErrorPrinting, CString &strScript, BOTFile_t &BOTFile, set <string> &SkinVariants)
|
|
{
|
|
bool bReturn = false;
|
|
|
|
CTextPool *pTextPool = new CTextPool(40960); // any old number, it'll expand internally
|
|
|
|
CGenericParser2 OutputParser; // .. this should be interesting...
|
|
OutputParser.SetWriteable(true); // itu?
|
|
|
|
CGPGroup *pModelGroup = OutputParser.GetBaseParseGroup()->AddGroup( sSCRIPTKEYWORD_LOADMODEL, &pTextPool );
|
|
if (pModelGroup)
|
|
{
|
|
if (gpsGameDir)
|
|
{
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_BASEDIR, gpsGameDir, &pTextPool);
|
|
}
|
|
// name "SW-967"... (this field is more of a label really, for bolting purposes). See comment at top.
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_NAME, BOTFile.strName.c_str(), &pTextPool);
|
|
|
|
// modelfile "models/players/swamptrooper/model.glm"
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_MODELFILE, va("models/players/%s/model.glm",BOTFile.strModel.c_str()), &pTextPool);
|
|
|
|
// if any skin variants supplied, grab the one off the top and use it...
|
|
//
|
|
if (!SkinVariants.empty())
|
|
{
|
|
// oldskinfile "commander"
|
|
//
|
|
pModelGroup->AddPair(sSCRIPTKEYWORD_OLDSKINFILE, (*SkinVariants.begin()).c_str(), &pTextPool);
|
|
SkinVariants.erase(SkinVariants.begin());
|
|
}
|
|
|
|
// now here's hoping Rick's code works...
|
|
//
|
|
|
|
// create the text pool...
|
|
//
|
|
CTextPool *pOutputTextPool = new CTextPool(40960); // any old number, will expand if necessary
|
|
OutputParser.Write(pOutputTextPool);
|
|
|
|
strScript = pOutputTextPool->GetPool(); // feed script back to main program as text
|
|
|
|
CleanTextPool(pOutputTextPool);
|
|
bReturn = true;
|
|
}
|
|
|
|
CleanTextPool(pTextPool);
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
void CSOF2NPCViewer::OnDblclkListNpcs()
|
|
{
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
int iSelected = pListBox->GetCurSel();
|
|
|
|
if (iSelected != LB_ERR)
|
|
{
|
|
CString strCaption;
|
|
|
|
pListBox->GetText(iSelected, strCaption);
|
|
|
|
if (m_bSOF2Mode)
|
|
{
|
|
// SOF2...
|
|
//
|
|
NPCFile_t NPCFile;
|
|
NPC_CharacterTemplate_t Template;
|
|
NPC_Skin_t Skin;
|
|
if (NPC_DataLookup(strCaption, NPCFile, Template, Skin))
|
|
{
|
|
CString strScript;
|
|
|
|
if (NPC_CreateModViewScript(strCaption, strScript, NPCFile, Template, Skin))
|
|
{
|
|
*gpFeedback = strScript;
|
|
OnOK();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// JK2...
|
|
//
|
|
BOTFiles_t::iterator itBotFile = TheBOTFiles.find( (LPCSTR) strCaption );
|
|
if (itBotFile != TheBOTFiles.end())
|
|
{
|
|
BOTFile_t &Bot = (*itBotFile).second;
|
|
|
|
set <string> SkinVariants; // leave it empty for this call
|
|
|
|
CString strScript;
|
|
if (BOT_CreateModViewScript(strCaption, strScript, Bot, SkinVariants))
|
|
{
|
|
*gpFeedback = strScript;
|
|
OnOK();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#include "direct.h"
|
|
void Q_mkdir (const char *path)
|
|
{
|
|
#ifdef WIN32
|
|
if (_mkdir (path) != -1)
|
|
return;
|
|
#else
|
|
if (mkdir (path, 0777) != -1)
|
|
return;
|
|
#endif
|
|
if (errno != EEXIST)
|
|
{
|
|
ErrorBox (va("mkdir %s: %s",path, strerror(errno)));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CreatePath (const char *path)
|
|
{
|
|
const char *ofs;
|
|
char c;
|
|
char dir[1024];
|
|
|
|
#ifdef _WIN32
|
|
int olddrive = -1;
|
|
|
|
if ( path[1] == ':' )
|
|
{
|
|
olddrive = _getdrive();
|
|
_chdrive( toupper( path[0] ) - 'A' + 1 );
|
|
}
|
|
#endif
|
|
|
|
if (path[1] == ':')
|
|
path += 2;
|
|
|
|
for (ofs = path+1 ; *ofs ; ofs++)
|
|
{
|
|
c = *ofs;
|
|
if (c == '/' || c == '\\')
|
|
{ // create the directory
|
|
memcpy( dir, path, ofs - path );
|
|
dir[ ofs - path ] = 0;
|
|
Q_mkdir( dir );
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if ( olddrive != -1 )
|
|
{
|
|
_chdrive( olddrive );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
map <string, CString> AllScripts; // string("NPC caption from list box"), CString(modviewscript)
|
|
bool Gallery_Active(void)
|
|
{
|
|
return gbDoingGallery;
|
|
}
|
|
void Gallery_Done(void)
|
|
{
|
|
gbDoingGallery = false;
|
|
}
|
|
LPCSTR Gallery_GetOutputDir(void)
|
|
{
|
|
return gstrGalleryOutputDir;
|
|
}
|
|
LPCSTR Gallery_GetSeqToLock(void)
|
|
{
|
|
return gstrGallerySequenceToLock;
|
|
}
|
|
// reads first entry and removes it.
|
|
//
|
|
// returns 0 if fail, else number-remaining-plus-one
|
|
//
|
|
int GalleryRead_ExtractEntry(CString &strCaption, CString &strScript)
|
|
{
|
|
map <string, CString>::iterator itGallery = AllScripts.begin();
|
|
if (itGallery == AllScripts.end())
|
|
{
|
|
gbDoingGallery = false;
|
|
return 0;
|
|
}
|
|
|
|
strCaption = (*itGallery).first.c_str();
|
|
strScript = (*itGallery).second;
|
|
|
|
AllScripts.erase(itGallery);
|
|
return AllScripts.size() + 1;
|
|
}
|
|
|
|
void CSOF2NPCViewer::OnGallery()
|
|
{
|
|
AllScripts.clear();
|
|
|
|
#define sDEFAULT_SOF2_SCREENSHOTS_DIR "c:\\ravenlocal\\sof2\\gallery"
|
|
#define sDEFAULT_JK2_SCREENSHOTS_DIR "c:\\ravenlocal\\jk2\\gallery"
|
|
|
|
if (GetYesNo("This will load & screenshot every character in the list\n\nThis can take a *LONG* time. Proceed?"))
|
|
{
|
|
LPCSTR psOutputPath = GetString("Enter destination dir for screenshots (will be created if not found)", m_bSOF2Mode ? sDEFAULT_SOF2_SCREENSHOTS_DIR : sDEFAULT_JK2_SCREENSHOTS_DIR);
|
|
|
|
if (psOutputPath)
|
|
{
|
|
gstrGalleryOutputDir = psOutputPath;
|
|
|
|
// now for the fun (and mega-tacky!!) part, go through every item in the listbox,
|
|
// and generate a complete script for it... :-) Heh, CPU-abuse!!
|
|
//
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
int iListItems = pListBox->GetCount();
|
|
|
|
bool bGenerateEveryJK2SkinVariant = false;
|
|
if (!m_bSOF2Mode)
|
|
{
|
|
bGenerateEveryJK2SkinVariant = GetYesNo("Generate every available skin variant as a seperate gallery entry?\n\n( 'NO' will just generate one gallery entry per list entry )");
|
|
}
|
|
|
|
// work out which sequence to lock...
|
|
//
|
|
if (Model_Loaded())
|
|
{
|
|
ModelHandle_t hModel = AppVars.Container.hModel;
|
|
|
|
gstrGallerySequenceToLock = m_bSOF2Mode ? "idle01" : "BOTH_STAND1";
|
|
|
|
LPCSTR psSeqLockName = Model_Sequence_GetLockedName( hModel, true);
|
|
|
|
if (psSeqLockName)
|
|
{
|
|
if (GetYesNo(va("Currently-loaded model is locked to sequence \"%s\", use this for all models?\n\n( 'NO' will lock to \"%s\" instead )",psSeqLockName,(LPCSTR) gstrGallerySequenceToLock)))
|
|
{
|
|
gstrGallerySequenceToLock = psSeqLockName;
|
|
}
|
|
}
|
|
}
|
|
|
|
// off we go...
|
|
//
|
|
for (int iListItem = 0; iListItem < iListItems; iListItem++)
|
|
{
|
|
StatusMessage( va("Creating script %d/%d...", iListItem, iListItems) );
|
|
|
|
CString strCaption;
|
|
|
|
pListBox->GetText(iListItem, strCaption);
|
|
|
|
if (m_bSOF2Mode)
|
|
{
|
|
// SOF2...
|
|
//
|
|
NPCFile_t NPCFile;
|
|
NPC_CharacterTemplate_t Template;
|
|
NPC_Skin_t Skin;
|
|
if (NPC_DataLookup(strCaption, NPCFile, Template, Skin))
|
|
{
|
|
CString strScript;
|
|
|
|
if (NPC_CreateModViewScript(strCaption, strScript, NPCFile, Template, Skin))
|
|
{
|
|
AllScripts[ (LPCSTR)strCaption ] = strScript;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// JK2...
|
|
//
|
|
BOTFiles_t::iterator itBotFile = TheBOTFiles.find( (LPCSTR) strCaption );
|
|
if (itBotFile != TheBOTFiles.end())
|
|
{
|
|
BOTFile_t &Bot = (*itBotFile).second;
|
|
|
|
// grab all the skin variants?...
|
|
//
|
|
set <string> SkinVariants; // eg "model_blue", "model_red"
|
|
SkinVariants.clear();
|
|
|
|
if (bGenerateEveryJK2SkinVariant)
|
|
{
|
|
BOT_ScanSkins(Bot, SkinVariants);
|
|
}
|
|
|
|
do
|
|
{
|
|
CString strScript;
|
|
|
|
CString strThisSkinVariant;
|
|
if (!SkinVariants.empty())
|
|
{
|
|
strThisSkinVariant = (*SkinVariants.begin()).c_str();
|
|
}
|
|
|
|
if (BOT_CreateModViewScript(strCaption, strScript, Bot, SkinVariants))
|
|
{
|
|
CString strEntryName( (LPCSTR)strCaption );
|
|
|
|
if (!strThisSkinVariant.IsEmpty())
|
|
{
|
|
strEntryName += va("_%s",(LPCSTR)strThisSkinVariant);
|
|
}
|
|
|
|
AllScripts[ (LPCSTR) strEntryName ] = strScript;
|
|
}
|
|
}
|
|
while (!SkinVariants.empty());
|
|
}
|
|
|
|
}
|
|
}
|
|
StatusMessage( NULL );
|
|
|
|
gbDoingGallery = true;
|
|
OnOK();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// simply build a modview script (in memory, then disposes) of every NPC template so that
|
|
// any error messages can popup as they require...
|
|
//
|
|
// (basically a hack out of the OnGallery() function above)
|
|
//
|
|
// ( user sees messages by writing them down from popup boxes )
|
|
//
|
|
void CSOF2NPCViewer::OnValidate()
|
|
{
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
int iListItems = pListBox->GetCount();
|
|
|
|
for (int iListItem = 0; iListItem < iListItems; iListItem++)
|
|
{
|
|
StatusMessage( va("validating template %d/%d...", iListItem, iListItems) );
|
|
|
|
CString strCaption;
|
|
|
|
pListBox->GetText(iListItem, strCaption);
|
|
|
|
NPCFile_t NPCFile;
|
|
NPC_CharacterTemplate_t Template;
|
|
NPC_Skin_t Skin;
|
|
if (NPC_DataLookup(strCaption, NPCFile, Template, Skin))
|
|
{
|
|
CString strScript;
|
|
|
|
if (NPC_CreateModViewScript(strCaption, strScript, NPCFile, Template, Skin))
|
|
{
|
|
// do nothing...
|
|
}
|
|
}
|
|
}
|
|
StatusMessage( NULL );
|
|
|
|
InfoBox("Done");
|
|
}
|
|
}
|
|
|
|
BOOL CSOF2NPCViewer::PreTranslateMessage(MSG* pMsg)
|
|
{
|
|
// CG: The following block was added by the ToolTips component.
|
|
{
|
|
// Let the ToolTip process this message.
|
|
m_tooltip.RelayEvent(pMsg);
|
|
}
|
|
return CDialog::PreTranslateMessage(pMsg); // CG: This was added by the ToolTips component.
|
|
}
|
|
|
|
|
|
void CSOF2NPCViewer::OnButtonGenerateList()
|
|
{
|
|
CListBox* pListBox = (CListBox*)GetDlgItem(IDC_LIST_NPCS);
|
|
assert(pListBox);
|
|
if (pListBox)
|
|
{
|
|
StringSet_t strEntries;
|
|
|
|
int iListItems = pListBox->GetCount();
|
|
for (int iListItem = 0; iListItem < iListItems; iListItem++)
|
|
{
|
|
CString strCaption;
|
|
|
|
pListBox->GetText(iListItem, strCaption);
|
|
|
|
int iLoc = strCaption.FindOneOf(" \t");
|
|
if (iLoc != -1)
|
|
{
|
|
strCaption = strCaption.Left( iLoc );
|
|
}
|
|
|
|
strEntries.insert( (LPCSTR) strCaption );
|
|
}
|
|
|
|
CString strOutput;
|
|
|
|
for (StringSet_t::iterator it = strEntries.begin(); it != strEntries.end(); ++it)
|
|
{
|
|
strOutput += (*it).c_str();
|
|
strOutput += "\n";
|
|
}
|
|
|
|
if (!strOutput.IsEmpty())
|
|
{
|
|
SendStringToNotepad(strOutput, "entries.txt");
|
|
}
|
|
else
|
|
{
|
|
ErrorBox("No entries to send!");
|
|
}
|
|
}
|
|
}
|