PDA

View Full Version : VMF2Map



Mitch
20th November 2012, 12:57
I have been trying kung foo man's VMF to Map converter the last few days. I have been improving it a bit.

Current improvements:
I have added a form where you can select your VMF file to convert. And i have added a field where you can enter a name for your new file. After you click on 'convert' your file will be converted and downloaded.

Further i have improved the material usage.

Example:
- TOOLS/TOOLSSKYBOX becomes sky_toujane.
- TOOLS/TOOLSNODRAW becomes nodraw.
- TOOLS/TOOLSPLAYERCLIP becomes clip_player.
- TOOLS/TOOLSCLIP becomes clip.
- TOOLS/TOOLSTRIGGER becomes trigger.
- TOOLS/TOOLSSKIP becomes skip.
- TOOLS/TOOLSHINT becomes hint.
- TOOLS/TOOLSINVISIBLE becomes invisible. (doesn't exist)
- TOOLS/TOOLSAREAPORTAL becomes portal_nodraw.
- TOOLS/TOOLSBLOCKLIGHT becomes blocklight. (doesn't exist)
etc.

All unknown materials become caulk.

81

kung foo man
20th November 2012, 13:28
Wow, thats great :)

Did you compared the textures, so they kinda look the same? :D

The sourcecode with extension looks still really tidy. :)

The only thing i dont like is the web-interface. The .VMF-files can be 20MB in size and i dont want to use a browser to upload+download the new map. Thats to much overhead.

So i prefer PHP-CLI, just executing the vmf2map.php locally and it converts the .VMF in the same folder as the script. Like:



$map = convertVMF2Map("local.vmf");
file_put_contents("new.map", $map);


For people that want to see the original code: 80

Execute it using: http://windows.php.net/

Just put the PHP-Folder into C:\ and add the folder to your enviroment-variable called PATH (Rightclick on Computer -> Settings).

Then you can call the script with such a .bat-file:



php vmf2map.php
pause


If you dont want that PATH-variable, just write the full path of php:



C:\PHP\php.exe vmf2map.php
pause


Ok, now you may ask how to get a .vmf-File from a game like Counter Strike source, which just have .bsp-files? Use this java-decompiler: https://github.com/ata4/bspsrc/downloads

The only annoying problem is, that the CoD2-Radiant only can handle around 3750 Brushes (if i remember correctly). Big Source-Engine-Maps got around 8000 Brushes... so the smaller maps are more interessting, till we found a way for more brushes in CoD2.

kung foo man
4th December 2012, 07:53
I have extended and rewritten some Code (based on Mitch's Code, but removed the Web-Interface).

Added:
- Support for Triggers: they are now real Entities
- Missing Textures generate a Warning (only printed once for each Texture)

Want-List:
- Add Spawnpoints
- Auto-generated .gsc-File for Teleport/Push-Triggers

Random-Idea:
- If you want to add a matching Stock-Texture from CoD2 to the Convert-List, just open the decompiled .vmf-File in Hammer-Editor (start Source SDK in Steam). So you can also see, what a missing Default-Texture in Radiant was supposed to be.

Download here: 85

kung foo man
8th December 2012, 08:54
The vmf2map-converter got awesome!

Fullblown parsing now instead of explode'ing :D

Exporting triggers, spawnpointsm, extended texture-name-converter. Have fun! :)

87

kung foo man
25th December 2012, 18:10
Newest Converter:

kung foo man
26th February 2013, 16:10
Anybody ported some maps yet to CoD2?

johndoe
8th February 2014, 14:44
Anybody ported some maps yet to CoD2?well i'm trying to port a map to codwaw instead of cod2. i'm porting the textures as well. so i'm wondering if it's possible not to replace the textures with cod2 ones and leave them as they are. i'm pretty experienced with php but i don't want to read the whole file just to make a small change. any ideas?

kung foo man
8th February 2014, 16:27
I don't really understand what you want/not want. Which file you don't want to read and why? When you are experienced in PHP, why don't you just fit the texture-conversion-table to WaW textures?

