diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c5fd34..e7b03e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,4 +45,4 @@ include( "cmake/simpleinstall.cmake" ) add_subdirectory( "plugins/vpExamplePlugin" ) add_subdirectory( "plugins/vpGLView" ) add_subdirectory( "plugins/vpHalfLifeAlpha" ) -add_subdirectory( "utils/pluginrunner" ) +add_subdirectory( "utils/jmftools" ) diff --git a/utils/jmftools/CMakeLists.txt b/utils/jmftools/CMakeLists.txt new file mode 100644 index 0000000..1e74743 --- /dev/null +++ b/utils/jmftools/CMakeLists.txt @@ -0,0 +1,104 @@ +#----------------------------------------------------------------------------- +# jmftools +# +# Project Script +#----------------------------------------------------------------------------- + +cmake_minimum_required( VERSION 3.20 FATAL_ERROR ) + +include( "${SRCDIR}/sdk/JackPluginSDK.cmake" ) + +set( JMFTOOLS_SOURCE_FILES ) +BEGIN_SRC( JMFTOOLS_SOURCE_FILES "Source Files" ) + SRC_GRP( + SOURCES + #{ + "logger.cpp" + "main.cpp" + "math.cpp" + "mem.cpp" + + "${SRCDIR}/plugins/common/DataSerializer.cpp" + "${SRCDIR}/plugins/common/vstdlib_static.cpp" + #} + ) + + SRC_GRP( + SUBGROUP "Map Objects" + SOURCES + #{ + "mapbrush.cpp" + "mapcamera.cpp" + "mapentity.cpp" + "mapface.cpp" + "mapgroup.cpp" + "mappath.cpp" + "mapworld.cpp" + #} + ) + + SRC_GRP( + SUBGROUP "Serializers" + SOURCES + #{ + "serializer_jmf.cpp" + "serializer_map.cpp" + #} + ) +END_SRC( JMFTOOLS_SOURCE_FILES "Source Files" ) + +set( JMFTOOLS_HEADER_FILES ) +BEGIN_SRC( JMFTOOLS_HEADER_FILES "Header Files" ) + SRC_GRP( + SOURCES + #{ + "logger.h" + "math.h" + "mem.h" + + "${SRCDIR}/plugins/common/DataSerializer.h" + "${SRCDIR}/plugins/common/vstdlib_static.h" + #} + ) + + SRC_GRP( + SUBGROUP "Map Objects" + SOURCES + #{ + "mapbrush.h" + "mapcamera.h" + "mapentity.h" + "mapface.h" + "mapgroup.h" + "mappath.h" + "mapworld.h" + #} + ) + + SRC_GRP( + SUBGROUP "Serializers" + SOURCES + #{ + "serializer_jmf.h" + "serializer_map.h" + #} + ) +END_SRC( JMFTOOLS_HEADER_FILES "Header Files" ) + +add_executable( + jmftools + ${JMFTOOLS_SOURCE_FILES} ${JMFTOOLS_HEADER_FILES} +) + +install_library( + TARGET jmftools EXECUTABLE + INSTALL_DEST "${CMAKE_INSTALL_PREFIX}" +) + +target_include_directories( + jmftools PRIVATE + + "${SRCDIR}/plugins/common" +) + +target_use_jackpluginsdk( jmftools "${SRCDIR}/sdk" ) diff --git a/utils/jmftools/logger.cpp b/utils/jmftools/logger.cpp new file mode 100644 index 0000000..a769fe1 --- /dev/null +++ b/utils/jmftools/logger.cpp @@ -0,0 +1,126 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include + +#if defined( WIN32 ) +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif // WIN32 + +/* +=============== +Sys_Printf +=============== +*/ +void Sys_Printf( const char *format, ... ) +{ + if ( !format ) + { + return; + } + + static char buf[2048] = { 0 }; + + va_list argptr; + va_start( argptr, format ); + vsnprintf( buf, sizeof( buf ), format, argptr ); + va_end( argptr ); + +#if defined( WIN32 ) + OutputDebugString( buf ); + OutputDebugString( "\n" ); +#endif // WIN32 + + printf( "%s\n", buf ); +} + +/* +=============== +Sys_DPrintf +=============== +*/ +void Sys_DPrintf( const char *format, ... ) +{ + if ( !format ) + { + return; + } + + static char buf[2048] = { 0 }; + + va_list argptr; + va_start( argptr, format ); + vsnprintf( buf, sizeof( buf ), format, argptr ); + va_end( argptr ); + +#if defined( WIN32 ) + OutputDebugString( buf ); + OutputDebugString( "\n" ); +#endif // WIN32 + + printf( "%s\n", buf ); +} + +/* +=============== +Sys_Warning +=============== +*/ +void Sys_Warning( const char *format, ... ) +{ + if ( !format ) + { + return; + } + + static char buf[2048] = { 0 }; + + va_list argptr; + va_start( argptr, format ); + vsnprintf( buf, sizeof( buf ), format, argptr ); + va_end( argptr ); + +#if defined( WIN32 ) + OutputDebugString( "WARNING: " ); + OutputDebugString( buf ); + OutputDebugString( "\n" ); +#endif // WIN32 + + printf( "WARNING: %s\n", buf ); +} + +/* +=============== +Sys_Error +=============== +*/ +void Sys_Error( const char *format, ... ) +{ + if ( !format ) + { + return; + } + + static char buf[2048] = { 0 }; + + va_list argptr; + va_start( argptr, format ); + vsnprintf( buf, sizeof( buf ), format, argptr ); + va_end( argptr ); + +#if defined( WIN32 ) + OutputDebugString( "ERROR: " ); + OutputDebugString( buf ); + OutputDebugString( "\n" ); +#endif // WIN32 + + printf( "ERROR: %s\n", buf ); +} diff --git a/utils/jmftools/logger.h b/utils/jmftools/logger.h new file mode 100644 index 0000000..4a3166a --- /dev/null +++ b/utils/jmftools/logger.h @@ -0,0 +1,17 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( LOGGER_H ) +#define LOGGER_H + +extern void Sys_Printf( const char *format, ... ); +extern void Sys_DPrintf( const char *format, ... ); +extern void Sys_Warning( const char *format, ... ); +extern void Sys_Error( const char *format, ... ); + +#endif // !LOGGER_H diff --git a/utils/jmftools/main.cpp b/utils/jmftools/main.cpp new file mode 100644 index 0000000..00b2baf --- /dev/null +++ b/utils/jmftools/main.cpp @@ -0,0 +1,209 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +#if defined( WIN32 ) +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif // WIN32 + +// Plugin API +#define ENTITYAPI_NO_INLINEFUNCS +#define PLUGINEDITORFUNCTIONS_H +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +#include "mapworld.h" + +#include "DataSerializer.h" +#include "serializer_jmf.h" +#include "serializer_map.h" + +/* +=============== +V_VersionString +=============== +*/ +static const char *V_VersionString() +{ + return "jmftools"; +} + +/* +=============== +Global_GetCurrentWorld +=============== +*/ +static CMapWorld *s_currentWorld = NULL; +static qWorld_s *Global_GetCurrentWorld() +{ + return (qWorld_s *)s_currentWorld; +} + +/* +=============== +BuildPackageList +=============== +*/ +static bool BuildPackageList( qWorld_s *worldDef, char **outBuf, char listSeparator, int removeVolumePrefix ) +{ + if ( !worldDef ) + { + return false; + } + + return worldDef->m_mapWorld->buildPackageList( outBuf, listSeparator, removeVolumePrefix ); +} + +/* +=============== +Sys_MakeLocalFileName +=============== +*/ +static const char *Sys_MakeLocalFileName( const char *filePath ) +{ + return filePath; +} + +plugin_funcs_t gEditorfuncs; + +static void BuildEditorFunctionsTable() +{ + memset( &gEditorfuncs, 0, sizeof( gEditorfuncs ) ); + gEditorfuncs.m_intefaceVersion = sizeof( plugin_funcs_t ); + + gEditorfuncs.pfnSys_Printf = Sys_Printf; + gEditorfuncs.pfnSys_DPrintf = Sys_DPrintf; + gEditorfuncs.pfnSys_Warning = Sys_Warning; + gEditorfuncs.pfnSys_Error = Sys_Error; + + gEditorfuncs.pfnSys_Malloc = Sys_Malloc; + gEditorfuncs.pfnSys_Free = Sys_Free; + + gEditorfuncs.pfnSys_AllocString = Sys_AllocString; + + gEditorfuncs.filesystemfuncs.pfnSys_MakeLocalFileName = Sys_MakeLocalFileName; + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + gEditorfuncs.mathfuncs.pfnSys_PrintValue = Sys_PrintValue; + gEditorfuncs.mathfuncs.pfnSys_PrintMapCoord = Sys_PrintMapCoord; + gEditorfuncs.mathfuncs.pfnSys_PrintAxis = Sys_PrintAxis; +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + gEditorfuncs.mathfuncs.pfnSys_SnapVertex = Sys_SnapVertex; + gEditorfuncs.mathfuncs.pfnSys_SnapAxis = Sys_SnapAxis; + gEditorfuncs.mathfuncs.pfnSys_SnapVertexToGrid = Sys_SnapVertexToGrid; + gEditorfuncs.mathfuncs.pfnSys_SnapMapVertex = Sys_SnapMapVertex; + + gEditorfuncs.pfnV_VersionString = V_VersionString; + + gEditorfuncs.pfnGlobal_GetCurrentWorld = Global_GetCurrentWorld; + + gEditorfuncs.pfnBuildPackageList = BuildPackageList; +} + +static void PrintUsage( const char *exeName ) +{ + Sys_Printf( "Usage: %s -i -o ", exeName ); +} + +static char s_inputFile[MAX_PATH] = { 0 }; +static char s_outputFile[MAX_PATH] = { 0 }; + +/* +=============== +main +=============== +*/ +int main( int argc, char **argv ) +{ + BuildEditorFunctionsTable(); + + if ( argc == 1 ) + { + PrintUsage( argv[0] ); + return 0; + } + + for ( int i = 1; i < argc; i++ ) + { + if ( !stricmp( argv[i], "-i" ) || !stricmp( argv[i], "--input" ) ) + { + if ( i + 1 >= argc || argv[i + 1][0] == '-' ) + { + PrintUsage( argv[0] ); + return 1; + } + + strncpy( s_inputFile, argv[i + 1], sizeof( s_inputFile ) ); + s_inputFile[sizeof( s_inputFile ) - 1] = '\0'; + i++; + } + else if ( !stricmp( argv[i], "-o" ) || !stricmp( argv[i], "--output" ) ) + { + if ( i + 1 >= argc || argv[i + 1][0] == '-' ) + { + PrintUsage( argv[0] ); + return 1; + } + + strncpy( s_outputFile, argv[i + 1], sizeof( s_outputFile ) ); + s_outputFile[sizeof( s_outputFile ) - 1] = '\0'; + i++; + } + else + { + break; + } + } + + if ( s_inputFile[0] == '\0' || s_outputFile[0] == '\0' ) + { + Sys_Error( "Both input and output files arguments are required.\n" ); + + PrintUsage( argv[0] ); + return 1; + } + + // Spawn da world + s_currentWorld = new CMapWorld; + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + JMFSerializer jmfSerializer( s_inputFile, 0, 0, s_currentWorld ); +#else + JMFSerializer jmfSerializer( s_inputFile, s_currentWorld ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + if ( !jmfSerializer.Import() ) + { + Sys_Printf( "Failed to import \"%s\"\n", s_inputFile ); + return 1; + } + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + MAPSerializer mapSerializer( s_outputFile, 0, 0, s_currentWorld, FMODE_STDIO ); +#else + MAPSerializer mapSerializer( s_outputFile, s_currentWorld, FMODE_STDIO ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + if ( !mapSerializer.Export() ) + { + Sys_Printf( "Failed to export \"%s\"", s_outputFile ); + return 1; + } + + if ( s_currentWorld ) + { + delete s_currentWorld; + s_currentWorld = NULL; + } + + return 0; +} diff --git a/utils/jmftools/mapbrush.cpp b/utils/jmftools/mapbrush.cpp new file mode 100644 index 0000000..1e447ff --- /dev/null +++ b/utils/jmftools/mapbrush.cpp @@ -0,0 +1,208 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapBrush::CMapBrush() +{ + m_brushData = (qBrush_s *)Sys_Malloc( sizeof( qBrush_s ) ); + + m_selectionId = -1; + + m_cbColor = m_cbFaceColor = rgba_t( 0, 0, 0, 255 ); + + m_brushSize = vec3_origin; + + //m_visGroupList = NULL; + + memset( m_brushData, 0, sizeof( *m_brushData ) ); + m_brushData->m_mapBrush = this; +} + +/* +=============== +Destructor +=============== +*/ +CMapBrush::~CMapBrush() +{ + //removeDecals(); + + removeFromAllVisGroups(); + + if ( m_brushData->m_patch ) + { + if ( m_brushData->m_patch->m_mapPatch ) + { + delete m_brushData->m_patch->m_mapPatch; + } + } + + if ( m_brushData->m_overlay ) + { + if ( m_brushData->m_overlay->m_mapOverlay ) + { + delete m_brushData->m_overlay->m_mapOverlay; + } + } + + qFace_s *nextFaceDef = NULL; + for ( qFace_s *faceDef = m_brushData->m_faceList; faceDef != NULL; faceDef = nextFaceDef ) + { + nextFaceDef = faceDef->next; + + if ( faceDef->m_mapFace ) + { + delete faceDef->m_mapFace; + } + } + + Sys_Free( m_brushData ); +} + +/* +=============== +addToVisGroup +=============== +*/ +void CMapBrush::addToVisGroup( CVisGroup *visGroup ) +{ + if ( std::find( m_visGroupList.begin(), m_visGroupList.end(), visGroup ) == m_visGroupList.end() ) + { + m_visGroupList.push_back( visGroup ); + } +} + +/* +=============== +removeFromAllVisGroups +=============== +*/ +void CMapBrush::removeFromAllVisGroups() +{ +} + + +/* +=============== +Constructor +=============== +*/ +CMapOverlay::CMapOverlay() +{ + m_overlayData = (qOverlay_s *)Sys_Malloc( sizeof( qOverlay_s ) ); + + m_unkFlt1 = 1.f; + m_unknownVec1 = vec3_origin; + + memset( m_overlayData, 0, sizeof( qOverlay_s ) ); + m_overlayData->m_mapOverlay = this; +} + +/* +=============== +Destructor +=============== +*/ +CMapOverlay::~CMapOverlay() +{ + if ( m_overlayData->m_shaderInfo ) + { + --*m_overlayData->m_shaderInfo->gap; + } + + Sys_Free( m_overlayData ); +} + +/* +=============== +setTexDef +=============== +*/ +void CMapOverlay::setTexDef( const qTexDef_s &texDef ) +{ + m_overlayData->m_texDef = texDef; +} + + +/* +=============== +Constructor +=============== +*/ +CMapPatch::CMapPatch( int numColumns, int numRows ) +{ + m_patchData = (qPatch_s *)Sys_Malloc( sizeof( qPatch_s ) ); + + if ( ( numColumns & 1 ) == 0 ) + { + numColumns = std::clamp( numColumns + 1, 3, 31 ); + } + + if ( ( numRows & 1 ) == 0 ) + { + numRows = std::clamp( numRows + 1, 3, 31 ); + } + + memset( m_patchData, 0, sizeof( *m_patchData ) ); + m_patchData->m_mapPatch = this; + + m_patchData->m_numColumns = numColumns; + m_patchData->m_numRows = numRows; +} + +/* +=============== +Destructor +=============== +*/ +CMapPatch::~CMapPatch() +{ + if ( m_patchData->m_shaderInfo ) + { + --*m_patchData->m_shaderInfo->gap; + } + + //freePatchPoints(); + + Sys_Free( m_patchData ); +} + +/* +=============== +setTexDef +=============== +*/ +void CMapPatch::setTexDef( const qTexDef_s &texDef ) +{ + m_patchData->m_texDef = texDef; +} diff --git a/utils/jmftools/mapbrush.h b/utils/jmftools/mapbrush.h new file mode 100644 index 0000000..f998cbf --- /dev/null +++ b/utils/jmftools/mapbrush.h @@ -0,0 +1,91 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPBRUSH_H ) +#define MAPBRUSH_H + +#include + +#include "PluginAPI.h" +#include "PluginWorld.h" + +class CVisGroup; + +class CMapOverlay; +class CMapPatch; + +class CMapBrush +{ +public: + CMapBrush(); + virtual ~CMapBrush(); + + static qBrush_s *create() + { + CMapBrush *mapBrush = new CMapBrush; + mapBrush->m_brushData->m_editorId = 0; + mapBrush->m_brushData->m_extraDataType = 0; + SetBits( mapBrush->m_brushData->m_editorFlags, EFL_DIRTY ); + return mapBrush->m_brushData; + } + +public: + void addToVisGroup( CVisGroup *visGroup ); + void removeFromAllVisGroups(); + +public: + qBrush_t *m_brushData; + int m_selectionId; + rgba_t m_cbColor; + rgba_t m_cbFaceColor; + vec3_t m_brushSize; + std::vector m_visGroupList; +}; + +class CMapOverlay +{ +public: + CMapOverlay(); + virtual ~CMapOverlay(); + + void setTexDef( const qTexDef_s &texDef ); + + static qOverlay_s *create() + { + CMapOverlay *mapOverlay = new CMapOverlay; + SetBits( mapOverlay->m_overlayData->m_editorFlags, EFL_DIRTY ); + return mapOverlay->m_overlayData; + } + +public: + qOverlay_s *m_overlayData; + vec3_t m_unknownVec1; + float m_unkFlt1; +}; + +class CMapPatch +{ +public: + CMapPatch( int numColumns, int numRows ); + virtual ~CMapPatch(); + + void setTexDef( const qTexDef_s &texDef ); + + static qPatch_s *create( int numColumns, int numRows ) + { + CMapPatch *mapPatch = new CMapPatch( numColumns, numRows ); + SetBits( mapPatch->m_patchData->m_editorFlags, EFL_DIRTY ); + return mapPatch->m_patchData; + } + +public: + qPatch_s *m_patchData; + char gap[16]; +}; + +#endif // !MAPBRUSH_H diff --git a/utils/jmftools/mapcamera.cpp b/utils/jmftools/mapcamera.cpp new file mode 100644 index 0000000..eed53f9 --- /dev/null +++ b/utils/jmftools/mapcamera.cpp @@ -0,0 +1,88 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapCamera::CMapCamera() +{ + m_cameraData = (qCamera_s *)Sys_Malloc( sizeof( qCamera_s ) ); + + m_selectionId = -1; + + m_cbColor1 = m_cbColor2 = rgba_t( 0, 0, 0, 255 ); + + m_vecAngles = vec3_origin; + + m_length = 0.f; + + memset( m_cameraData, 0, sizeof( *m_cameraData ) ); + m_cameraData->m_mapCamera = this; + + // TODO Random RGB for m_cbColor +} + +/* +=============== +Destructor +=============== +*/ +CMapCamera::~CMapCamera() +{ +} + +/* +=============== +setup +=============== +*/ +void CMapCamera::setup( const vec3_t &origin, const vec3_t &angles ) +{ + m_cameraData->m_vecOrigin = origin; + m_cameraData->m_vecAngles = angles; + + m_vecAngles = angles; + + float dist = sqrt( + ( angles[0] - origin[0] * angles[0] - origin[0] ) + + + ( angles[1] - origin[1] * angles[1] - origin[1] ) + + + ( angles[2] - origin[2] * angles[2] - origin[2] ) + ); + if ( dist >= FLT_EPSILON ) + { + m_length = dist; + } + else + { + m_length = 1.f; + } +} diff --git a/utils/jmftools/mapcamera.h b/utils/jmftools/mapcamera.h new file mode 100644 index 0000000..72fabc4 --- /dev/null +++ b/utils/jmftools/mapcamera.h @@ -0,0 +1,37 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPCAMERA_H ) +#define MAPCAMERA_H + +#include "PluginCamera.h" + +class CMapCamera +{ +public: + CMapCamera(); + virtual ~CMapCamera(); + + void setup( const vec3_t &origin, const vec3_t &angles ); + + static qCamera_s *create() + { + CMapCamera *mapCamera = new CMapCamera; + return mapCamera->m_cameraData; + } + +public: + qCamera_t *m_cameraData; + int m_selectionId; + rgba_t m_cbColor1; + rgba_t m_cbColor2; + vec3_t m_vecAngles; + float m_length; +}; + +#endif // !MAPCAMERA_H diff --git a/utils/jmftools/mapentity.cpp b/utils/jmftools/mapentity.cpp new file mode 100644 index 0000000..0a8a765 --- /dev/null +++ b/utils/jmftools/mapentity.cpp @@ -0,0 +1,220 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapEntity::CMapEntity() +{ + m_entityData = (qEntity_s *)Sys_Malloc( sizeof( qEntity_s ) ); + + m_currentGDClass = NULL; + + m_selectionId = -1; + + m_cbColor = rgba_t( 0, 0, 0, 255 ); + + m_vecData1 = NULL; + //m_visGroupList = NULL; + + memset( m_entityData, 0, sizeof( *m_entityData ) ); + m_entityData->m_mapEntity = this; + + reset(); + clearEpairs(); +} + +/* +=============== +Destructor +=============== +*/ +CMapEntity::~CMapEntity() +{ + removeFromAllVisGroups(); + + qBrush_s *nextBrushDef = NULL; + for ( qBrush_s *brushDef = m_entityData->m_brushList; brushDef != NULL; brushDef = nextBrushDef ) + { + nextBrushDef = brushDef->next; + + if ( brushDef->m_mapBrush ) + { + delete brushDef->m_mapBrush; + } + } + + freeUserData(); + + clearEpairs(); + + Sys_Free( m_entityData->m_className ); + m_entityData->m_className = NULL; + + Sys_Free( m_entityData->m_targetName ); + m_entityData->m_targetName = NULL; + + //CEntityKeys::clear( m_entityData->m_entityKeys ); + + //Spline_Free( m_entityData ); + + Sys_Free( m_entityData ); + m_entityData = NULL; +} + +/* +=============== +addToVisGroup +=============== +*/ +void CMapEntity::addToVisGroup( CVisGroup *visGroup ) +{ + if ( std::find( m_visGroupList.begin(), m_visGroupList.end(), visGroup ) == m_visGroupList.end() ) + { + m_visGroupList.push_back( visGroup ); + } +} + +/* +=============== +removeFromAllVisGroups +=============== +*/ +void CMapEntity::removeFromAllVisGroups() +{ +} + +/* +=============== +reset +=============== +*/ +void CMapEntity::reset() +{ + if ( !m_entityData->m_brushList ) + { + m_entityData->m_bboxMin = m_entityData->m_vecOrigin - 8.f; + m_entityData->m_bboxMax = m_entityData->m_vecOrigin + 8.f; + + if ( FBitSet( m_entityData->m_editorFlags, EFL_WORLDSPAWN ) ) + { + m_cbColor = rgba_t( 255, 255, 255, 255 ); + } + else + { + m_cbColor = rgba_t( 220, 30, 220, 255 ); + } + } +} + +/* +=============== +updateKeys +=============== +*/ +void CMapEntity::updateKeys( bool keepkeys ) +{ +} + +/* +=============== +freeUserData +=============== +*/ +void CMapEntity::freeUserData() +{ + if ( !m_entityData->m_drawData ) + { + m_entityData->m_modelType = mod_brush; + return; + } + + switch ( m_entityData->m_modelType ) + { + case mod_brush: + break; + case mod_sprite: + { + qSpriteDrawData_s *drawData = (qSpriteDrawData_s *)m_entityData->m_drawData; + + if ( drawData->m_spriteData ) + { + --drawData->m_spriteData->m_refCount; + + if ( drawData->m_spriteData->m_spriteShader ) + { + --*drawData->m_spriteData->m_spriteShader->gap; + } + } + break; + } + case mod_decal: + { + qDecal_s *drawData = (qDecal_s *)m_entityData->m_drawData; + + --drawData->m_decalShader->m_refCount; + //Decal_FreeFragments( drawData ); + break; + } + case mod_studio: + { + qStudioDrawData_s *drawData = (qStudioDrawData_s *)m_entityData->m_drawData; + + if ( drawData->m_studioData1 ) + { + --drawData->m_studioData1->m_refCount; + } + + if ( drawData->m_studioData2 ) + { + --drawData->m_studioData2->m_refCount; + } + break; + } +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + case mod_particles: + { + qParticlesDrawData_s *drawData = (qParticlesDrawData_s *)m_entityData->m_drawData; + + if ( drawData->m_particlesData ) + { + --drawData->m_particlesData->m_refCount; + } + break; + } +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + }; + + free( m_entityData->m_drawData ); + m_entityData->m_drawData = NULL; + + m_entityData->m_modelType = mod_brush; +} diff --git a/utils/jmftools/mapentity.h b/utils/jmftools/mapentity.h new file mode 100644 index 0000000..5180da8 --- /dev/null +++ b/utils/jmftools/mapentity.h @@ -0,0 +1,97 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPENTITY_H ) +#define MAPENTITY_H + +#include + +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginEntity.h" + +#include "math.h" +#include "mem.h" + +struct GDClass; +class CVisGroup; // mapworld.h + +class CMapEntity +{ +public: + CMapEntity(); + virtual ~CMapEntity(); + + static qEntity_t *create( const char *className, const vec3_t &origin, int editorFlags ) + { + CMapEntity *mapEntity = new CMapEntity; + mapEntity->m_entityData->m_editorId = 0; + mapEntity->m_entityData->m_vecOrigin = origin; + mapEntity->m_entityData->m_bboxMin = mapEntity->m_entityData->m_bboxMax = origin; + mapEntity->m_entityData->m_className = Sys_AllocString( className ); + mapEntity->m_entityData->m_editorFlags = ( editorFlags & 0xFFFDFFFB ) | 4; + return mapEntity->m_entityData; + } + + static void clearEpairs( epair_s **epairList ) + { + for ( epair_t *pair = *epairList, *next; pair != NULL; pair = next ) + { + next = pair->next; + + Sys_Free( pair->key ); + Sys_Free( pair->value ); + + Sys_Free( pair ); + } + *epairList = NULL; + } + +public: + void addToVisGroup( CVisGroup *visGroup ); + void removeFromAllVisGroups(); + +protected: + void reset(); + + void clearEpairs() { clearEpairs( &m_entityData->epairs ); } + + void updateKeys( bool keepkeys ); + + void freeUserData(); + +public: + qEntity_s *m_entityData; + GDClass *m_currentGDClass; + int m_selectionId; + rgba_t m_cbColor; + void *m_vecData1; + std::vector m_visGroupList; +}; + +FORCEINLINE epair_t *AllocEpair( const char *key, const char *value ) +{ + epair_t *pair = (epair_t *)Sys_Malloc( sizeof( epair_t ) ); + + pair->next = NULL; + pair->key = Sys_AllocString( key ); + pair->value = Sys_AllocString( value ); + + return pair; +} + +FORCEINLINE void AppendEpair( epair_t **list, const char *key, const char *value ) +{ + while ( *list ) + { + list = &( *list )->next; + } + + *list = AllocEpair( key, value ); +} + +#endif // !MAPENTITY_H diff --git a/utils/jmftools/mapface.cpp b/utils/jmftools/mapface.cpp new file mode 100644 index 0000000..9591c9c --- /dev/null +++ b/utils/jmftools/mapface.cpp @@ -0,0 +1,89 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapFace::CMapFace( int vertexCount ) +{ + m_faceData = (qFace_s *)Sys_Malloc( sizeof( qFace_s ) ); + + m_selectionId = -1; + + m_cbColor = rgba_t( 0, 0, 0, 255 ); + + m_unknownFloat = 1.f; + + memset( m_faceData, 0, sizeof( *m_faceData ) ); + + if ( vertexCount > 0 ) + { + m_faceData->m_vertices = (qVertex_s *)Sys_Malloc( vertexCount * sizeof( qVertex_s ) ); + } + + m_faceData->m_mapFace = this; + + m_faceData->m_vertexCount = vertexCount; + + if ( vertexCount == 3 ) + { + m_unknownInt = 4; + } + else + { + m_unknownInt = 2 * ( vertexCount != 4 ) + 7; + } +} + +/* +=============== +Destructor +=============== +*/ +CMapFace::~CMapFace() +{ + if ( m_faceData->m_shaderInfo ) + { + --*m_faceData->m_shaderInfo->gap; + } + + Sys_Free( m_faceData->m_vertices ); + Sys_Free( m_faceData ); +} + +/* +=============== +setTexDef +=============== +*/ +void CMapFace::setTexDef( const qTexDef_s &texDef ) +{ + m_faceData->m_texDef = texDef; +} diff --git a/utils/jmftools/mapface.h b/utils/jmftools/mapface.h new file mode 100644 index 0000000..dd33221 --- /dev/null +++ b/utils/jmftools/mapface.h @@ -0,0 +1,38 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPFACE_H ) +#define MAPFACE_H + +#include "PluginAPI.h" + +class CMapFace +{ +public: + CMapFace( int vertexCount ); + virtual ~CMapFace(); + + void setTexDef( const qTexDef_s &texDef ); + + static qFace_s *create( int vertexCount ) + { + CMapFace *mapFace = new CMapFace( vertexCount ); + SetBits( mapFace->m_faceData->m_editorFlags, EFL_DIRTY ); + return mapFace->m_faceData; + } + +public: + qFace_s *m_faceData; + int m_selectionId; + rgba_t m_cbColor; + float m_unknownFloat; + int m_unknownInt; + char gap[16]; +}; + +#endif // !MAPFACE_H diff --git a/utils/jmftools/mapgroup.cpp b/utils/jmftools/mapgroup.cpp new file mode 100644 index 0000000..f55b6eb --- /dev/null +++ b/utils/jmftools/mapgroup.cpp @@ -0,0 +1,56 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapGroup::CMapGroup() +{ + m_groupData = (qGroup_s *)Sys_Malloc( sizeof( qGroup_s ) ); + + m_selectionId = -1; + + m_cbColor1 = m_cbColor2 = rgba_t( 0, 0, 0, 255 ); + + memset( m_groupData, 0, sizeof( *m_groupData ) ); + m_groupData->m_mapGroup = this; + + // TODO Random RGB for m_cbColor1 +} + +/* +=============== +Destructor +=============== +*/ +CMapGroup::~CMapGroup() +{ + Sys_Free( m_groupData ); +} diff --git a/utils/jmftools/mapgroup.h b/utils/jmftools/mapgroup.h new file mode 100644 index 0000000..3438b15 --- /dev/null +++ b/utils/jmftools/mapgroup.h @@ -0,0 +1,33 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPGROUP_H ) +#define MAPGROUP_H + +#include "PluginGroups.h" + +class CMapGroup +{ +public: + CMapGroup(); + virtual ~CMapGroup(); + + static qGroup_s *create() + { + CMapGroup *mapGroup = new CMapGroup; + return mapGroup->m_groupData; + } + +public: + qGroup_t *m_groupData; + int m_selectionId; + rgba_t m_cbColor1; + rgba_t m_cbColor2; +}; + +#endif // !MAPGROUP_H diff --git a/utils/jmftools/mappath.cpp b/utils/jmftools/mappath.cpp new file mode 100644 index 0000000..44ea60e --- /dev/null +++ b/utils/jmftools/mappath.cpp @@ -0,0 +1,100 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapPath::CMapPath() +{ + m_pathData = (qPath_t *)Sys_Malloc( sizeof( qPath_t ) ); + + m_cbColor = rgba_t( 0, 0, 0, 255 ); + + m_unknown = false; + + memset( m_pathData, 0, sizeof( *m_pathData ) ); + m_pathData->m_mapPath = this; +} + +/* +=============== +Destructor +=============== +*/ +CMapPath::~CMapPath() +{ + Sys_Free( m_pathData->m_pathClassname ); + Sys_Free( m_pathData->m_pathName ); + + qNode_s *nextNodeDef = NULL; + for ( qNode_s *nodeDef = m_pathData->m_nodeList; nodeDef != NULL; nodeDef = nextNodeDef ) + { + nextNodeDef = nodeDef->next; + + if ( nodeDef->m_mapNode ) + { + delete nodeDef->m_mapNode; + } + } + + Sys_Free( m_pathData ); +} + + +/* +=============== +Constructor +=============== +*/ +CMapNode::CMapNode() +{ + m_nodeData = (qNode_s *)Sys_Malloc( sizeof( qNode_s ) ); + + m_selectionId = -1; + + m_cbColor = rgba_t( 0, 0, 0, 255 ); + + m_nodeData = (qNode_s *)Sys_Malloc( sizeof( qNode_s ) ); + + memset( m_nodeData, 0, sizeof( *m_nodeData ) ); + m_nodeData->m_mapNode = this; +} + +/* +=============== +Destructor +=============== +*/ +CMapNode::~CMapNode() +{ + clearEpairs(); + + Sys_Free( m_nodeData ); +} diff --git a/utils/jmftools/mappath.h b/utils/jmftools/mappath.h new file mode 100644 index 0000000..97ba1ad --- /dev/null +++ b/utils/jmftools/mappath.h @@ -0,0 +1,58 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPPATH_H ) +#define MAPPATH_H + +#include "PluginPaths.h" +#include "PluginNodes.h" + +#include "mapentity.h" + +class CMapNode; + +class CMapPath +{ +public: + CMapPath(); + virtual ~CMapPath(); + + static qPath_t *create() + { + CMapPath *mapPath = new CMapPath; + return mapPath->m_pathData; + } + +public: + qPath_t *m_pathData; + rgba_t m_cbColor; + bool m_unknown; +}; + +class CMapNode +{ +public: + CMapNode(); + virtual ~CMapNode(); + + static qNode_t *create() + { + CMapNode *mapNode = new CMapNode; + return mapNode->m_nodeData; + } + +protected: + void clearEpairs() { CMapEntity::clearEpairs( &m_nodeData->epairs ); } + +public: + qNode_t *m_nodeData; + int m_selectionId; + rgba_t m_cbColor; +}; + +#endif // !MAPPATH_H diff --git a/utils/jmftools/mapworld.cpp b/utils/jmftools/mapworld.cpp new file mode 100644 index 0000000..9f2f74d --- /dev/null +++ b/utils/jmftools/mapworld.cpp @@ -0,0 +1,345 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* +=============== +Constructor +=============== +*/ +CMapWorld::CMapWorld() +{ + m_gameConfig = NULL; //gameConfig; + + m_vecCordonMin = m_vecCordonMax = m_skyData.m_skyAxis = vec3_origin; + + m_project = NULL; //project; + + m_octree = NULL; + + /* + v4 = operator new[](a1: 0x80000u); + v5 = (_QWORD *)v4; + do + *v5++ = 0; + while ( v5 != (_QWORD *)(v4 + 0x80000) ); + CMapWorld_gap = v4; + + &CMapWorld_gap[8] = 1; + &CMapWorld_gap[12] = 1; + &CMapWorld_gap[16] = 0; + &CMapWorld_gap[20] = 0; + */ + + m_lastUsedEntityEditorId = 0; + m_lastUsedBrushEditorId = 0; + m_lastUsedFaceEditorId = 0; + m_lastUsedNodeEditorId = 0; + m_lastUsedPathEditorId = 0; + m_lastUsedCameraEditorId = 0; + //CMapWorld_gap3 = 0; + m_lastUsedVisGroupEditorId = 0; + //CMapWorld_gap4 = 0; + + //&CMapWorld_gap4[8] = 0; + //&CMapWorld_gap4[16] = 0; + //&CMapWorld_gap4[24] = 0; + //&CMapWorld_gap4[32] = 0; + //&CMapWorld_gap4[40] = 0; + + //m_visGroupList = NULL; + + memset( this, 0, sizeof( qWorld_s ) ); + m_mapWorld = this; + + //updateSelectionMode(); +} + +/* +=============== +Destructor +=============== +*/ +CMapWorld::~CMapWorld() +{ + clear(); + + if ( m_octree ) + { + delete m_octree; + } + + /* + v3 = *(void **)this->CMapWorld_gap; + if ( v3 != nullptr ) + operator delete[](a1: v3); + */ +} + +/* +=============== +createVisGroup +=============== +*/ +CVisGroup *CMapWorld::createVisGroup( const char *visGroupName ) +{ + CVisGroup *visGroup = new CVisGroup; + visGroup->setName( visGroupName ); + + m_visGroupList.push_back( visGroup ); + + //setModified( true ); + + return visGroup; +} + +/* +=============== +pushGroup +=============== +*/ +void CMapWorld::pushGroup( qGroup_s *groupDef ) +{ + //groupDef->m_mapGroup = alloc_selectionId(); + + groupDef->prev = NULL; + groupDef->next = m_groupList; + if ( m_groupList != NULL ) + { + m_groupList->prev = groupDef; + } + m_groupList = groupDef; +} + +/* +=============== +buildPackageList +=============== +*/ +bool CMapWorld::buildPackageList( char **outBuf, char listSeparator, int removeVolumePrefix ) +{ + if ( !outBuf ) + { + return false; + } + + Sys_Free( *outBuf ); + + // TODO + *outBuf = Sys_AllocString( "" ); + + return true; +} + +/* +=============== +clear +=============== +*/ +void CMapWorld::clear() +{ + SetBits( m_editorFlags, EFL_FRESHWORLD ); + + removeAllEntities(); + removeAllVisGroups(); + removeAllCameras(); + removeAllPaths(); + //unloadPointFile(); +} + +/* +=============== +removeAllEntities +=============== +*/ +void CMapWorld::removeAllEntities() +{ + qEntity_s *nextEntityDef = NULL; + for ( qEntity_s *entityDef = m_entityList; entityDef != NULL; entityDef = nextEntityDef ) + { + nextEntityDef = entityDef->next; + + for ( qBrush_s *brushDef = entityDef->m_brushList; brushDef != NULL; brushDef = brushDef->next ) + { + brushDef->m_mapBrush->removeFromAllVisGroups(); + } + + entityDef->m_mapEntity->removeFromAllVisGroups(); + + if ( entityDef->m_mapEntity ) + { + delete entityDef->m_mapEntity; + } + } + + qGroup_s *nextGroupDef = NULL; + for ( qGroup_s *groupDef = m_groupList; groupDef != NULL; groupDef = nextGroupDef ) + { + nextGroupDef = groupDef->next; + + if ( groupDef->m_mapGroup ) + { + delete groupDef->m_mapGroup; + } + } + + m_entityList = NULL; + m_groupList = NULL; + m_lastEntity = NULL; + m_selEntityList = NULL; + m_selBrushList = NULL; + m_selGroupList = NULL; + m_selFaceList = NULL; +} + +/* +=============== +removeAllVisGroups +=============== +*/ +void CMapWorld::removeAllVisGroups() +{ + for ( auto visGroup : m_visGroupList ) + { + if ( visGroup ) + { + delete visGroup; + } + } + + m_lastUsedVisGroupEditorId = 0; +} + +/* +=============== +removeAllCameras +=============== +*/ +void CMapWorld::removeAllCameras() +{ + qCamera_s *nextCameraDef = NULL; + for ( qCamera_s *cameraDef = m_cameraList; cameraDef != NULL; cameraDef = nextCameraDef ) + { + nextCameraDef = cameraDef->next; + + if ( cameraDef->m_mapCamera ) + { + delete cameraDef->m_mapCamera; + } + } + + m_cameraList = NULL; + m_selCamera = NULL; +} + +/* +=============== +removeAllPaths +=============== +*/ +void CMapWorld::removeAllPaths() +{ + qPath_s *nextPathDef = NULL; + for ( qPath_s *pathDef = m_pathList; pathDef != NULL; pathDef = nextPathDef ) + { + nextPathDef = pathDef->next; + + if ( pathDef->m_mapPath ) + { + delete pathDef->m_mapPath; + } + } + + m_pathList = NULL; + m_selNodeList = NULL; +} + + +/* +=============== +Constructor +=============== +*/ +CVisGroup::CVisGroup() +{ + m_name = NULL; + m_cbColor = rgba_t( 0, 0, 0, 0 ); + m_editorId = 0; + m_visible = false; + + gap = 0; +} + +/* +=============== +Destructor +=============== +*/ +CVisGroup::~CVisGroup() +{ + Sys_Free( m_name ); +} + +/* +=============== +setName +=============== +*/ +void CVisGroup::setName( const char *name ) +{ + if ( m_name && stricmp( m_name, name ) != 0 ) + { + free( m_name ); + m_name = NULL; + } + + if ( !m_name ) + { + m_name = Sys_AllocString( name ); + } +} + + +/* +=============== +Constructor +=============== +*/ +CBackgroundImage::CBackgroundImage() +{ + m_filePath = NULL; +} + +/* +=============== +Destructor +=============== +*/ +CBackgroundImage::~CBackgroundImage() +{ + Sys_Free( m_filePath ); +} diff --git a/utils/jmftools/mapworld.h b/utils/jmftools/mapworld.h new file mode 100644 index 0000000..ab9c3b9 --- /dev/null +++ b/utils/jmftools/mapworld.h @@ -0,0 +1,143 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MAPWORLD_H ) +#define MAPWORLD_H + +#include + +#include "PluginWorld.h" + +struct CGameConfig; +class CProject; +class COctree; + +class CVisGroup; + +class CMapWorld : public qWorld_s +{ +public: + CMapWorld(); + ~CMapWorld(); + + CVisGroup *createVisGroup( const char *visGroupName ); + + void pushGroup( qGroup_s *groupDef ); + + bool buildPackageList( char **outBuf, char listSeparator, int removeVolumePrefix ); + +public: + void clear(); + void removeAllEntities(); + void removeAllVisGroups(); + void removeAllCameras(); + void removeAllPaths(); + +public: + CGameConfig *m_gameConfig; + CProject *m_project; + COctree *m_octree; + + char CMapWorld_gap[24]; + int m_lastUsedEntityEditorId; + int m_lastUsedBrushEditorId; + int m_lastUsedFaceEditorId; + int m_lastUsedNodeEditorId; + int m_lastUsedPathEditorId; + int m_lastUsedCameraEditorId; + char CMapWorld_gap3[4]; + int m_lastUsedVisGroupEditorId; + char CMapWorld_gap4[48]; + + std::vector m_visGroupList; +}; + +template +void push( T *&linkedList, T *&linkedListLast, T *objectDef ) +{ + objectDef->next = NULL; + objectDef->prev = linkedListLast; + if ( linkedListLast != NULL ) + { + linkedListLast->next = objectDef; + } + if ( linkedList == NULL ) + { + linkedList = objectDef; + } + linkedListLast = objectDef; +} + +template +void pushSelected( T *&linkedList, T *objectDef ) +{ + objectDef->selectedNext = NULL; + objectDef->selectedPrev = linkedList; + if ( linkedList != NULL ) + { + linkedList->selectedNext = objectDef; + } + linkedList = objectDef; +} + +#define GetNewEditorId( object, id ) if ( id == -1 ) id = 1; else ++id; object->m_editorId = id; + +class CVisGroup +{ +public: + CVisGroup(); + ~CVisGroup(); + + void setName( const char *name ); + +public: + char *m_name; + rgba_t m_cbColor; + int m_editorId; + int gap; + bool m_visible; +}; + + +class CBackgroundImage +{ +public: + struct Parms + { + Parms() + { + m_scale = 1.0; + m_alpha = 255; + m_filter = 1; + m_flags = 0; + m_xOffset = 0; + m_yOffset = 0; + } + + double m_scale; + int m_alpha; + int m_filter; + int m_flags; + int m_xOffset; + int m_yOffset; + }; + +public: + CBackgroundImage(); + ~CBackgroundImage(); + + void setParms( const CBackgroundImage::Parms &parms ) { m_parms = parms; } + + char *m_filePath; + Parms m_parms; + /*char gap2[8]; + int m_glTextureId; + bool m_loaded;*/ +}; + +#endif // !MAPWORLD_H diff --git a/utils/jmftools/math.cpp b/utils/jmftools/math.cpp new file mode 100644 index 0000000..9837749 --- /dev/null +++ b/utils/jmftools/math.cpp @@ -0,0 +1,169 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include + +// Plugin API +#include "BaseTypes.h" + +vec3_t vec3_origin( 0.f, 0.f, 0.f ); + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +/* +=============== +FormatFloat +=============== +*/ +static const char *FormatFloat( int precision, bool forceDecimal, float value ) +{ + static char buffers[32][32]; + static unsigned currentBuffer = 0; + + char *out = buffers[currentBuffer]; + currentBuffer = ( currentBuffer + 1 ) & 31; + + memset( out, 0, 32 ); + + if ( precision >= 0 ) + { + char format[8]; + + strcpy( format, "%.0f" ); + + if ( precision <= 8 ) + { + format[2] = char( '0' + precision ); + sprintf( out, format, value ); + + if ( precision == 0 ) + return out; + } + else + { + format[2] = '9'; + sprintf( out, format, value ); + } + + int len = (int)strlen( out ) - 1; + + if ( len > 0 ) + { + char *p = &out[len]; + + while ( len > 0 && *p == '0' ) + { + *p-- = '\0'; + --len; + } + + if ( *p == '.' ) + { + if ( forceDecimal ) + { + if ( (int)strlen( out ) - 1 > len ) + { + p[1] = '0'; + p[2] = '\0'; + } + } + else + { + *p = '\0'; + } + } + } + + return out; + } + + sprintf( out, "%g", value ); + return out; +} + +/* +=============== +Sys_PrintValue +=============== +*/ +/*static*/ const char *Sys_PrintValue( float value ) +{ + if ( value == 0.f ) + { + return "0"; + } + + return FormatFloat( 6, false, value ); +} + +/* +=============== +Sys_PrintMapCoord +=============== +*/ +/*static*/ const char *Sys_PrintMapCoord( float coord ) +{ + if ( coord == 0.f ) + { + return "0"; + } + + return FormatFloat( 6, false, coord ); +} + +/* +=============== +Sys_PrintAxis +=============== +*/ +/*static*/ const char *Sys_PrintAxis( float axis ) +{ + if ( axis == 0.f ) + { + return "0"; + } + + return FormatFloat( 6, false, axis ); +} +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + +/* +=============== +Sys_SnapVertex +=============== +*/ +/*static*/ void Sys_SnapVertex( float *rgflVertex ) +{ +} + +/* +=============== +Sys_SnapAxis +=============== +*/ +/*static*/ void Sys_SnapAxis( int num, float *rgflAxis ) +{ +} + +/* +=============== +Sys_SnapVertexToGrid +=============== +*/ +/*static*/ void Sys_SnapVertexToGrid( float *rgflVertex ) +{ +} + +/* +=============== +Sys_SnapMapVertex +=============== +*/ +/*static*/ void Sys_SnapMapVertex( float *rgflVertex ) +{ +} diff --git a/utils/jmftools/math.h b/utils/jmftools/math.h new file mode 100644 index 0000000..d765b37 --- /dev/null +++ b/utils/jmftools/math.h @@ -0,0 +1,24 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MATH_H ) +#define MATH_H + +extern vec3_t vec3_origin; + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +const char *Sys_PrintValue( float value ); +const char *Sys_PrintMapCoord( float coord ); +const char *Sys_PrintAxis( float axis ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA +void Sys_SnapVertex( float *rgflVertex ); +void Sys_SnapAxis( int num, float *rgflAxis ); +void Sys_SnapVertexToGrid( float *rgflVertex ); +void Sys_SnapMapVertex( float *rgflVertex ); + +#endif // !MATH_H diff --git a/utils/jmftools/mem.cpp b/utils/jmftools/mem.cpp new file mode 100644 index 0000000..e4b9754 --- /dev/null +++ b/utils/jmftools/mem.cpp @@ -0,0 +1,47 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include +#include +#include + +/*static*/ void *Sys_Malloc( size_t size ) +{ + void *ptr = calloc( 1, size ); + if ( !ptr ) + { + //CFloatWinding::cleanup(); + //CDoubleWinding::cleanup(); + //TempBuffer_Flush(); + return calloc( 1, size ); + } + + return ptr; +} + +/*static*/ void Sys_Free( void *ptr ) +{ + if ( ptr ) + { + free( ptr ); + } +} + +/*static*/ char *Sys_AllocString( const char *src ) +{ + if ( !src ) + { + return NULL; + } + + char *dest = (char *)Sys_Malloc( strlen( src ) + 1 ); + strcat( dest, src ); + + return dest; +} diff --git a/utils/jmftools/mem.h b/utils/jmftools/mem.h new file mode 100644 index 0000000..b3774fc --- /dev/null +++ b/utils/jmftools/mem.h @@ -0,0 +1,17 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( MEM_H ) +#define MEM_H + +extern void *Sys_Malloc( size_t size ); +extern void Sys_Free( void *ptr ); + +extern char *Sys_AllocString( const char *src ); + +#endif // !MEM_H diff --git a/utils/jmftools/serializer_jmf.cpp b/utils/jmftools/serializer_jmf.cpp new file mode 100644 index 0000000..7620a07 --- /dev/null +++ b/utils/jmftools/serializer_jmf.cpp @@ -0,0 +1,1742 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include +#include +#include + +// Plugin API +#define PLUGINEDITORFUNCTIONS_H +#define ENTITYAPI_NO_INLINEFUNCS +#include "PluginMeta.h" + +#include "DataSerializer.h" +#include "serializer_jmf.h" + +#include "logger.h" +#include "math.h" +#include "mem.h" + +// Containers +#include "mapbrush.h" +#include "mapcamera.h" +#include "mapentity.h" +#include "mapface.h" +#include "mapgroup.h" +#include "mappath.h" +#include "mapworld.h" + +/* This struct is written as a 124 byte blob to the JMF */ +typedef struct qTexDef_117_s +{ + // w is xShift + vec4_t m_UAxis; + + // w is yShift + vec4_t m_VAxis; + + vec2_t m_scale; + + float m_rotate; + + /* See TEXALIGN_ defines above */ + int m_textureAlignment; + + int m_value; // Quake II "Value" + int m_surfaceFlags; // Quake II SurfaceFlags + int m_contentFlags; // Quake II ContentFlags + + char m_textureName[64]; +} qTexDef_117_t; +COMPILE_TIME_ASSERT( sizeof( qTexDef_117_s ) == 124 ); + +FORCEINLINE void ConvertTexDef( const qTexDef_117_s &oldTexDef, qTexDef_s &texDef ) +{ + texDef.m_UAxis = oldTexDef.m_UAxis; + texDef.m_VAxis = oldTexDef.m_VAxis; + texDef.m_scale = oldTexDef.m_scale; + texDef.m_rotate = oldTexDef.m_rotate; + texDef.m_textureAlignment = oldTexDef.m_textureAlignment; + texDef.m_value = oldTexDef.m_value; + texDef.m_lightmapScale = 0; + texDef.m_surfaceFlags = oldTexDef.m_surfaceFlags; + texDef.m_contentFlags = oldTexDef.m_contentFlags; + memcpy( texDef.m_textureName, oldTexDef.m_textureName, sizeof( texDef.m_textureName ) ); +} + +/* +=============== +JMFSerializer +=============== +*/ +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +JMFSerializer::JMFSerializer( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ) + : Serializer( filePath, seekOffset, readLimit, worldDef, FMODE_STDIO_BINARY ) +#else +JMFSerializer::JMFSerializer( const char *filePath, struct qWorld_s *worldDef ) + : Serializer( filePath, worldDef, FMODE_STDIO_BINARY ) +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA +{ + // worldDef is required for the JMF Serializer + assert( worldDef ); + assert( worldDef->m_mapWorld ); + + m_mapWorld = worldDef->m_mapWorld; + + m_jmfVersion = 0; + + m_exportPath[0] = '\0'; +} + +/* +=============== +~JMFSerializer +=============== +*/ +JMFSerializer::~JMFSerializer() +{ +} + +/* +=============== +Export +=============== +*/ +bool JMFSerializer::Export() +{ + if ( !m_mapWorld ) + { + return false; + } + + if ( !FBitSet( m_mapWorld->m_editorFlags, ( 1 << 23 ) ) ) + { + Sys_Printf( "Saving: \"%s\"", m_filePath ); + } + + m_jmfVersion = 122; + + if ( !OpenForWrite() ) + { + return false; + } + + if ( !ProcessHeader() ) + { + Sys_Error( "can't write file header" ); + return false; + } + + // Export info + { + if ( m_exportPath[0] == '\0' ) + { + setExportPath( m_filePath ); + } + + // NOTE: Originally exportPath is a element of a list inside m_project and hasExportPath is a size of that list, + // but since only one element of the list is used there's no reason to replicate the original logic. + + int hasExportPath = ( m_exportPath[0] != '\0' ); + if ( !WriteData( &hasExportPath, sizeof( int ) ) ) + { + Sys_Error( "can't write export info" ); + return false; + } + + if ( hasExportPath != 0 ) + { + if ( !WriteString( m_exportPath ) ) + { + Sys_Error( "can't write export info" ); + return false; + } + } + } + + assert( m_writeMode ); + + // Background images + { + // TODO: Read bgImage from a list inside m_project + CBackgroundImage bgImage; + memset( &bgImage, 0, sizeof( CBackgroundImage ) ); + + bgImage.m_filePath = Sys_AllocString( "" ); + + for ( int i = 0; i < 3; i++ ) + { + if ( !SerializeBackground( &bgImage ) ) + { + return false; + } + } + } + + // Groups + { + int groupCount = 0; + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + // Serialize only useful groups + if ( groupDef->m_objectCount > 0 ) + { + ++groupCount; + } + } + + if ( !WriteData( &groupCount, sizeof( int ) ) ) + { + return false; + } + + int ignoredGroupCount = 0; + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + // Serialize only useful groups + if ( groupDef->m_objectCount > 0 ) + { + if ( !SerializeGroup( groupDef ) ) + { + return false; + } + } + else + { + ++ignoredGroupCount; + } + } + + if ( ignoredGroupCount != 0 ) + { + Sys_Printf( "%i empty group(s) ignored", ignoredGroupCount ); + } + } + + // VisGroups + { + int visGroupCount = (int)m_mapWorld->m_visGroupList.size(); + if ( !WriteData( &visGroupCount, sizeof( int ) ) ) + { + return false; + } + + for ( auto visGroup : m_mapWorld->m_visGroupList ) + { + if ( !SerializeVisGroup( visGroup ) ) + { + return false; + } + } + } + + if ( !WriteData( &m_mapWorld->m_vecCordonMin, sizeof( vec3_t ) ) || !WriteData( &m_mapWorld->m_vecCordonMax, sizeof( vec3_t ) ) ) + { + return false; + } + + // Cameras + { + int cameraCount = 0; + for ( qCamera_s *cameraDef = m_mapWorld->m_cameraList; cameraDef != NULL; cameraDef = cameraDef->next ) + { + ++cameraCount; + } + + if ( !WriteData( &cameraCount, sizeof( int ) ) ) + { + return false; + } + + for ( qCamera_s *cameraDef = m_mapWorld->m_cameraList; cameraDef != NULL; cameraDef = cameraDef->next ) + { + if ( !SerializeCamera( cameraDef ) ) + { + return false; + } + } + } + + // Paths + { + int pathCount = 0; + for ( qPath_s *pathDef = m_mapWorld->m_pathList; pathDef != NULL; pathDef = pathDef->next ) + { + ++pathCount; + } + + if ( !WriteData( &pathCount, sizeof( int ) ) ) + { + return false; + } + + for ( qPath_s *pathDef = m_mapWorld->m_pathList; pathDef != NULL; pathDef = pathDef->next ) + { + if ( /*pathDef->m_nodeList != NULL ||*/ !SerializePath( pathDef ) ) + { + return false; + } + } + } + + // Entities + { + for ( qEntity_s *entityDef = m_mapWorld->m_entityList; entityDef != NULL; entityDef = entityDef->next ) + { + if ( !SerializeEntity( entityDef ) ) + { + return false; + } + } + } + + return true; +} + +/* +=============== +Import +=============== +*/ +bool JMFSerializer::Import() +{ + if ( !m_mapWorld ) + { + return false; + } + + if ( !FBitSet( m_mapWorld->m_editorFlags, ( 1 << 23 ) ) ) + { + Sys_Printf( "Loading: \"%s\"", m_filePath ); + } + + if ( !OpenForRead() ) + { + return false; + } + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fseek( m_fileHandle, 0, SEEK_END ); + m_readLimit = (size_t)ftell( m_fileHandle ); + fseek( m_fileHandle, 0, SEEK_SET ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + + m_jmfVersion = 0; + + if ( !ProcessHeader() ) + { + return false; + } + + if ( m_jmfVersion >= 114 ) + { + int hasExportPath = 0; + if ( ReadData( &hasExportPath, sizeof( int ) ) && hasExportPath != 0 ) + { + char *exportPath = NULL; + if ( ReadString( &exportPath ) && ( exportPath && exportPath[0] ) ) + { + // Append to a list inside m_project + } + } + } + + if ( m_jmfVersion >= 122 ) + { + for ( int i = 0; i < 3; i++ ) + { + // Use a list inside m_project + SerializeBackground( NULL ); + } + } + + int groupCount = 0; + if ( ReadData( &groupCount, sizeof( int ) ) ) + { + for ( int i = 0; i < groupCount; i++ ) + { + SerializeGroup( NULL ); + } + } + + int visGroupCount = 0; + if ( ReadData( &visGroupCount, sizeof( int ) ) ) + { + for ( int i = 0; i < visGroupCount; i++ ) + { + SerializeVisGroup( NULL ); + } + } + + if ( !ReadData( &m_worldDef->m_vecCordonMin, sizeof( vec3_t ) ) || !ReadData( &m_worldDef->m_vecCordonMax, sizeof( vec3_t ) ) ) + { + return false; + } + + int cameraCount = 0; + if ( ReadData( &cameraCount, sizeof( int ) ) ) + { + for ( int i = 0; i < cameraCount; i++ ) + { + SerializeCamera( NULL ); + } + } + + int pathCount = 0; + if ( ReadData( &pathCount, sizeof( int ) ) ) + { + for ( int i = 0; i < pathCount; i++ ) + { + SerializePath( NULL ); + } + } + + while ( SerializeEntity( NULL ) ) + ; + + if ( !m_worldDef->m_entityList ) + { + Sys_Error( "the file is corrupt" ); + return false; + } + + return true; +} + +/* +=============== +WriteString +=============== +*/ +bool JMFSerializer::WriteString( const char *data ) +{ + int length = -1; + + if ( data != NULL ) + { + length = strlen( data ); + } + + bool bOK = WriteData( &length, sizeof( int ) ); + if ( bOK && length > 0 ) + { + bOK = WriteData( data, length ); + } + + return bOK; +} + +/* +=============== +ReadString +=============== +*/ +bool JMFSerializer::ReadString( char **outBuf, bool showerror /*= true*/ ) +{ + int length = -1; + + if ( !ReadData( &length, sizeof( int ), showerror ) || ( length == -1 ) ) + { + *outBuf = NULL; + return false; + } + + char *string = (char *)Sys_Malloc( length + 1 ); + if ( !string ) + { + *outBuf = NULL; + return false; + } + + if ( length > 0 && !ReadData( string, length ) ) + { + free( string ); + *outBuf = NULL; + return false; + } + + *outBuf = string; + return true; +} + +/* +=============== +WriteStringVersion +=============== +*/ +bool JMFSerializer::WriteStringVersion( const char *data, int jmfVersion ) +{ + if ( m_jmfVersion >= jmfVersion ) + { + return WriteString( data ); + } + + return false; +} + +/* +=============== +ReadStringVersion +=============== +*/ +bool JMFSerializer::ReadStringVersion( char **outBuf, int jmfVersion, bool showerror /*= true*/ ) +{ + if ( m_jmfVersion >= jmfVersion ) + { + return ReadString( outBuf, showerror ); + } + + return false; +} + +/* +=============== +ProcessHeader +=============== +*/ +bool JMFSerializer::ProcessHeader() +{ + if ( m_writeMode ) + { + int magic = JHMFHEADER; + bool bOK = WriteData( &magic, sizeof( int ) ); + if ( bOK ) + { + bOK = WriteData( &m_jmfVersion, sizeof( int ) ); + } + + return bOK; + } + else + { + int magic = 0; + if ( ReadData( &magic, sizeof( int ) ) && magic == JHMFHEADER ) + { + if ( ReadData( &m_jmfVersion, sizeof( int ) ) && m_jmfVersion >= 111 ) + { + return true; + } + + Sys_Error( "bad file version (%i should be %i or higher)", m_jmfVersion, 111 ); + return false; + } + } + + Sys_Error( "bad file header" ); + return false; +} + +/* +=============== +SerializeBackground +=============== +*/ +bool JMFSerializer::SerializeBackground( CBackgroundImage *bgImage ) +{ + if ( m_writeMode ) + { + if ( bgImage != NULL ) + { + if ( !WriteString( bgImage->m_filePath ) ) + { + return false; + } + + if ( !WriteData( &bgImage->m_parms, sizeof( CBackgroundImage::Parms ) ) ) + { + return false; + } + } + else + { + if ( !WriteString( NULL ) ) + { + return false; + } + + CBackgroundImage::Parms parms; + memset( &parms, 0, sizeof( CBackgroundImage::Parms ) ); + if ( !WriteData( &parms, sizeof( CBackgroundImage::Parms ) ) ) + { + return false; + } + } + + return true; + } + else + { + char *filePath = NULL; + if ( !ReadString( &filePath ) ) + { + return false; + } + + if ( bgImage != NULL ) + { + bgImage->m_filePath = Sys_AllocString( filePath ); + + CBackgroundImage::Parms parms {}; + if ( !ReadData( &parms, sizeof( CBackgroundImage::Parms ) ) ) + { + return false; + } + + bgImage->setParms( parms ); + } + else + { + CBackgroundImage::Parms parms {}; + if ( !ReadData( &parms, sizeof( CBackgroundImage::Parms ) ) ) + { + return false; + } + } + + return true; + } + + return false; +} + +/* +=============== +SerializeGroup +=============== +*/ +bool JMFSerializer::SerializeGroup( qGroup_s *groupDef ) +{ + if ( m_writeMode ) + { + assert( groupDef ); + + if ( !FBitSet( groupDef->m_editorFlags, EFL_IGNORE ) ) + { + int parentEditorId = 0; + rgba_t editorColor = groupDef->m_mapGroup->m_cbColor1; + + if ( groupDef->m_parent ) + { + if ( !SerializeGroup( groupDef->m_parent ) ) + { + return false; + } + + // This code makes no sense at all. + // It must get the color from groupDef->m_parent->m_mapGroup and also check if m_mapGroup is not NULL + editorColor = groupDef->m_mapGroup->m_cbColor1; + + if ( groupDef->m_parent ) + { + parentEditorId = groupDef->m_parent->m_editorId; + } + } + + if ( !WriteData( &groupDef->m_editorId, sizeof( int ) ) || + !WriteData( &parentEditorId, sizeof( int ) ) || + !WriteData( &groupDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &groupDef->m_objectCount, sizeof( int ) ) || + !WriteData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + SetBits( groupDef->m_editorFlags, EFL_IGNORE ); + return true; + } + } + else + { + int editorId = 0; + int parentEditorId = 0; + int editorFlags = 0; + int objectCount = 0; + rgba_t editorColor; + + if ( !ReadData( &editorId, sizeof( int ) ) || + !ReadData( &parentEditorId, sizeof( int ) ) || + !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &objectCount, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + qGroup_s *groupDef = CMapGroup::create(); + m_mapWorld->pushGroup( groupDef ); + + groupDef->m_ownerWorld = m_mapWorld; + + groupDef->m_editorFlags = editorFlags; + groupDef->m_editorId = editorId; + groupDef->m_objectCount = objectCount; + + groupDef->m_mapGroup->m_cbColor1 = editorColor; + //groupDef->m_mapGroup->calcFinalColor(); + + if ( parentEditorId != 0 ) + { + for ( qGroup_s *gDef = m_worldDef->m_groupList; gDef != NULL; gDef = gDef->next ) + { + if ( gDef->m_editorId == parentEditorId ) + { + groupDef->m_parent = gDef; + break; + } + } + } + + if ( FBitSet( groupDef->m_editorFlags, EFL_SELECTED ) ) + { + ClearBits( groupDef->m_editorFlags, EFL_SELECTED ); + SetBits( groupDef->m_editorFlags, EFL_SELECTED ); + + pushSelected( m_mapWorld->m_selGroupList, groupDef ); + } + + return true; + } + + return false; +} + +/* +=============== +SerializeVisGroup +=============== +*/ +bool JMFSerializer::SerializeVisGroup( CVisGroup *visGroupDef ) +{ + if ( m_writeMode ) + { + assert( visGroupDef ); + + if ( !WriteString( visGroupDef->m_name ) || + !WriteData( &visGroupDef->m_editorId, sizeof( int ) ) || + !WriteData( &visGroupDef->m_cbColor, sizeof( rgba_t ) ) || + !WriteData( &visGroupDef->m_visible, sizeof( bool ) ) ) + { + return false; + } + + return true; + } + else + { + char *visGroupName = NULL; + if ( !ReadString( &visGroupName ) ) + { + return false; + } + + CVisGroup *visGroup = m_mapWorld->createVisGroup( visGroupName ); + + int editorId = 0; + rgba_t editorColor = rgba_t( 0, 0, 0, 255 ); + bool isVisible = true; + + if ( !ReadData( &editorId, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) || + !ReadData( &isVisible, sizeof( bool ) ) ) + { + return false; + } + + visGroup->m_editorId = editorId; + visGroup->m_cbColor = editorColor; + visGroup->m_visible = isVisible; + return true; + } + + return false; +} + +/* +=============== +SerializeCamera +=============== +*/ +bool JMFSerializer::SerializeCamera( qCamera_s *cameraDef ) +{ + if ( m_writeMode ) + { + assert( cameraDef ); + + if ( !WriteData( &cameraDef->m_vecOrigin, sizeof( vec3_t ) ) || + !WriteData( &cameraDef->m_vecAngles, sizeof( vec3_t ) ) || + !WriteData( &cameraDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &cameraDef->m_mapCamera->m_cbColor1, sizeof( rgba_t ) ) ) + { + return false; + } + + return true; + } + else + { + vec3_t origin; + vec3_t angles; + int editorFlags = 0; + rgba_t editorColor; + + if ( !ReadData( &origin, sizeof( vec3_t ) ) || + !ReadData( &angles, sizeof( vec3_t ) ) || + !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + qCamera_s *cameraDef = CMapCamera::create(); + cameraDef->m_ownerWorld = m_mapWorld; + + //cameraDef->m_mapCamera->m_selectionId = m_mapWorld->alloc_selectionId(); + + push( m_mapWorld->m_cameraList, m_worldDef->m_lastCamera, cameraDef ); + + SetBits( cameraDef->m_editorFlags, editorFlags ); + + GetNewEditorId( cameraDef, m_mapWorld->m_lastUsedCameraEditorId ); + + cameraDef->m_mapCamera->setup( origin, angles ); + + cameraDef->m_mapCamera->m_cbColor1 = editorColor; + //cameraDef->m_mapCamera->calcFinalColor(); + + if ( FBitSet( cameraDef->m_editorFlags, EFL_SELECTED ) ) + { + m_worldDef->m_selCamera = cameraDef; + } + + return true; + } + + return false; +} + +/* +=============== +SerializePath +=============== +*/ +bool JMFSerializer::SerializePath( qPath_s *pathDef ) +{ + if ( m_writeMode ) + { + assert( pathDef ); + + if ( !WriteString( pathDef->m_pathClassname ) || + !WriteString( pathDef->m_pathName ) || + !WriteData( &pathDef->m_pathDirection, sizeof( int ) ) || + !WriteData( &pathDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &pathDef->m_mapPath->m_cbColor, sizeof( rgba_t ) ) ) + { + return false; + } + + int nodeCount = 0; + for ( qNode_s *nodeDef = pathDef->m_nodeList; nodeDef != NULL; nodeDef = nodeDef->next ) + { + ++nodeCount; + } + + if ( !WriteData( &nodeCount, sizeof( int ) ) ) + { + return false; + } + + for ( qNode_s *nodeDef = pathDef->m_nodeList; nodeDef != NULL; nodeDef = nodeDef->next ) + { + SerializeNode( nodeDef, pathDef ); + } + + return true; + } + else + { + char *classname = NULL; + ReadString( &classname ); + + if ( !classname || !classname[0] ) + { + return false; + } + + char *name = NULL; + ReadString( &name ); + + int direction = 0; + int editorFlags = 0; + rgba_t editorColor; + + if ( !ReadData( &direction, sizeof( int ) ) || !ReadData( &editorFlags, sizeof( int ) ) || !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + qPath_t *pathDef = CMapPath::create(); + pathDef->m_ownerWorld = m_mapWorld; + + pathDef->m_mapPath->m_cbColor = editorColor; + + push( m_mapWorld->m_pathList, m_mapWorld->m_lastPath, pathDef ); + + GetNewEditorId( pathDef, m_mapWorld->m_lastUsedPathEditorId ); + + pathDef->m_pathClassname = classname; + pathDef->m_pathName = name; + pathDef->m_pathDirection = direction; + pathDef->m_editorFlags = editorFlags; + + int nodeCount = 0; + if ( !ReadData( &nodeCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < nodeCount; i++ ) + { + SerializeNode( NULL, pathDef ); + } + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + int nodeIdx = 1; + for ( qNode_s *nodeDef = pathDef->m_nodeList; nodeDef != NULL; nodeDef = nodeDef->next ) + { + if ( nodeDef ) + { + nodeDef->m_nodeIndex = nodeIdx; + } + + ++nodeIdx; + } +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + + //pathDef->m_mapPath->build( 0 ); + pathDef->m_editorFlags &= ~0xFFFDFFFD; // TODO + + return true; + } + + return false; +} + +/* +=============== +SerializeNode +=============== +*/ +bool JMFSerializer::SerializeNode( qNode_s *nodeDef, qPath_s *pathDef ) +{ + if ( m_writeMode ) + { + assert( nodeDef ); + assert( pathDef ); + + if ( !WriteString( nodeDef->m_nameOverride ) || + !WriteString( nodeDef->m_fireEntityOnPass ) || + !WriteData( &nodeDef->m_vecOrigin, sizeof( vec3_t ) ) || + !WriteData( &nodeDef->m_vecAngles, sizeof( vec3_t ) ) || + !WriteData( &nodeDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &nodeDef->m_mapNode->m_cbColor, sizeof( rgba_t ) ) ) + { + return false; + } + + int keyValueCount = 0; + for ( epair_s *epair = nodeDef->epairs; epair != NULL; epair = epair->next ) + { + ++keyValueCount; + } + + if ( !WriteData( &keyValueCount, sizeof( int ) ) ) + { + return false; + } + + for ( epair_s *epair = nodeDef->epairs; epair != NULL; epair = epair->next ) + { + if ( !WriteString( epair->key ) || !WriteString( epair->value ) ) + { + return false; + } + } + + return true; + } + else + { + qNode_s *nodeDef = CMapNode::create(); + + //nodeDef->m_mapNode->m_selectionId = m_mapWorld->alloc_selectionId(); + + push( pathDef->m_nodeList, pathDef->m_lastNode, nodeDef ); + + nodeDef->m_ownerPath = pathDef; + + GetNewEditorId( nodeDef, m_mapWorld->m_lastUsedNodeEditorId ); + + ReadString( (char **)&nodeDef->m_nameOverride ); + ReadString( (char **)&nodeDef->m_fireEntityOnPass ); + + int editorFlags = 0; + rgba_t editorColor; + + if ( !ReadData( &nodeDef->m_vecOrigin, sizeof( vec3_t ) ) || + !ReadData( &nodeDef->m_vecAngles, sizeof( vec3_t ) ) || + !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + SetBits( nodeDef->m_editorFlags, editorFlags ); + nodeDef->m_mapNode->m_cbColor = editorColor; + + int keyValueCount = 0; + if ( !ReadData( &keyValueCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < keyValueCount; i++ ) + { + char *key = NULL; + char *value = NULL; + + ReadString( &key ); + ReadString( &value ); + + if ( key != NULL && value != NULL ) + { + AppendEpair( &nodeDef->epairs, key, value ); + } + } + + ClearBits( nodeDef->m_editorFlags, EFL_SELECTED ); + return true; + } + + return false; +} + +/* +=============== +SerializeEntity +=============== +*/ +bool JMFSerializer::SerializeEntity( qEntity_s *entityDef ) +{ + if ( m_writeMode ) + { + assert( entityDef ); + + int groupId = 0; + int parentGroupId = 0; + + if ( entityDef->m_groupList != NULL ) + { + groupId = entityDef->m_groupList->m_editorId; + } + + if ( entityDef->m_lastGroup != NULL ) + { + parentGroupId = entityDef->m_lastGroup->m_editorId; + } + + if ( !WriteString( entityDef->m_className ) || + !WriteData( &entityDef->m_vecOrigin, sizeof( vec3_t ) ) || + !WriteData( &entityDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &groupId, sizeof( int ) ) || + !WriteData( &parentGroupId, sizeof( int ) ) || + !WriteData( &entityDef->m_mapEntity->m_cbColor, sizeof( rgba_t ) ) ) + { + return false; + } + + // "spawnflags" + if ( !WriteString( entityDef->m_entityKeys[0].key ) ) + { + return false; + } + + // "origin" "angles" "scale" + if ( !WriteStringVersion( entityDef->m_entityKeys[1].key, 113 ) || + !WriteStringVersion( entityDef->m_entityKeys[2].key, 113 ) || + !WriteStringVersion( entityDef->m_entityKeys[3].key, 113 ) ) + { + return false; + } + + // "targetname" "target" + if ( !WriteStringVersion( entityDef->m_entityKeys[4].key, 116 ) || + !WriteStringVersion( entityDef->m_entityKeys[5].key, 116 ) ) + { + return false; + } + + // "skyname" + if ( !WriteStringVersion( entityDef->m_entityKeys[6].key, 117 ) ) + { + return false; + } + + // "model" "model" "texture" "model" "model" + if ( !WriteStringVersion( entityDef->m_entityKeys[7].key, 119 ) || + !WriteStringVersion( entityDef->m_entityKeys[8].key, 119 ) || + !WriteStringVersion( entityDef->m_entityKeys[9].key, 119 ) || + !WriteStringVersion( entityDef->m_entityKeys[10].key, 119 ) || + !WriteStringVersion( entityDef->m_entityKeys[11].key, 119 ) ) + { + return false; + } + + // "script" + if ( !WriteStringVersion( entityDef->m_entityKeys[12].key, 121 ) ) + { + return false; + } + + if ( !WriteData( &entityDef->m_spawnflags, sizeof( int ) ) || + !WriteData( &entityDef->m_vecAngles, sizeof( vec3_t ) ) || + !WriteData( &entityDef->m_renderMode, sizeof( qEntityRenderMode_s ) ) ) + { + return false; + } + + qEntityState_s &entityState = entityDef->m_entityState; + if ( m_jmfVersion <= 114 ) + { + if ( !WriteData( &entityState, 16 ) ) + { + return false; + } + } + else + { + if ( !WriteData( &entityState, sizeof( qEntityState_s ) ) ) + { + return false; + } + } + + int keyValueCount = 0; + for ( epair_s *epair = entityDef->epairs; epair != NULL; epair = epair->next ) + { + ++keyValueCount; + } + + if ( !WriteData( &keyValueCount, sizeof( int ) ) ) + { + return false; + } + + for ( epair_s *epair = entityDef->epairs; epair != NULL; epair = epair->next ) + { + if ( !WriteString( epair->key ) || !WriteString( epair->value ) ) + { + return false; + } + } + + int visGroupCount = (int)entityDef->m_mapEntity->m_visGroupList.size(); + if ( !WriteData( &visGroupCount, sizeof( int ) ) ) + { + return false; + } + + for ( auto visGroup : entityDef->m_mapEntity->m_visGroupList ) + { + if ( !WriteData( &visGroup->m_editorId, sizeof( int ) ) ) + { + return false; + } + } + + int brushCount = 0; + for ( qBrush_s *brushDef = entityDef->m_brushList; brushDef != NULL; brushDef = brushDef->next ) + { + ++brushCount; + } + + if ( !WriteData( &brushCount, sizeof( int ) ) ) + { + return false; + } + + for ( qBrush_s *brushDef = entityDef->m_brushList; brushDef != NULL; brushDef = brushDef->next ) + { + SerializeBrush( brushDef, entityDef ); + } + + return true; + } + else + { + char *classname = NULL; + ReadString( &classname, false ); // We don't want to show an error about file read fail when we reach EOF + + if ( !classname || !classname[0] ) + { + return false; + } + + vec3_t origin; + int editorFlags = 0; + + int groupId = 0; + int parentGroupId = 0; + + rgba_t editorColor; + + if ( !ReadData( &origin, sizeof( vec3_t ) ) || + !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &groupId, sizeof( int ) ) || + !ReadData( &parentGroupId, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + qEntity_s *entityDef = CMapEntity::create( classname, origin, editorFlags ); + entityDef->m_ownerWorld = m_mapWorld; + + entityDef->m_mapEntity->m_cbColor = editorColor; + + //m_mapWorld->linkObjectToWorld( entityDef ); + + push( m_mapWorld->m_entityList, m_mapWorld->m_lastEntity, entityDef ); + + GetNewEditorId( entityDef, m_mapWorld->m_lastUsedEntityEditorId ); + + ReadString( (char **)&entityDef->m_entityKeys[0].key ); // "spawnflags" + ReadStringVersion( (char **)&entityDef->m_entityKeys[1], 113 ); // "origin" + ReadStringVersion( (char **)&entityDef->m_entityKeys[2], 113 ); // "angles" + // askForVersion113BugWorkaround + ReadStringVersion( (char **)&entityDef->m_entityKeys[3], 113 ); // "scale" + ReadStringVersion( (char **)&entityDef->m_entityKeys[4], 116 ); // "targetname" + ReadStringVersion( (char **)&entityDef->m_entityKeys[5], 116 ); // "target" + ReadStringVersion( (char **)&entityDef->m_entityKeys[6], 117 ); // "skyname" + ReadStringVersion( (char **)&entityDef->m_entityKeys[7], 119 ); // "model" + ReadStringVersion( (char **)&entityDef->m_entityKeys[8], 119 ); // "model" + ReadStringVersion( (char **)&entityDef->m_entityKeys[9], 119 ); // "texture" + ReadStringVersion( (char **)&entityDef->m_entityKeys[10], 119 ); // "model" + ReadStringVersion( (char **)&entityDef->m_entityKeys[11], 119 ); // "model" + ReadStringVersion( (char **)&entityDef->m_entityKeys[12], 121 ); // "script" + + if ( !ReadData( &entityDef->m_spawnflags, sizeof( int ) ) || !ReadData( &entityDef->m_vecAngles, sizeof( vec3_t ) ) || !ReadData( &entityDef->m_renderMode, sizeof( qEntityRenderMode_s ) ) ) + { + return false; + } + + qEntityState_s &entityState = entityDef->m_entityState; + if ( m_jmfVersion <= 114 ) + { + if ( !ReadData( &entityState, 16 ) ) + { + return false; + } + } + else + { + if ( !ReadData( &entityState, sizeof( qEntityState_s ) ) ) + { + return false; + } + } + + int keyValueCount = 0; + if ( !ReadData( &keyValueCount, sizeof( int ) ) || keyValueCount > 32000 ) + { + return false; + } + + for ( int i = 0; i < keyValueCount; i++ ) + { + char *key = NULL; + char *value = NULL; + + ReadString( &key ); + ReadString( &value ); + + if ( key != NULL && value != NULL ) + { + AppendEpair( &entityDef->epairs, key, value ); + } + } + + int visGroupCount = 0; + if ( !ReadData( &visGroupCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < visGroupCount; i++ ) + { + int visGroupId = 0; + if ( !ReadData( &visGroupId, sizeof( int ) ) ) + { + return false; + } + + for ( auto visGroup : m_mapWorld->m_visGroupList ) + { + if ( visGroup->m_editorId != visGroupId ) + { + continue; + } + + entityDef->m_mapEntity->addToVisGroup( visGroup ); + } + } + + int brushCount = 0; + if ( !ReadData( &brushCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < brushCount; i++ ) + { + SerializeBrush( NULL, entityDef ); + } + + //entityDef->m_mapEntity->updateKeys( true ); + //entityDef->m_mapEntity->build( 906 ); + + if ( groupId > 0 ) + { + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + if ( groupId == groupDef->m_editorId ) + { + entityDef->m_groupList = groupDef; + break; + } + } + } + + if ( parentGroupId > 0 ) + { + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + if ( parentGroupId == groupDef->m_editorId ) + { + entityDef->m_lastGroup = groupDef; + break; + } + } + } + + if ( FBitSet( entityDef->m_editorFlags, EFL_SELECTED ) ) + { + ClearBits( entityDef->m_editorFlags, EFL_SELECTED ); + SetBits( entityDef->m_editorFlags, EFL_SELECTED ); + + pushSelected( m_mapWorld->m_selEntityList, entityDef ); + + //entityDef->m_mapEntity->updateSplineParms( 32 ); + } + + return true; + } + + return false; +} + +/* +=============== +SerializeBrush +=============== +*/ +bool JMFSerializer::SerializeBrush( qBrush_s *brushDef, qEntity_s *entityDef ) +{ + if ( m_writeMode ) + { + assert( brushDef ); + assert( entityDef ); + + int groupId = 0; + int parentGroupId = 0; + + if ( entityDef->m_groupList != NULL ) + { + groupId = entityDef->m_groupList->m_editorId; + } + + if ( entityDef->m_lastGroup != NULL ) + { + parentGroupId = entityDef->m_lastGroup->m_editorId; + } + + if ( !WriteData( &brushDef->m_extraDataType, sizeof( int ) ) || + !WriteData( &brushDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &groupId, sizeof( int ) ) || + !WriteData( &parentGroupId, sizeof( int ) ) || + !WriteData( &brushDef->m_mapBrush->m_cbColor, sizeof( rgba_t ) ) ) + { + return false; + } + + int visGroupCount = (int)brushDef->m_mapBrush->m_visGroupList.size(); + if ( !WriteData( &visGroupCount, sizeof( int ) ) ) + { + return false; + } + + for ( auto visGroup : brushDef->m_mapBrush->m_visGroupList ) + { + if ( !WriteData( &visGroup->m_editorId, sizeof( int ) ) ) + { + return false; + } + } + + int faceCount = 0; + for ( qFace_s *faceDef = brushDef->m_faceList; faceDef != NULL; faceDef = faceDef->next ) + { + ++faceCount; + } + + if ( !WriteData( &faceCount, sizeof( int ) ) ) + { + return false; + } + + for ( qFace_s *faceDef = brushDef->m_faceList; faceDef != NULL; faceDef = faceDef->next ) + { + SerializeFace( faceDef, brushDef ); + } + + switch ( brushDef->m_extraDataType ) + { + case BRUSH_EXTRADATA_PATCH: + { + assert( brushDef->m_patch ); + qPatch_s *patchDef = brushDef->m_patch; + + if ( !WriteData( &patchDef->m_numColumns, sizeof( int ) ) || + !WriteData( &patchDef->m_numRows, sizeof( int ) ) || + !WriteData( &patchDef->m_texDef, sizeof( qTexDef_s ) ) || + !WriteData( &patchDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &patchDef->m_data, sizeof( patchDef->m_data ) ) ) + { + return false; + } + + break; + } + case BRUSH_EXTRADATA_OVERLAY: + { + assert( brushDef->m_overlay ); + qOverlay_s *overlayDef = brushDef->m_overlay; + + if ( !WriteData( &overlayDef->m_texDef, sizeof( qTexDef_s ) ) || + !WriteData( &overlayDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &overlayDef->m_plane, sizeof( qPlane_s ) ) || + !WriteData( &overlayDef->m_data, sizeof( overlayDef->m_data ) ) ) + { + return false; + } + + break; + } + } + + return true; + } + else + { + int extraDataType = 0; + int editorFlags = 0; + int groupId = 0; + int parentGroupId = 0; + rgba_t editorColor; + + if ( m_jmfVersion > 111 && !ReadData( &extraDataType, sizeof( int ) ) || + !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &groupId, sizeof( int ) ) || + !ReadData( &parentGroupId, sizeof( int ) ) || + !ReadData( &editorColor, sizeof( rgba_t ) ) ) + { + return false; + } + + qBrush_s *brushDef = CMapBrush::create(); + + SetBits( brushDef->m_editorFlags, editorFlags ); + + brushDef->m_ownerEntity = entityDef; + + brushDef->m_extraDataType = extraDataType; + + brushDef->m_mapBrush->m_cbColor = editorColor; + //brushDef->m_mapBrush->calcFinalColor(); + + //brushDef->m_mapBrush->m_selectionId = m_mapWorld->alloc_selectionId(); + + push( entityDef->m_brushList, entityDef->m_lastBrush, brushDef ); + + GetNewEditorId( brushDef, m_mapWorld->m_lastUsedBrushEditorId ); + + int visGroupCount = 0; + if ( !ReadData( &visGroupCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < visGroupCount; i++ ) + { + int visGroupId = 0; + if ( !ReadData( &visGroupId, sizeof( int ) ) ) + { + return false; + } + + for ( auto visGroup : m_mapWorld->m_visGroupList ) + { + if ( visGroup->m_editorId != visGroupId ) + { + continue; + } + + brushDef->m_mapBrush->addToVisGroup( visGroup ); + } + } + + int faceCount = 0; + if ( !ReadData( &faceCount, sizeof( int ) ) ) + { + return false; + } + + for ( int i = 0; i < faceCount; i++ ) + { + SerializeFace( NULL, brushDef ); + } + + switch ( extraDataType ) + { + case BRUSH_EXTRADATA_PATCH: + { + int numColumns = 0; + int numRows = 0; + + if ( !ReadData( &numColumns, sizeof( int ) ) || !ReadData( &numRows, sizeof( int ) ) ) + { + return false; + } + + qPatch_t *patchDef = CMapPatch::create( numColumns, numRows ); + + qTexDef_s texDef; + if ( m_jmfVersion > 117 ) + { + if ( !ReadData( &texDef, sizeof( qTexDef_s ) ) ) + { + return false; + } + } + else + { + qTexDef_117_s oldTexDef; + if ( !ReadData( &oldTexDef, sizeof( qTexDef_117_s ) ) ) + { + return false; + } + + ConvertTexDef( oldTexDef, texDef ); + } + + if ( !ReadData( &patchDef->m_editorFlags, sizeof( int ) ) ) + { + return false; + } + + if ( m_jmfVersion > 119 ) + { + if ( !ReadData( &patchDef->m_data, sizeof( patchDef->m_data ) ) ) + { + return false; + } + } + else + { + Sys_Error( "NOT IMPLEMENTED" ); + return false; + } + + patchDef->m_ownerBrush = brushDef; + brushDef->m_patch = patchDef; + + patchDef->m_mapPatch->setTexDef( texDef ); + //patchDef->m_mapPatch->build( 2048 ); + break; + } + case BRUSH_EXTRADATA_OVERLAY: + { + qOverlay_t *overlayDef = CMapOverlay::create(); + + qTexDef_s texDef; + if ( m_jmfVersion > 117 ) + { + if ( !ReadData( &texDef, sizeof( qTexDef_s ) ) ) + { + return false; + } + } + else + { + qTexDef_117_s oldTexDef; + if ( !ReadData( &oldTexDef, sizeof( qTexDef_117_s ) ) ) + { + return false; + } + + ConvertTexDef( oldTexDef, texDef ); + } + + if ( !ReadData( &overlayDef->m_editorFlags, sizeof( int ) ) || + !ReadData( &overlayDef->m_plane, sizeof( qPlane_s ) ) || + !ReadData( &overlayDef->m_data, sizeof( overlayDef->m_data ) ) ) + { + return false; + } + + overlayDef->m_ownerBrush = brushDef; + brushDef->m_overlay = overlayDef; + + overlayDef->m_mapOverlay->setTexDef( texDef ); + //overlayDef->m_mapOverlay->build( 0 ); + break; + } + } + + //brushDef->m_mapBrush->build( 10 ); + + if ( groupId > 0 ) + { + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + if ( groupId == groupDef->m_editorId ) + { + brushDef->m_groupList = groupDef; + break; + } + } + } + + if ( parentGroupId > 0 ) + { + for ( qGroup_s *groupDef = m_mapWorld->m_groupList; groupDef != NULL; groupDef = groupDef->next ) + { + if ( parentGroupId == groupDef->m_editorId ) + { + brushDef->m_lastGroup = groupDef; + break; + } + } + } + + if ( FBitSet( brushDef->m_editorFlags, EFL_SELECTED ) ) + { + ClearBits( brushDef->m_editorFlags, EFL_SELECTED ); + SetBits( brushDef->m_editorFlags, EFL_SELECTED ); + + pushSelected( m_mapWorld->m_selBrushList, brushDef ); + } + + return true; + } + + return false; +} + +/* +=============== +SerializeFace +=============== +*/ +bool JMFSerializer::SerializeFace( qFace_s *faceDef, qBrush_s *brushDef ) +{ + if ( m_writeMode ) + { + assert( faceDef ); + assert( brushDef ); + + if ( !WriteData( &faceDef->m_editorFlags, sizeof( int ) ) || + !WriteData( &faceDef->m_vertexCount, sizeof( int ) ) || + !WriteData( &faceDef->m_texDef, sizeof( qTexDef_s ) ) || + !WriteData( &faceDef->m_plane, sizeof( qPlane_s ) ) || + !WriteData( &faceDef->m_vertices, faceDef->m_vertexCount * sizeof( qVertex_s ) ) ) + { + return false; + } + + return true; + } + else + { + int editorFlags = 0; + int vertexCount = 0; + if ( !ReadData( &editorFlags, sizeof( int ) ) || + !ReadData( &vertexCount, sizeof( int ) ) ) + { + return false; + } + + qFace_s *faceDef = CMapFace::create( vertexCount ); + + SetBits( faceDef->m_editorFlags, editorFlags ); + faceDef->m_ownerBrush = brushDef; + + qTexDef_s texDef; + if ( m_jmfVersion > 117 ) + { + if ( !ReadData( &texDef, sizeof( qTexDef_s ) ) ) + { + return false; + } + } + else + { + qTexDef_117_s oldTexDef; + if ( !ReadData( &oldTexDef, sizeof( qTexDef_117_s ) ) ) + { + return false; + } + + ConvertTexDef( oldTexDef, texDef ); + } + + if ( !ReadData( &faceDef->m_plane, sizeof( qPlane_s ) ) ) + { + return false; + } + + if ( !ReadData( faceDef->m_vertices, vertexCount * sizeof( qVertex_s ) ) ) + { + return false; + } + + //faceDef->m_mapFace->m_selectionId = m_mapWorld->alloc_selectionId(); + + push( brushDef->m_faceList, brushDef->m_lastFace, faceDef ); + + GetNewEditorId( faceDef, m_mapWorld->m_lastUsedFaceEditorId ); + + faceDef->m_mapFace->setTexDef( texDef ); + //faceDef->m_mapFace->build( 16 ); + + ClearBits( faceDef->m_editorFlags, EFL_SELECTED ); + + return true; + } + + return false; +} diff --git a/utils/jmftools/serializer_jmf.h b/utils/jmftools/serializer_jmf.h new file mode 100644 index 0000000..329ec0a --- /dev/null +++ b/utils/jmftools/serializer_jmf.h @@ -0,0 +1,62 @@ +//=====================================================================================// +// +// Purpose: JMFTools +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( SERIALIZER_RMF_H ) +#define SERIALIZER_RMF_H + +#define JHMFHEADER (('F'<<24)+('M'<<16)+('H'<<8)+'J') // little-endian "JHMF" + +class Serializer; + +class CBackgroundImage; +class CVisGroup; + +class JMFSerializer : public Serializer +{ +public: +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + JMFSerializer( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ); +#else + JMFSerializer( const char *filePath, struct qWorld_s *worldDef ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + virtual ~JMFSerializer(); + + virtual bool Export(); + virtual bool Import(); + + void setExportPath( const char *exportPath ) { strncpy( m_exportPath, exportPath, sizeof( m_exportPath ) ); m_exportPath[sizeof( m_exportPath ) - 1] = '\0'; } + const char *getExportPath() { return m_exportPath; } + +protected: + bool WriteString( const char *data ); + bool ReadString( char **outBuf, bool showerror = true ); + + bool WriteStringVersion( const char *data, int jmfVersion ); + bool ReadStringVersion( char **outBuf, int jmfVersion, bool showerror = true ); + +protected: + bool ProcessHeader(); + + bool SerializeBackground( CBackgroundImage *bgImage ); + bool SerializeGroup( qGroup_s *groupDef ); + bool SerializeVisGroup( CVisGroup *visGroupDef ); + bool SerializeCamera( qCamera_s *cameraDef ); + bool SerializePath( qPath_s *pathDef ); + bool SerializeNode( qNode_s *nodeDef, qPath_s *pathDef ); + bool SerializeEntity( qEntity_s *entityDef ); + bool SerializeBrush( qBrush_s *brushDef, qEntity_s *entityDef ); + bool SerializeFace( qFace_s *faceDef, qBrush_s *brushDef ); + +private: + CMapWorld *m_mapWorld; + int m_jmfVersion; + + char m_exportPath[MAX_PATH]; +}; + +#endif // !SERIALIZER_RMF_H diff --git a/utils/jmftools/serializer_map.cpp b/utils/jmftools/serializer_map.cpp new file mode 100644 index 0000000..ec8e536 --- /dev/null +++ b/utils/jmftools/serializer_map.cpp @@ -0,0 +1,1016 @@ +//=====================================================================================// +// +// Purpose: Half-Life Alpha 0.52 plugin for J.A.C.K. +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#include + +// Plugin API +#undef ENTITYAPI_NO_INLINEFUNCS +#undef PLUGINEDITORFUNCTIONS_H +#include "PluginMeta.h" + +#include "DataSerializer.h" +#include "serializer_map.h" + +#include "vstdlib_static.h" + +#define MAPVERSION_LEGACY 0 +#define MAPVERSION_VALVE220 220 +#define MAPVERSION MAPVERSION_VALVE220 + +//#define FINE_OUTPUT + +#if defined( FINE_OUTPUT ) +#define EXTRA_TAB "\t" +#else +#define EXTRA_TAB +#endif // FINE_OUTPUT + +#if defined( FINE_OUTPUT ) +static int s_entCnt = 0; +#endif // FINE_OUTPUT + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +/* +=============== +ExportMAP +=============== +*/ +bool ExportMAP( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ) +{ + MAPSerializer mapSerializer( filePath, seekOffset, readLimit, worldDef, FMODE_PARSERAPI ); + return mapSerializer.Export(); +} + +/* +=============== +ImportMAP +=============== +*/ +bool ImportMAP( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ) +{ + MAPSerializer mapSerializer( filePath, seekOffset, readLimit, worldDef, FMODE_PARSERAPI ); + return mapSerializer.Import(); +} +#else +/* +=============== +ExportMAP +=============== +*/ +bool ExportMAP( const char *filePath, struct qWorld_s *worldDef ) +{ + MAPSerializer mapSerializer( filePath, worldDef, FMODE_PARSERAPI ); + return mapSerializer.Export(); +} + +/* +=============== +ImportMAP +=============== +*/ +bool ImportMAP( const char *filePath, struct qWorld_s *worldDef ) +{ + MAPSerializer mapSerializer( filePath, worldDef, FMODE_PARSERAPI ); + return mapSerializer.Import(); +} +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + +/* +=============== +MAPSerializer +=============== +*/ +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +MAPSerializer::MAPSerializer( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef, int fileMode ) + : Serializer( filePath, seekOffset, readLimit, worldDef, fileMode ) +#else +MAPSerializer::MAPSerializer( const char *filePath, struct qWorld_s *worldDef, int fileMode ) + : Serializer( filePath, worldDef, fileMode ) +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA +{ + m_mapVersion = 0; + m_numInvalidSolid = 0; + m_packageList = NULL; + m_cordon = false; +} + +/* +=============== +~MAPSerializer +=============== +*/ +MAPSerializer ::~MAPSerializer() +{ +#if defined( FINE_OUTPUT ) + s_entCnt = 0; +#endif // FINE_OUTPUT +} + +/* +=============== +Export +=============== +*/ +bool MAPSerializer::Export() +{ + if ( !BuildPackageList( m_worldDef, &m_packageList, ';', 1 ) ) + { + return false; + } + + if ( !FBitSet( m_worldDef->m_editorFlags, ( 1 << 23 ) ) ) + { + Sys_Printf( "Saving: \"%s\"", m_filePath ); + } + + if ( !OpenForWrite() ) + { + return false; + } + + m_mapVersion = MAPVERSION; + + m_cordon = FBitSet( m_worldDef->m_editorFlags, EFL_CORDON ); + + if ( m_cordon && + ( fabs( m_worldDef->m_vecCordonMin.x ) < 0.001 && fabs( m_worldDef->m_vecCordonMin.y ) < 0.001 && fabs( m_worldDef->m_vecCordonMin.z ) < 0.001 || + fabs( m_worldDef->m_vecCordonMax.x ) < 0.001 && fabs( m_worldDef->m_vecCordonMax.y ) < 0.001 && fabs( m_worldDef->m_vecCordonMax.z ) < 0.001 ) ) + { + m_cordon = false; + } + + bool success = true; + + // + // Serialize entities + // + for ( qEntity_t *entityDef = m_worldDef->m_entityList; entityDef != NULL; entityDef = entityDef->next ) + { + if ( !SerializeEntities( entityDef ) ) + { + success = false; + break; + } + } + + // + // Serialize path nodes + // + if ( success ) + { + for ( qPath_t *pathDef = m_worldDef->m_pathList; pathDef != NULL; pathDef = pathDef->next ) + { + if ( !SerializePathNodes( pathDef ) ) + { + success = false; + break; + } + } + } + + // + // Cleanup packages + // + if ( m_packageList ) + { + Sys_Free( m_packageList ); + m_packageList = nullptr; + } + + return success; +} + +/* +=============== +Import +=============== +*/ +bool MAPSerializer::Import() +{ + if ( !FBitSet( m_worldDef->m_editorFlags, ( 1 << 23 ) ) ) + { + Sys_Printf( "Loading: \"%s\"", m_filePath ); + } + + if ( !OpenForRead() ) + { + return false; + } + + m_mapVersion = 0; + + while ( m_parser->pfnSC_GetToken( true ) ) + { + m_parser->pfnSC_UnGetToken(); + + SerializeEntities( NULL ); + } + + if ( m_numInvalidSolid != 0 ) + { + Sys_Printf( "%i solids were not loaded due to errors in MAP file", m_numInvalidSolid ); + } + + return true; +} + +/* +=============== +SerializeCordon +=============== +*/ +void MAPSerializer::SerializeCordon() +{ + if ( !m_cordon ) + { + return; + } + + // TODO +} + +/* +=============== +SerializeBrushFaces +=============== +*/ +bool MAPSerializer::SerializeBrushFaces( struct qFace_s *faceDef, struct qBrush_s *brushDef ) +{ + vec3_t p0, p1, p2; + vec4_t uAxis, vAxis; + + if ( m_writeMode ) + { + p0 = faceDef->m_vertices[1].coords; + p1 = faceDef->m_vertices[0].coords; + p2 = faceDef->m_vertices[2].coords; + + Sys_SnapVertex( p0.Base() ); + Sys_SnapVertex( p1.Base() ); + Sys_SnapVertex( p2.Base() ); + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, EXTRA_TAB EXTRA_TAB "( %s %s %s ) ( %s %s %s ) ( %s %s %s )", Sys_PrintMapCoordVector3D( p0 ), Sys_PrintMapCoordVector3D( p1 ), Sys_PrintMapCoordVector3D( p2 ) ); +#else + fprintf( m_fileHandle, EXTRA_TAB EXTRA_TAB "( %g %g %g ) ( %g %g %g ) ( %g %g %g )", Sys_PrintVector3D( p0 ), Sys_PrintVector3D( p1 ), Sys_PrintVector3D( p2 ) ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + + uAxis = faceDef->m_texDef.m_UAxis; + vAxis = faceDef->m_texDef.m_VAxis; + + Sys_SnapAxis( 4, uAxis.Base() ); + Sys_SnapAxis( 4, vAxis.Base() ); + +#if ( MAPVERSION == MAPVERSION_VALVE220 ) +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, " %s [ %s %s %s %s ] [ %s %s %s %s ] %s %s %s\n", + faceDef->m_texDef.m_textureName, + + // [ + Sys_PrintAxisVector4D( uAxis ), + // ] + + // [ + Sys_PrintAxisVector4D( vAxis ), + // ] + + Sys_PrintValue( faceDef->m_texDef.m_rotate ), + Sys_PrintValueVector2D( faceDef->m_texDef.m_scale ) + ); +#else + fprintf( m_fileHandle, " %s [ %g %g %g %g ] [ %g %g %g %g ] %g %g %g\n", + faceDef->m_texDef.m_textureName, + + // [ + Sys_PrintVector4D( uAxis ), + // ] + + // [ + Sys_PrintVector4D( vAxis ), + // ] + + faceDef->m_texDef.m_rotate, + Sys_PrintVector2D( faceDef->m_texDef.m_scale ) + ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA +#elif ( MAPVERSION == MAPVERSION_LEGACY ) +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, " %s %s %s %s %s %s\n", + faceDef->m_texDef.m_textureName, + + Sys_PrintAxis( faceDef->m_texDef.m_UAxis.w ), + Sys_PrintAxis( faceDef->m_texDef.m_VAxis.w ), + + Sys_PrintAxis( faceDef->m_texDef.m_rotate ), + Sys_PrintValueVector2D( faceDef->m_texDef.m_scale ) + ); +#else + fprintf( m_fileHandle, " %s %g %g %g %g %g\n", + faceDef->m_texDef.m_textureName, + + faceDef->m_texDef.m_UAxis.w, + faceDef->m_texDef.m_VAxis.w, + + faceDef->m_texDef.m_rotate, + Sys_PrintVector2D( faceDef->m_texDef.m_scale ) + ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA +#else + #error +#endif // ( MAPVERSION == MAPVERSION_VALVE220 ) + + return true; + } + else + { + m_parser->pfnSC_Parse1DMatrix( 3, p0.Base() ); + m_parser->pfnSC_Parse1DMatrix( 3, p1.Base() ); + m_parser->pfnSC_Parse1DMatrix( 3, p2.Base() ); + + qPlane_t plane; + PlaneFromPoints( p0, p1, p2, plane ); + + qTexDef_t texDef; + memset( &texDef, 0, sizeof( qTexDef_t ) ); + + m_parser->pfnSC_SetParseFlags( m_parser->pfnSC_GetParseFlags() | PFL_NOERRORS ); + + m_parser->pfnSC_GetToken( false ); + strncpy( texDef.m_textureName, m_parser->pfnSC_Token(), sizeof( texDef.m_textureName ) ); + texDef.m_textureName[sizeof( texDef.m_textureName ) - 1] = '\0'; + + m_parser->pfnSC_SetParseFlags( m_parser->pfnSC_GetParseFlags() & ~PFL_NOERRORS ); + + V_Strupr( texDef.m_textureName ); + + if ( m_mapVersion <= MAPVERSION_LEGACY ) + { + // shiftX shiftY rotation scaleX scaleY + + m_parser->pfnSC_GetToken( false ); + + const char *token = m_parser->pfnSC_Token(); + if ( token[0] == '[' && token[1] == '\0' ) + { + Sys_Warning( "not a version %i file (is it a decompiled BSP output?)", m_mapVersion ); + + m_mapVersion = MAPVERSION_VALVE220; + m_parser->pfnSC_UnGetToken(); + } + else + { + texDef.m_UAxis.w = V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_VAxis.w = V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_rotate = -V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_scale.x = V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_scale.y = V_Atof( m_parser->pfnSC_Token() ); + + texDef.m_textureAlignment = TEXALIGN_QUAKE; + } + } + + if ( m_mapVersion >= MAPVERSION_VALVE220 ) + { + // [ ux uy uz shift ] [ vx vy vz shift ] rotate scaleX scaleY + + // TODO + m_parser->pfnSC_GetToken( false ); + + // [ + m_parser->pfnSC_GetToken( false ); + texDef.m_UAxis.x = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_UAxis.y = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_UAxis.z = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_UAxis.w = V_Atof( m_parser->pfnSC_Token() ); + // ] + + m_parser->pfnSC_MatchToken( "]" ); + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + m_parser->pfnSC_MatchToken( "[" ); + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + // [ + m_parser->pfnSC_GetToken( false ); + texDef.m_VAxis.x = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_VAxis.y = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_VAxis.z = V_Atof( m_parser->pfnSC_Token() ); + m_parser->pfnSC_GetToken( false ); + texDef.m_VAxis.w = V_Atof( m_parser->pfnSC_Token() ); + // ] + + m_parser->pfnSC_MatchToken( "]" ); + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + m_parser->pfnSC_GetToken( false ); + texDef.m_rotate = V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_scale.x = V_Atof( m_parser->pfnSC_Token() ); + + m_parser->pfnSC_GetToken( false ); + texDef.m_scale.y = V_Atof( m_parser->pfnSC_Token() ); + + texDef.m_textureAlignment = TEXALIGN_FACE; + } + + while ( texDef.m_rotate < 0.0f ) + { + texDef.m_rotate += 360.0f; + } + + while ( texDef.m_rotate >= 360.0f ) + { + texDef.m_rotate -= 360.0f; + } + + if ( texDef.m_scale.x == 0.0f ) + { + texDef.m_scale.x = 1.0f; + } + + if ( texDef.m_scale.y == 0.0f ) + { + texDef.m_scale.y = 1.0f; + } + + // Skip all unsupported tokens + while ( m_parser->pfnSC_TokenAvailable() ) + { + m_parser->pfnSC_GetToken( false ); + } + + qFace_t *faceDef = Face_Create( m_worldDef, brushDef, &texDef, 0 ); + if ( faceDef ) + { + faceDef->m_plane = plane; + return true; + } + } + + return false; +} + +/* +=============== +SerializeBrushes +=============== +*/ +bool MAPSerializer::SerializeBrushes( struct qBrush_s *brushDef, struct qEntity_s *entityDef ) +{ + if ( m_writeMode ) + { + if ( !brushDef->m_faceList ) + { + return false; + } + + if ( FBitSet( m_worldDef->m_editorFlags, 0x100000 ) && FBitSet( brushDef->m_editorFlags, 8 ) ) + { + return true; + } + + if ( m_cordon && ( + ( brushDef->m_bboxMin.x > m_worldDef->m_vecCordonMax.x - 0.001 ) || ( brushDef->m_bboxMin.y > m_worldDef->m_vecCordonMax.y - 0.001 ) || ( brushDef->m_bboxMin.z > m_worldDef->m_vecCordonMax.z - 0.001 ) || + ( brushDef->m_bboxMax.x > m_worldDef->m_vecCordonMin.x + 0.001 ) || ( brushDef->m_bboxMax.y > m_worldDef->m_vecCordonMin.y + 0.001 ) || ( brushDef->m_bboxMax.z > m_worldDef->m_vecCordonMin.z + 0.001 ) ) ) + { + return true; + } + + fprintf( m_fileHandle, "%s\n", EXTRA_TAB "{" ); + + for ( qFace_t *faceDef = brushDef->m_faceList; faceDef != NULL; faceDef = faceDef->next ) + { + SerializeBrushFaces( faceDef, brushDef ); + } + + fprintf( m_fileHandle, "%s\n", EXTRA_TAB "}" ); + + return true; + } + else + { + m_parser->pfnSC_MatchToken( "{" ); + + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + qBrush_t *newBrushDef = Brush_Create( m_worldDef, entityDef ); + if ( !newBrushDef ) + { + return false; + } + + while ( m_parser->pfnSC_GetToken( true ) ) + { + const char *token = m_parser->pfnSC_Token(); + + // Exit if we've reached the final '}' + if ( token[0] == '}' && token[1] == '\0' ) + { + break; + } + + // SC_Parse1DMatrix assumes that we have '(' in the next token + m_parser->pfnSC_UnGetToken(); + + if ( !SerializeBrushFaces( NULL, newBrushDef ) ) + { + return false; + } + }; + + return true; + } + + return false; +} + +/* +=============== +SerializeEntities +=============== +*/ +bool MAPSerializer::SerializeEntities( struct qEntity_s *entityDef ) +{ + vec3_t origin; + vec3_t angles; + + if ( m_writeMode ) + { + // TODO: Flags + if ( !m_cordon ) + { +#if defined( FINE_OUTPUT ) + fprintf( m_fileHandle, "// entity %i\n", s_entCnt++ ); +#endif // FINE_OUTPUT + + fprintf( m_fileHandle, "%s\n", "{" ); + + fprintf( m_fileHandle, EXTRA_TAB "\"classname\" \"%s\"\n", entityDef->m_className ); + + for ( epair_t *epair = entityDef->epairs; epair != NULL; epair = epair->next ) + { + if ( !epair->key || !epair->value ) + continue; + + if ( !stricmp( epair->key, entityDef->m_entityKeys[1].key ) ) + continue; + + fprintf( m_fileHandle, EXTRA_TAB "\"%s\" \"%s\"\n", epair->key, epair->value ); + } + + // Brush-based entities must have the brush with ORIGIN texture attached to it + if ( !entityDef->m_brushList ) + { +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, EXTRA_TAB "\"%s\" \"%s %s %s\"\n", entityDef->m_entityKeys[1].key, Sys_PrintMapCoordVector3D( entityDef->m_vecOrigin ) ); +#else + fprintf( m_fileHandle, EXTRA_TAB "\"%s\" \"%g %g %g\"\n", entityDef->m_entityKeys[1].key, Sys_PrintVector3D( entityDef->m_vecOrigin ) ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + } + + // Main "worldspawn" entity stores information about the resources + if ( FBitSet( entityDef->m_editorFlags, EFL_WORLDSPAWN ) ) + { + fprintf( m_fileHandle, EXTRA_TAB "\"mapversion\" \"%i\"\n", m_mapVersion ); + fprintf( m_fileHandle, EXTRA_TAB "\"wad\" \"%s\"\n", m_packageList ); + fprintf( m_fileHandle, EXTRA_TAB "\"_generator\" \"%s (%s)\"\n", V_VersionString(), "vpHalfLifeAlpha" ); + SerializeCordon(); + } + + for ( qBrush_t *brushDef = entityDef->m_brushList; brushDef != NULL; brushDef = brushDef->next ) + { + SerializeBrushes( brushDef, entityDef ); + } + + fprintf( m_fileHandle, "%s\n", "}" ); + +#if defined( FINE_OUTPUT ) + fprintf( m_fileHandle, "\n" ); +#endif // FINE_OUTPUT + + return true; + } + + // TODO: Cordon + + return true; + } + else + { + int createFlags = ENT_BLDFLG_FULLBUILD; + char classname[512] = { 0 }; + + epair_t *epairList = NULL; + + qEntity_t *newEntityDef = NULL; + + // + // Find the first block + // + m_parser->pfnSC_MatchToken( "{" ); + if ( m_parser->pfnSC_CheckError() ) + { + return false; + } + + while ( m_parser->pfnSC_GetToken( true ) ) + { + const char *token = m_parser->pfnSC_Token(); + + // Exit if we've reached the final '}' + if ( token[0] == '}' && token[1] == '\0' ) + break; + + // Check if entity has embedded brushes + if ( token[0] == '{' && token[1] == '\0' ) + { + ClearBits( createFlags, ENT_BLDFLG_FULLBUILD ); + + // + // Entity must exists before brushes are created + // + if ( !newEntityDef ) + { + if ( !classname[0] ) + { + FreeEpairList( epairList ); + return false; + } + + if ( !stricmp( classname, "worldspawn" ) ) + { + SetBits( createFlags, EFL_WORLDSPAWN ); + } + + newEntityDef = Entity_Create( m_worldDef, classname, origin.Base(), createFlags ); + if ( !newEntityDef ) + { + FreeEpairList( epairList ); + return false; + } + + newEntityDef->epairs = epairList; + newEntityDef->m_vecAngles = angles; + } + + // SerializeBrushes assumes that we have '{' in the next token + m_parser->pfnSC_UnGetToken(); + + if ( !SerializeBrushes( NULL, newEntityDef ) ) + { + ++m_numInvalidSolid; + + m_parser->pfnSC_ResetError(); + + // Skip to the next entity + while ( m_parser->pfnSC_GetToken( true ) ) + { + if ( m_parser->pfnSC_Token()[0] == '}' ) + { + break; + } + } + } + + continue; + } + + if ( newEntityDef ) + { + m_parser->pfnSC_ParseError( "epairs are not allowed after brushes" ); + + m_parser->pfnSC_ResetError(); + m_parser->pfnSC_ResetError(); + + // Skip to the next entity + while ( m_parser->pfnSC_GetToken( true ) ) + { + if ( m_parser->pfnSC_Token()[0] == '}' ) + { + break; + } + } + + m_parser->pfnSC_UnGetToken(); + return false; + } + + // + // Parse key + // + char key[64] = { 0 }; + strncpy( key, m_parser->pfnSC_Token(), sizeof( key ) ); + + // + // Parse value + // + if ( !m_parser->pfnSC_GetToken( false ) ) + { + FreeEpairList( epairList ); + return false; + } + const char *value = m_parser->pfnSC_Token(); + + // + // Special keys + // + if ( !stricmp( key, "classname" ) ) + { + strncpy( classname, value, sizeof( classname ) ); + } + else if ( !stricmp( key, "origin" ) ) + { + (void)sscanf( value, "%f %f %f", &origin.x, &origin.y, &origin.z ); + } + else if ( !stricmp( key, "mapversion" ) ) + { + m_mapVersion = V_Atoi( value ); + } + else if ( !stricmp( key, "wad" ) || !stricmp( key, "_generator" ) ) + { + // Skip "wad" and "_generator" + } + else if ( !stricmp( key, "angles" ) ) + { + (void)sscanf( value, "%f %f %f", &angles.x, &angles.y, &angles.z ); + } + else if ( !stricmp( key, "angle" ) ) + { + float angle = V_Atof( value ); + + if ( angle == -1 ) // Up + { + angles[PITCH] = -90; + angles[YAW] = 0; + angles[ROLL] = 0; + } + else if ( angle == -2 ) // Down + { + angles[PITCH] = 90; + angles[YAW] = 0; + angles[ROLL] = 0; + } + else + { + angles[PITCH] = 0; + angles[YAW] = angle; + angles[ROLL] = 0; + } + } + else + { + AddEpairToList( epairList, key, value ); + } + } + + if ( !newEntityDef ) + { + if ( !classname[0] ) + { + FreeEpairList( epairList ); + return false; + } + + if ( !stricmp( classname, "worldspawn" ) ) + { + SetBits( createFlags, EFL_WORLDSPAWN ); + } + + newEntityDef = Entity_Create( m_worldDef, classname, origin.Base(), createFlags ); + if ( !newEntityDef ) + { + FreeEpairList( epairList ); + return false; + } + + newEntityDef->epairs = epairList; + newEntityDef->m_vecAngles = angles; + } + + Entity_Build( newEntityDef, m_mapVersion >= MAPVERSION_VALVE220 ? ENT_BLDFLG_FULLBUILD | ENT_BLDFLG_BRUSH_FACESNADOVERLAYS : ENT_BLDFLG_FULLBUILD | ENT_BLDFLG_BRUSH_FACESNADOVERLAYS | ENT_BLDFLG_BIT3 ); + + return true; + } + + return false; +} + +/* +=============== +SerializePathNodes +=============== +*/ +bool MAPSerializer::SerializePathNodes( struct qPath_s *pathDef ) +{ + if ( !m_writeMode ) + { + return true; + } + + if ( !pathDef->m_nodeList ) + return true; + + if ( !pathDef->m_pathClassname || !pathDef->m_pathClassname[0] ) + return true; + + if ( m_cordon ) + { + const vec3_t &bbmin = pathDef->m_bboxMin; + const vec3_t &bbmax = pathDef->m_bboxMax; + + if (( bbmin.x > m_worldDef->m_vecCordonMax.x - 0.001 ) || ( bbmin.y > m_worldDef->m_vecCordonMax.y - 0.001 ) || ( bbmin.z > m_worldDef->m_vecCordonMax.z - 0.001 ) || + ( bbmax.x < m_worldDef->m_vecCordonMin.x + 0.001 ) || ( bbmax.y < m_worldDef->m_vecCordonMin.y + 0.001 ) || ( bbmax.z < m_worldDef->m_vecCordonMin.z + 0.001 )) + { + return true; + } + + for ( qNode_s *nodeDef = pathDef->m_nodeList; nodeDef != NULL; nodeDef = nodeDef->next ) + { + const vec3_t &org = nodeDef->m_vecOrigin; + + if (( org.x >= m_worldDef->m_vecCordonMin.x + 0.001f && org.y >= m_worldDef->m_vecCordonMin.y + 0.001f && org.z >= m_worldDef->m_vecCordonMin.z + 0.001f ) && + ( org.x <= m_worldDef->m_vecCordonMax.x - 0.001f && org.y <= m_worldDef->m_vecCordonMax.y - 0.001f && org.z <= m_worldDef->m_vecCordonMax.z - 0.001f )) + { + return true; + } + } + } + + int nodeIdx = 0; + for ( qNode_t *nodeDef = pathDef->m_nodeList; nodeDef != NULL; nodeDef = nodeDef->next ) + { + fprintf( m_fileHandle, "%s\n", "{" ); + + fprintf( m_fileHandle, "\"classname\" \"%s\"\n", pathDef->m_pathClassname ); + + if ( nodeDef->m_nameOverride && nodeDef->m_nameOverride[0] ) + { + fprintf( m_fileHandle, "\"targetname\" \"%s\"\n", nodeDef->m_nameOverride ); + + if ( nodeDef == pathDef->m_nodeList ) + { + ++nodeIdx; + } + } + else + { + if ( nodeIdx != 0 ) + { + fprintf( m_fileHandle, "\"targetname\" \"%s_%i\"\n", pathDef->m_pathName, nodeIdx ); + } + else + { + fprintf( m_fileHandle, "\"targetname\" \"%s\"\n", pathDef->m_pathName ); + } + + ++nodeIdx; + } + + if ( nodeDef->next ) + { + if ( nodeDef->next->m_nameOverride && nodeDef->next->m_nameOverride[0] ) + { + fprintf( m_fileHandle, "\"target\" \"%s\"\n", nodeDef->next->m_nameOverride ); + } + else + { + fprintf( m_fileHandle, "\"target\" \"%s_%i\"\n", pathDef->m_pathName, nodeIdx ); + } + } + else if ( pathDef->m_pathDirection == 1 ) + { + const char *target = pathDef->m_nodeList->m_nameOverride; + + if ( !target || !target[0] ) + { + target = pathDef->m_pathName; + } + + fprintf( m_fileHandle, "\"target\" \"%s\"\n", target ); + } + else if ( pathDef->m_pathDirection == 2 && nodeDef->prev ) + { + fprintf( m_fileHandle, "\"target\" \"%s_%i\"\n", pathDef->m_pathName, nodeIdx ); + } + + for ( epair_s *epairs = nodeDef->epairs; epairs != NULL; epairs = epairs->next ) + { + if ( epairs->key && epairs->value ) + { + fprintf( m_fileHandle, "\"%s\" \"%s\"\n", epairs->key, epairs->value ); + } + } + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, "\"origin\" \"%s %s %s\"\n", Sys_PrintMapCoordVector3D( nodeDef->m_vecOrigin ) ); +#else + fprintf( m_fileHandle, "\"origin\" \"%g %g %g\"\n", Sys_PrintVector3D( nodeDef->m_vecOrigin ) ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + + const vec3_t &ang = nodeDef->m_vecAngles; + if ( ang.x != 0.f || ang.y != 0.f || ang.z != 0.f ) + { + fprintf( m_fileHandle, "\"angles\" \"%.0f %.0f %.0f\"\n", Sys_PrintVector3D( ang ) ); + } + + if ( nodeDef->m_fireEntityOnPass && nodeDef->m_fireEntityOnPass[0] ) + { + fprintf( m_fileHandle, "\"message\" \"%s\"\n", nodeDef->m_fireEntityOnPass ); + } + + fprintf( m_fileHandle, "%s\n", "}" ); + } + + if ( pathDef->m_pathDirection == 2 ) + { + for ( qNode_s *nodeDef = pathDef->m_lastNode->prev; nodeDef != NULL; nodeDef = nodeDef->prev ) + { + fprintf( m_fileHandle, "%s\n", "{" ); + + fprintf( m_fileHandle, "\"classname\" \"%s\"\n", pathDef->m_pathClassname ); + + fprintf( m_fileHandle, "\"targetname\" \"%s_%i\"\n", pathDef->m_pathName, nodeIdx++ ); + + if ( nodeDef->prev->prev != NULL ) + { + fprintf( m_fileHandle, "\"target\" \"%s_%i\"\n", pathDef->m_pathName, nodeIdx ); + } + else + { + const char *target = pathDef->m_nodeList->m_nameOverride; + + if ( !target || !target[0] ) + { + target = pathDef->m_pathName; + } + + fprintf( m_fileHandle, "\"target\" \"%s\"\n", target ); + } + + for ( epair_s *epairs = nodeDef->epairs; epairs != NULL; epairs = epairs->next ) + { + if ( epairs->key && epairs->value ) + { + fprintf( m_fileHandle, "\"%s\" \"%s\"\n", epairs->key, epairs->value ); + } + } + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + fprintf( m_fileHandle, "\"origin\" \"%s %s %s\"\n", Sys_PrintMapCoordVector3D( nodeDef->m_vecOrigin ) ); +#else + fprintf( m_fileHandle, "\"origin\" \"%g %g %g\"\n", Sys_PrintVector3D( nodeDef->m_vecOrigin ) ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + + const vec3_t &ang = nodeDef->m_vecAngles; + if ( ang.x != 0.f || ang.y != 0.f || ang.z != 0.f ) + { + fprintf( m_fileHandle, "\"angles\" \"%.0f %.0f %.0f\"\n", Sys_PrintVector3D( ang ) ); + } + + if ( nodeDef->m_fireEntityOnPass && nodeDef->m_fireEntityOnPass[0] ) + { + fprintf( m_fileHandle, "\"message\" \"%s\"\n", nodeDef->m_fireEntityOnPass ); + } + + fprintf( m_fileHandle, "%s\n", "}" ); + } + } + + return true; +} diff --git a/utils/jmftools/serializer_map.h b/utils/jmftools/serializer_map.h new file mode 100644 index 0000000..99ae35d --- /dev/null +++ b/utils/jmftools/serializer_map.h @@ -0,0 +1,50 @@ +//=====================================================================================// +// +// Purpose: Half-Life Alpha 0.52 plugin for J.A.C.K. +// +// Author(-s): SanyaSho (2026) +// +//=====================================================================================// + +#if !defined( SERIALIZER_MAP_H ) +#define SERIALIZER_MAP_H + +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA +extern bool ExportMAP( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ); +extern bool ImportMAP( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef ); +#else +extern bool ExportMAP( const char *filePath, struct qWorld_s *worldDef ); +extern bool ImportMAP( const char *filePath, struct qWorld_s *worldDef ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + +class Serializer; + +class MAPSerializer : public Serializer +{ +public: +#if JACK_API_VERSION >= API_VERSION_STEAM_BETA + MAPSerializer( const char *filePath, size_t seekOffset, size_t readLimit, struct qWorld_s *worldDef, int fileMode ); +#else + MAPSerializer( const char *filePath, struct qWorld_s *worldDef, int fileMode ); +#endif // JACK_API_VERSION >= API_VERSION_STEAM_BETA + virtual ~MAPSerializer(); + +public: // Serializer + virtual bool Export(); + virtual bool Import(); + +protected: + void SerializeCordon(); + bool SerializeBrushFaces( struct qFace_s *faceDef, struct qBrush_s *brushDef ); + bool SerializeBrushes( struct qBrush_s *brushDef, struct qEntity_s *entityDef ); + bool SerializeEntities( struct qEntity_s *entityDef ); + bool SerializePathNodes( struct qPath_s *pathDef ); + +protected: + int m_mapVersion; + int m_numInvalidSolid; + char *m_packageList; + bool m_cordon; +}; + +#endif // !SERIALIZER_MAP_H