// 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 #include #include #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_), ); // m_tooltip.AddTool(GetDlgItem(IDC_), ""); 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 vSurfaces_Off; // offsurf1 "head_bck_lwr_l" vector vSurfaces_On; // onsurf1 "fhead_bck_lwr_l_off" string strModel; // model "models\characters\bolt_ons\backpack_radio.glm" } NPC_Item_t; typedef map TheNPCWeapons_t; typedef map 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_Weapons_t; typedef vector 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 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 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 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; iGetSubGroups(); 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 BOTFiles_t; BOTFiles_t TheBOTFiles; static void BOT_ScanSkins(BOTFile_t &Bot, set &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; iResetContent(); 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 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 &vSurfaces_On, vector &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_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_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 vSurfaces_On; vector vSurfaces_Off; vector < pair< string, string> > vBoltData; // // // 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; iBoltOnAddGroup( 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 &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 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 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 ::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 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!"); } } }