johndoe
8th February 2014, 16:40
I don't really understand what you want/not want. Which file you don't want to read and why? When you are experienced in PHP, why don't you just fit the texture-conversion-table to WaW textures?
i guess you're right. i should stop being lazy and find my way through it then. i have another question by the way. does it convert the patches as well?

kung foo man
8th February 2014, 17:24
Yep, only brushes

johndoe
7th March 2014, 23:39
I changed some parts of the script to prevent texture replacement and exported the original textures from cs source to codwaw. This is how it looks.

http://i.imgur.com/IxhojAc.jpg?1
http://i.imgur.com/PS5ItZN.jpg?1
http://i.imgur.com/S1onbjO.jpg?1
http://i.imgur.com/GqCNGF6.jpg?1
http://i.imgur.com/d4ep76u.jpg?1
http://i.imgur.com/NVcINqn.jpg?1
http://i.imgur.com/dMyZjIO.jpg?1
http://i.imgur.com/thSxigY.jpg?1

I'm still trying to port the models but i accidentally deleted cs source. I'll try to get back to work on that as soon as possible.

johndoe
27th March 2014, 22:23
Ported the models and added my own touch to the script to place the models to the correct origins. Here's what i have now. I realized the size of a unit in source engine doesn't match cod's though, so i have to realign the models and the map will be pretty much done. Thank you guys for the vmf parser by the way, it saved me lots of time :)

johndoe
3rd April 2014, 15:05
I feel like people stopped paying attention to this topic but what the hell. I changed some parts of the script to prevent texture replacement so that i could find the textures i needed to port. Also found the difference between iw engine's and source engine's unit size and added an extra function to fix the it. Also added some extra lines to add the models. After converting a .vmf file to a .map file, this is what i got after fixing the texture alignments.

http://i.imgur.com/4lzvA6v.jpg?1?7053

All i had to do was convert the models to .obj files, then convert the models and textures for waw, then convert the .vmf file to a .map file. Here's the script that i've edited. Note that this one below doesn't change the texture names and stuff. You'll need to get the texture names and convert them for whatever cod version you have and it'll work just fine.


<?php
function convertunit($origin) {
$exp = explode(' ',$origin);
foreach($exp as $unit) {
$origin .= trim($unit)*1.3344.' ';
}
return trim($origin);
}
function parseVMF($vmfString)
{

$debug = false;

$len = strlen($vmfString);
if ($debug) $len = 2098;

$stack = array();

$isInQuote = false;
$key = "";
$value = "";
$quoteKey = "";
$quoteValue = "";
$quoteWhat = "key";

for ($i=0; $i<$len; $i++)
{
$c = $vmfString{$i}; // current char

switch ($c)
{
case "\r":
break;
case "\n":
if (strlen(trim($key))) {
//echo "new key: \"$key\"";
$stack[] = $key;
$key = "";
}
//echo "\n";
break;
case "\"":

if ($isInQuote) // so we are CLOSING key or value
{
if (strlen($quoteKey) && strlen($quoteValue))
{
//echo "(NEW PAIR \"$quoteKey\"->\"$quoteValue\")";
$stack[] = array($quoteKey=>$quoteValue);
$quoteKey = "";
$quoteValue = "";
}

if ($quoteWhat == "key")
$quoteWhat = "value";
else if ($quoteWhat == "value")
$quoteWhat = "key";
}

$isInQuote = !$isInQuote;
break;
case "{":
$stack[] = "ENTER-GROUP";
if ($debug) echo "{";
break;
case "}":
$stack[] = "LEAVE-GROUP";
if ($debug) echo "}";
break;

case "\t":
break;
//case " ":
// break;
default:
if (!$isInQuote && strlen(trim($c)))
{
$key .= $c;
}

if ($isInQuote)
if ($quoteWhat == "key")
$quoteKey .= $c;
else
$quoteValue .= $c;


//echo $c;
}
}

if ($debug) echo "<hr>";

$vmf = array();

//$addTo = undefined;

$depth = 0;
$parent = &$vmf;
$history = array();
$groupname = "not init";
foreach ($stack as $element)
{
if (!is_array($element)) // is groupname or GROUP-indicator
{
/*
if (!isset($vmf[$element]))
$vmf[$element] = array();
$vmf[$element][] = array();
//$addTo = &$
*/

if ($element == "ENTER-GROUP")
{
if ($debug) echo "ENTER GROUP depth=$depth\n";
continue;
}
if ($element == "LEAVE-GROUP")
{
if ($debug) echo "LEAVE GROUP\n";

$depth--;
$parent = &$history[$depth];
continue;
}

$groupname = $element;

if (!isset($parent[$groupname]))
{
$parent[$groupname] = array();
//$parent[$groupname][] = array();
}
$history[$depth] = &$parent;
$parent = &$parent[$groupname][];
$depth++;

//$i = count($parent[$groupname]);
//$parent[$groupname][$i] = $groupname . $i;

//$parent = &$parent[$groupname][$i];

if ($debug) echo "NEW GROUP $groupname\n";
} else {
$keys = array_keys($element);
$key = $keys[0];
$val = $element[$key];

$n = count($parent);
//echo $n . "\n";
//$parent[$n] = $groupname . $n;

$parent[$key] = $val;
//echo $key . "->" . $element[$key];
}

//echo "\n";
}


if ($debug) echo "<hr>";
if ($debug) var_dump($vmf);


return $vmf;
}

function fixWaterBrush(&$solid)
{
$isOnlyWaterAndNodraw = true;
$watername = "";
foreach ($solid["side"] as $side)
if (strpos(strtoupper($side["material"]), "WATER")!==false || strpos(strtoupper($side["material"]), "TOOLSNODRAW")!==false) {
if (strpos(strtoupper($side["material"]), "WATER")!==false)
$watername = $side["material"];
} else
return false;

if (trim($watername) == "") // its completely nodraw
return false;

foreach ($solid["side"] as &$side)
$side["material"] = $watername;

return true;
}

function exportVMFasGSC($vmf)
{
$gsc = "main()\n{\n";

foreach ($vmf["entity"] as $entity)
{
if (
$entity["classname"] != "item_ammopack_medium"
&& $entity["classname"] != "item_healthkit_medium"
&& $entity["classname"] != "item_ammopack_small"
&& $entity["classname"] != "item_healthkit_small"
)
continue;


$classname = $entity["classname"];
$origin = str_replace(" ", ",", convertunit($entity["origin"]));
$angles = str_replace(" ", ",", $entity["angles"]);
$gsc .= "\tstd\item::addItem(\"$classname\", ($origin), ($angles));\n";
}

$gsc .= "\n";

foreach ($vmf["entity"] as $entity)
{
if ( ! isset($entity["model"]))
continue;
if ($entity["classname"] != "prop_dynamic" && $entity["model"] != "models/props_gameplay/resupply_locker.mdl")
continue;

$name = $entity["targetname"];
$origin = str_replace(" ", ",", convertunit($entity["origin"]));
$angles = str_replace(" ", ",", $entity["angles"]);
$gsc .= "\tstd\item::addItem(\"$name\", ($origin), ($angles));\n";
}
$gsc .= "}";

return $gsc;
}

function exportVMFasMAP($vmf, $extend_map)
{
$map = "";
$map .= "iwmap 4\n";

// PRINT NORMAL BRUSHES
$map .= "{\n";
$map .= "\t\"classname\" \"worldspawn\"\n";

// some brushes are in world
foreach ($vmf["world"][0]["solid"] as $solid)
{
if ($solid["side"][0]["material"] == "TOOLS/TOOLSPLAYERCLIP") // ONLY FOR SURF_UTOPIA
continue;
if ($solid["side"][0]["material"] == "TOOLS/TOOLSSKYBOX") // ONLY FOR SURF_UTOPIA
continue;

if (fixWaterBrush($solid) && 0)
echo "Fixed Water on Brush Solid-ID=" . $solid["id"] . "\n";

$map .= "\t{\n";
foreach ($solid["side"] as $side)
{
//$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray -248 192 -96 120 0 0 \n";
$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0 \n";
}
$map .= "\t}\n";
}

// others are func_detail-entities
foreach ($vmf["entity"] as $entity)
{
if (
$entity["classname"] != "func_detail"
&& $entity["classname"] != "func_brush"
&& $entity["classname"] != "func_illusionary" /*could be detail also but who cares*/ /*later: lol this one would to to many nodes: no compile*/
)
continue;

// if illusionary: add clip_player PLUS normal textures as non-colliding.
// so ppl can shoot through but cant go through.

if ($entity["classname"] == "func_illusionary")
{
foreach ($entity["solid"] as $solid)
{
$map .= "\t// func_illusionary\n";
$map .= "\t{\n";
foreach ($solid["side"] as $side)
{
$map .= "\t\t" . $side["plane"] . " clip 64 64 -88 8 0 0 lightmap_gray 16384 16384 0 0 0 0\n";
}
$map .= "\t}\n";
}
}

foreach ($entity["solid"] as $solid)
{
if ($solid["side"][0]["material"] == "TOOLS/TOOLSPLAYERCLIP") // ONLY FOR SURF_UTOPIA
continue;
if ($solid["side"][0]["material"] == "TOOLS/TOOLSSKYBOX") // ONLY FOR SURF_UTOPIA
continue;


if (fixWaterBrush($solid) && 0)
echo "Fixed Water on Brush Entity-ID=" . $entity["id"] . "\n";




$map .= "\t{\n";
if ($entity["classname"] == "func_illusionary")
{
//break;
//contents detail;
//contents nonColliding;
//$map .= "\tcontents detail;\n";
$map .= "\tcontents nonColliding;\n";
}
foreach ($solid["side"] as $side)
{
//$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray -248 192 -96 120 0 0 \n";
$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0 \n";
}
$map .= "\t}\n";
}
}

$map .= $extend_map;

$map .= "}\n\n"; // all brushes are in worldspawn

// now the entities:

// spawnpoints
foreach ($vmf["entity"] as $entity)
{
if ($entity["classname"] != "info_player_teamspawn")
continue;
if ( ! isset($entity["TeamNum"]))
continue;

if ($entity["TeamNum"] == "2")
$classname = "mp_ctf_spawn_allied";
else
$classname = "mp_ctf_spawn_axis";
$origin = convertunit($entity["origin"]);
$angles = $entity["angles"];
$map .= "{\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"classname\" \"$classname\"\n";
$map .= "\t\"angles\" \"$angles\"\n";
$map .= "}\n";
}


foreach ($vmf["entity"] as $entity)
{
if ($entity["classname"] != "info_player_counterterrorist")
continue;
$origin = convertunit($entity["origin"]);
$angles = $entity["angles"];
$map .= "{\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"classname\" \"mp_ctf_spawn_allied\"\n";
$map .= "\t\"angles\" \"$angles\"\n";
$map .= "}\n";
}
foreach ($vmf["entity"] as $entity)
{
if ($entity["classname"] != "info_player_terrorist")
continue;
$origin = convertunit($entity["origin"]);
$angles = $entity["angles"];
$map .= "{\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"classname\" \"mp_ctf_spawn_axis\"\n";
$map .= "\t\"angles\" \"$angles\"\n";
$map .= "}\n";
}

//
foreach ($vmf["entity"] as $entity)
{
if (
$entity["classname"] != "trigger_teleport"
&& $entity["classname"] != "trigger_push"
&& $entity["classname"] != "func_regenerate"
&& $entity["classname"] != "func_respawnroom"
)
continue;

foreach ($entity["solid"] as $solid)
{
$map .= "{\n";
$map .= "\t\"classname\" \"trigger_multiple\"\n";
$map .= "\t\"targetname\" \"{$entity["classname"]}\"\n";

if (isset($entity["target"])) $map .= "\t\"target\" \"{$entity["target"]}\"\n";

$count = 0;
if (isset($entity["speed"])) { $map .= "\t\"speed\" \"{$entity["speed"]}\"\n"; $count++; }
if (isset($entity["pushdir"])) { $map .= "\t\"pushdir\" \"{$entity["pushdir"]}\"\n"; $count++; }
if ($count == 2) // hack... add later support for other key-values inside server
{
$map .= "\t\"target\" \"{$entity["speed"]}|{$entity["pushdir"]}\"\n";
}

if (isset($entity["TeamNum"])) { $map .= "\t\"target\" \"{$entity["TeamNum"]}\"\n"; $count++; }

$map .= "\t{\n";
foreach ($solid["side"] as $side)
{
//$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray -248 192 -96 120 0 0 \n";
$map .= "\t\t" . $side["plane"] . " " .getNewMaterial($side["material"]). " 64 64 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0 \n";
}
$map .= "\t}\n";
$map .= "}\n";
}
}

// global intermissions
foreach ($vmf["cameras"][0]["camera"] as $camera)
{
$origin = substr($camera["position"], 1, -1);
$map .= "{\n";
$map .= "\t\"classname\" \"mp_global_intermission\"\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"angles\" \"0 0 0\"\n";
$map .= "}\n";
}

// origins
foreach ($vmf["entity"] as $entity)
{
if ($entity["classname"] != "info_teleport_destination")
continue;
$origin = convertunit($entity["origin"]);
$targetname = $entity["targetname"];
$map .= "{\n";
$map .= "\t\"classname\" \"script_origin\"\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"angles\" \"" . $entity["angles"] . "\"\n";
$map .= "\t\"targetname\" \"$targetname\"\n";
$map .= "}\n";

// HACK HACK HACK -> otherwise those rooms are invisible
$map .= "{\n";
$map .= "\t\"classname\" \"mp_global_intermission\"\n";
$map .= "\t\"origin\" \"$origin\"\n";
$map .= "\t\"angles\" \"0 0 0\"\n";
$map .= "}\n";
}
foreach($vmf['entity'] as $entity) {
$model = $entity['model'];
$model = str_replace('-','_',$model);
$model = str_replace('.mdl,'_',$model);
if(preg_match('#.mdl#si',$model)) {
$origin = convertunit($entity['origin']);
$angles = $entity['angles'];
$model = explode('/',$model);
$model = $model[count($model)-1];
$map .= "{\n";
$map .= '"classname" "misc_model"';
$map .= '"model" "'.$model.'"';
$map .= '"origin" "'.$origin.'"';
$map .= '"angles" "'.$angles.'"';
$map .= "}\n";
}

}

return $map;
}

function getNewMaterial($materialname)
{
return $materialname;
}

ini_set("memory_limit", "500M");


$maps = array("cs_assault_d");

foreach ($maps as $name)
{
$missingTextures = array(); // global used to detect every missing texture only once


echo "Converting $name.vmf!\n";

$vmfString = file_get_contents("$name.vmf");
$vmf = parseVMF($vmfString);
$map = exportVMFasMAP($vmf, $extend_map);
$gsc = exportVMFasGSC($vmf);

//$map = convertVMF2Map($name . ".vmf");
if (!$map)
die("ERROR: Converting failed.\n");

if (count($missingTextures))
{
$textureNames = array_keys($missingTextures);
foreach ($textureNames as $textureName)
echo "WARNING: no texture for $textureName\n";
}

file_put_contents($name.".map", $map);
}

johndoe
5th May 2014, 16:39
I know most people on here will not be interested, but i'm trying to convert the terrain patches as well. I was planning on making an application that ports source engine maps to cod using the vmf parser and the only missing thing is the terrain patches which i couldn't manage to convert. I don't know much about both vmf and map file formats so i thought it'd be best to ask it here. If any of you guys can help me it'll be really great. Thanks in advance.

serthy
5th May 2014, 16:43
...terrain patches which i couldn't manage to convert...
iirc i used the vmf2map converter only with terrain patches :0
http://killtube.org/showthread.php?1043-OBJ2Map-mesh-wise

johndoe
5th May 2014, 17:31
iirc i used the vmf2map converter only with terrain patches :0
http://killtube.org/showthread.php?1043-OBJ2Map-mesh-wise

unfortunately obj2map doesn't do the trick for me. i can however convert the terrain patches from a source map to an obj file and convert it to a map file, but the map file created with obj2map isn't really memory-friendly and i never got it with the correct size of the map. even if it does convert the patches correctly, it still makes the whole porting process more complicated and harder. that's why i asked for help on this topic, but if i'm wrong about obj2map, please inform me. thanks for replying btw :)