PDA

View Full Version : Smaller mappacks, less download



IzNoGoD
19th September 2013, 20:14
This is a long post about how to create very small mappacks for your server. It relies on a modified version of the extension (...) and allows clients to only download the currently playing map instead of every map you have on the server.

It is currently running on the JumpersHeaven server (/connect jh.killtube.org) without issues and as such I decided to release everything.

Prerequisits:
- A properly functioning brain capable of more advanced computer stuff.
- Python 3.3
- 7zip
- Proper knowledge of codscript
- Knowledge of libcod and compiling it
- A few hours of your time
- fs_game for your mod
- Probably more

First step is to install python 3.x if you dont already have it installed.

Create a folder on C:/, I used JH2 (C:/JH2 is actually hardcoded in the python script, if you want to use some other folder you have to change the script)

Create a folder "stock" in said folder, copy all (including localized_english) iwds from your cod2 directory to there and extract them in such a way the C:/JH2/stock/maps folder exists (extract on the spot, not to a subdir)

Create a folder called "merged" in C:/JH2/ folder, extract all your mappacks to said folder

Edit all your soundaliases files so every single map has a mapname.csv soundalias (there is NO check for this, you have to do it by hand)
Edit all your soundaliases so they have a proper loadspec for every sound file (is technically not entirely needed, but the script is instructed to throw warnings if improper loadspecs are used which makes the output quite unreadable)

Create a folder "add_models" and a folder "add_shaders" in C:/JH2/

Look through the scripts of your maps to see if any additional xmodels or images are used.
- If any additional xmodels are used, store their names in a mapname.txt files (do this per-map) in the add_models folder, layout (taken from jm_pier_2.txt):


xmodel/prop_lamp01_on
xmodel/prop_lamp01
xmodel/cod4_sunglasses
xmodel/pier2_ancient_coin
xmodel/caspi_desert_gold
xmodel/de_v
- Do the same for images the scripts use, store in same manner in add_shaders. Example:


pier2_credits_map_name
objective
pier2_manifest_paper
cj_banner2
unreal_jumperz_crew_rzl128x128
lockpicking_cylinder_0
lockpicking_cylinder_1
lockpicking_cylinder_2
lockpicking_cylinder_3
lockpicking_cylinder_4
lockpicking_cylinder_5
lockpicking_cylinder_6
lockpicking_cylinder_7
lockpicking_cylinder_8
lockpicking_cylinder_9
lockpicking_pin
lockpicking_background
Note there is no folder here, just the material (!) name.

Create a .bat file with the following contents in C:/JH2 as zip_packs.bat:

for /D %%d in (*.*) do 7z a -tzip "%%d.iwd" ".\%%d\*"
This will later on pack the files into their .iwd packs.

Now, make sure the create_packs2.py file is also in C:/JH2/. You can open a commandprompt there if you like, or just double-click the .py file, it should execute in a commandwindow. Note that doubleclicking does not allow you to see any errors with 7zip, so i advice you to open a commandprompt.

The script now starts to do its magic. It does the following:
- Find all .d3dbsp files in merged/maps/mp folder
- Find all corresponding .csv files in merged/maps/mp to search loadingscreens, read them and add the found material files to a material list
- Reads the d3dbsp file and finds all materials and all xmodels used within that certain map, then adds them to the material and xmodel lists respectively
- Opens up all xmodel files (if not found in the stock files) and reads them to find all materials used by the xmodel. Note: this sometimes seems to fail, and the script will throw a "bad stuff happened for materialfind xmodelname mapname" error. Afaik this is not a real issue, as it seems the xmodel in question is not using any materials.
- Reads xmodel to find xmodelsurf/xmodelparts used by it, then adding it to lists
- Opens and reads the mapname.csv soundalias file (This is why you should name your soundalias file the same as the mapname), adds all sounds to a list
- Goes through all material files and finds the corresponding .iwi files

After these steps, it goes through all lists for that map and searches the stock folder for those files. If not found, it searches the merged folder for them. If still not found, it throws an error which you should try to fix.

If a .iwi file is listed as not found, you SHOULD fix it for it MIGHT crash the server, which it surely will if the file in question is part of a loadscreen.

Now that all maps have their own folder, the packing of the maps can begin. Pressing enter instructs 7zip to pack all separate maps into separate .iwd files (JumpersHeaven server has 200ish iwd files at the moment). These will be in the packs directory, automatically created by the script (and deleted once the script restarts, so be careful)

After the script completes, you will have a couple of folders added to the C:/JH2/ folder, namely:

- Packs
- soundaliases
- empty_files
- filelist

Packs:
Contains all .iwd files of the individual maps, as well as the extracted counterparts in separate folders

soundaliases
Contains all read soundaliases files. This should be added to your mod iwd file, which should be zzz_something.iwd

empty_files:
Contains all files present in the packs, but as 0byte files, in order to fool cod2 into thinking the files are actually present. Should be packed into a 000something.iwd file as a client iwd in your fs_game

filelist:
Contains a lot of .txt files with 1 line of text each. This is the list of .iwd files needed to run a certain map and opens up doors to shared image packs for multiple maps, but it is not a requirement to change anything to this. Should be put in fs_game/scriptdata/mapfiles/ on your server and, if your server runs stock maps, accompanied by a couple of empty .txt files named mp_toujane.txt, mp_carentan etc.

Now the packing of your maps is done it's time to start with the actual modding. You'll need a modified version of the libcod extension, with this present as function 602:


int gsc_system_command()
{
char* cmd;
if (stackGetNumberOfParams() < 2) // function, command
{
printf_hide("scriptengine> ERROR: please specify atleast 2 arguments to gsc_system_command()\n");
return stackPushUndefined();
}
if (!stackGetParamString(1, &cmd))
{
printf_hide("scriptengine> ERROR: closer(): param \"cmd\"[1] has to be a string!\n");
return stackPushUndefined();
}
system(cmd);
return stackPushInt(0);
}
This should allow cod2 to directly access linux shell commands in order to dynamically link/unlink the .iwd files into your fs_game folder

As this script totally breaks stock map functions, the /rcon map and /rcon devmap commands stop working and should be replaced by another function. As kung is currently looking into possible solutions for this I'm not gonna give you a function to replace these. If you require such a function, write it yourself.

The voting however HAS been rewritten by me using the callback playercommand supplied by libcod. Your codecallback_playercommand in _callbacksetup should look like this:




CodeCallback_PlayerCommand(args) //depends on the extension, put it in callbacksetup.gsc
{
args = fixChatArgs(args);
if(args[0] == "vote")
{
if(isdefined(args[1]) && args[1].size)
{
if(args[1] == "yes")
{
self vote(true);
return;
}
else if(args[1] == "no")
{
self vote(false);
return;
}
}
}
if(tolower(args[0]) == "callvote")
{
self callvote(args);
return;
}
std\utils::ClientCommand(self getEntityNumber());
}


in which you can put the vote commands in a separate .gsc file. These functions are:


init() //call on mapstart
{
level.izno_votetime = 60 * 1000;
level.izno_vote_cooldowntime = 60 * 1000;
level.nextmap = ??; //i dunno, i use my own mysql nextmap function here. Make sure to fill this out anyway, without it everything crashes and burns.
}
This should be read carefully as the level.nextmap is NOT set yet but SHOULD be set. I'm too lazy to find which cvars to read for this and am currently using a mysql function for nextmap.
Furthermore, these functions should be included:


onvote(args)
{
if(isdefined(args[1]) && args[1].size)
{
switch(tolower(args[1]))
{
case "map":
case "typemap":
{
if((args[1] == "map" && isdefined(args[2])) || (args[1] == "typemap" && isdefined(args[3])))
{
if(args[1] == "map")
num = 2;
else
num = 3;
mapname = args[num];
fid = openfile("mapfiles/" + mapname + ".txt", "read");
if(fid != -1)
{
closefile(fid);
self startmapvote(mapname);
}
else
self iprintln("No matching mapnames found");
return;
}
}
case "map_restart":
{
self startmap_restartvote();
return;
}
case "g_gametype":
{
self iprintlnbold("You are not allowed to change the gametype");
return;
}
case "map_rotate":
{
if(isdefined(level.nextmap))
self startmap_rotatevote();
return;
}
case "tempbanuser":
case "kick":
{
if(isdefined(args[2]) && args[2].size)
self startkickvote(args[2]);
return;
}
case "tempbanclient":
case "clientkick":
{
if(isdefined(args[2]) && args[2].size)
self startclientkickvote(args[2]);
return;
}
}
}
}

cancallvote()
{
if(!getcvarint("g_allowvote"))
{
self iprintln("Voting is disabled");
return false;
}
if(isdefined(level.izno_vote) && level.izno_vote["endtime"] > gettime())
{
self iprintln("A vote is already in progress!");
return false;
}
else if(isdefined(level.izno_vote) && level.izno_vote["cooldown"] > gettime())
{
self iprintln("A vote was called recently");
self iprintln("Please wait before calling another one");
return false;
}
return true;
}

startmap_restartvote()
{
if(!cancallvote())
return;
level.izno_vote = [];
level.izno_vote["veto"] = false;
level.izno_vote["type"] = "map_restart";
level.izno_vote["endtime"] = gettime() + level.izno_votetime;
level.izno_vote["in_favor"] = 1;
level.izno_vote["opposed"] = 0;
iprintlnbold("A vote has started");
iprintln(self.name + "^7 has called a vote to restart the map");
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
players[i].izno_vote = undefined;
self.izno_vote = true;
level thread monitorvote();
}
startmap_rotatevote()
{
if(!cancallvote())
return;
level.izno_vote = [];
level.izno_vote["veto"] = false;
level.izno_vote["type"] = "map_rotate";
level.izno_vote["map"] = level.nextmap;
level.izno_vote["endtime"] = gettime() + level.izno_votetime;
level.izno_vote["in_favor"] = 1;
level.izno_vote["opposed"] = 0;
iprintlnbold("A vote has started");
iprintln(self.name + "^7 has called a vote to start the next map");
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
players[i].izno_vote = undefined;
self.izno_vote = true;
level thread monitorvote();
}

startclientkickvote(num)
{
if(num != 0 && int(num) + "" != num)
{
self iprintln("Invalid client number");
return;
}
user = undefined;
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
{
if(players[i] getentitynumber() == int(num))
{
user = players[i];
break;
}
}
if(isdefined(user) && self == user)
self iprintln("You cannot vote to kick yourself");
else if(isdefined(user))
self startkickplayervote(user);
else
self iprintln("No users found");
}

startkickvote(name)
{
if(!cancallvote())
return;
found = 0;
user = undefined;
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
{
if(players[i].name == name)
{
found++;
user = players[i];
}
}
if(found == 1)
{
if(self == user)
{
self iprintln("You cannot vote to kick yourself");
return;
}
self startkickplayervote(user);
}
else if(!found)
self iprintln("No users found by the name " + name);
else
self iprintln("Multiple users found by the name " + name);
}

startkickplayervote(user)
{
level.izno_vote = [];
level.izno_vote["veto"] = false;
level.izno_vote["type"] = "kick";
level.izno_vote["player"] = user;
level.izno_vote["endtime"] = gettime() + level.izno_votetime;
level.izno_vote["in_favor"] = 1;
level.izno_vote["opposed"] = 1;
iprintlnbold("A vote has started");
iprintln(self.name + "^7 has called a vote to kick " + user.name);
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
players[i].izno_vote = undefined;
self.izno_vote = true;
user.izno_vote = false;
level thread monitorvote();
}

startmapvote(map)
{
if(!cancallvote())
return;
level.izno_vote = [];
level.izno_vote["veto"] = false;
level.izno_vote["type"] = "map";
level.izno_vote["map"] = map;
level.izno_vote["endtime"] = gettime() + level.izno_votetime;
level.izno_vote["in_favor"] = 1;
level.izno_vote["opposed"] = 0;
iprintlnbold("A vote has started");
iprintln(self.name + "^7 has called a vote to change the map to " + map);
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
players[i].izno_vote = undefined;
self.izno_vote = true;
level thread monitorvote();
}

vote(side)
{
if(isdefined(self.izno_vote))
{
self iprintln("You have already voted");
return;
}
else if(isdefined(level.izno_vote) && level.izno_vote["endtime"] > gettime())
{
if(side)
{
self.izno_vote = true;
level.izno_vote["in_favor"]++;
}
else
{
self.izno_vote = false;
level.izno_vote["opposed"]++;
}
}
else
self iprintln("No vote in progress");
}

checkvotenotended()
{
if(level.izno_vote["veto"])
return false;
players = getentarray("player", "classname");
in_favor = 0;
opposed = 0;
total = 0;
for(i = 0; i < players.size; i++)
{
if(isdefined(players[i].izno) && players[i].izno["afk_timer_vote"] > gettime())
{
if(isdefined(players[i].izno_vote))
{
if(players[i].izno_vote)
in_favor++;
else
opposed++;
}
total++;
}
}
level.izno_vote["in_favor"] = in_favor;
level.izno_vote["opposed"] = opposed;
level.izno_vote["total"] = total;
if(gettime() < level.izno_vote["endtime"] && level.izno_vote["in_favor"] <= level.izno_vote["total"] / 2 && level.izno_vote["opposed"] < level.izno_vote["total"] / 2)
return true;
return false;
}

monitorvote()
{
players = getentarray("player", "classname");
remaining = (level.izno_vote["endtime"] - gettime()) / 1000;
level.izno_vote["cooldown"] = gettime() + level.izno_vote_cooldowntime;
oldtext_voted = "";
oldtext_notvoted = "";
text_voted = "";
text_notvoted = "";
while(checkvotenotended())
{
switch(level.izno_vote["type"])
{
case "map":
{
text_voted = "^3Vote(" + int(remaining) + "): Map: " + level.izno_vote["map"] + "^3\nYES: " + level.izno_vote["in_favor"] + " NO: " + level.izno_vote["opposed"];
text_notvoted = "^3Vote(" + int(remaining) + "): Map: " + level.izno_vote["map"] + "^3\nYES(F1): " + level.izno_vote["in_favor"] + " NO(F2): " + level.izno_vote["opposed"];
break;
}
case "map_restart":
{
text_voted = "^3Vote(" + int(remaining) + "): Restart map \nYES: " + level.izno_vote["in_favor"] + " NO: " + level.izno_vote["opposed"];
text_notvoted = "^3Vote(" + int(remaining) + "): Restart map \nYES(F1): " + level.izno_vote["in_favor"] + " NO(F2): " + level.izno_vote["opposed"];
break;
}
case "map_rotate":
{
text_voted = "^3Vote(" + int(remaining) + "): Next map: " + level.izno_vote["map"] + "^3\nYES: " + level.izno_vote["in_favor"] + " NO: " + level.izno_vote["opposed"];
text_notvoted = "^3Vote(" + int(remaining) + "): Next map: " + level.izno_vote["map"] + "^3\nYES(F1): " + level.izno_vote["in_favor"] + " NO(F2): " + level.izno_vote["opposed"];
break;
}
case "kick":
{
if(isdefined(level.izno_vote["player"]))
{
text_voted = "^3Vote(" + int(remaining) + "): Kick: " + level.izno_vote["player"].name + "^3\nYES: " + level.izno_vote["in_favor"] + " NO: " + level.izno_vote["opposed"];
text_notvoted = "^3Vote(" + int(remaining) + "): Kick: " + level.izno_vote["player"].name + "^3\nYES(F1): " + level.izno_vote["in_favor"] + " NO(F2): " + level.izno_vote["opposed"];
break;
}
else
break;
}
}
if(oldtext_voted != text_voted || oldtext_notvoted != text_notvoted)
{
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
{
if(isdefined(players[i].izno_vote))
players[i] setclientcvar("vote_alternative", text_voted);
else
players[i] setclientcvar("vote_alternative", text_notvoted);
}
}
remaining -= 0.05;
oldtext_voted = text_voted;
oldtext_notvoted = text_notvoted;
wait 0.05;
}
checkvotenotended();
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
players[i] setclientcvar("vote_alternative", "");
if(level.izno_vote["type"] == "kick" && !isdefined(level.izno_vote["player"]))
{
iprintln("The player already left");
level.izno_vote = undefined;
}
if(level.izno_vote["veto"])
iprintlnbold("This vote was veto'd by an admin");
else if(level.izno_vote["in_favor"] > level.izno_vote["total"] / 2 || (level.izno_vote["in_favor"] > level.izno_vote["total"] / 4 && level.izno_vote["in_favor"] > level.izno_vote["opposed"] && level.izno_vote["total"] > 4))
{
switch(level.izno_vote["type"])
{
case "map":
{
iprintlnbold("The vote for map " + level.izno_vote["map"] + " passed");
level.nextmap = level.izno_vote["map"];
wait 3;
[[level.endgameconfirmed]](5);
break;
}
case "map_rotate":
{
iprintlnbold("The vote to start the next map (" + level.izno_vote["map"] + ")^7 passed");
level.nextmap = level.izno_vote["map"];
wait 3;
[[level.endgameconfirmed]](5);
break;
}
case "map_restart":
{
iprintlnbold("The vote for a map restart passed");
level.nextmap = getcvar("mapname");
wait 3;
[[level.endgameconfirmed]](0);
break;
}
case "kick":
{
iprintlnbold("The vote to kick " + level.izno_vote["player"].name + " ^7passed");
wait 3;
kick(level.izno_vote["player"] getentitynumber());
break;
}
}
}
else if(level.izno_vote["in_favor"] <= level.izno_vote["opposed"])
iprintln("Vote failed. Yes votes must exceed no votes");
else
iprintln("Vote failed. Not enough players voted");
level.izno_vote["endtime"] = gettime();
}

Furthermore, it needs a cvar in hud.menu:


menuDef
{
name "vote_alternative"
fullScreen MENU_FALSE
visible MENU_TRUE
rect 0 0 0 0 HORIZONTAL_ALIGN_CENTER VERTICAL_ALIGN_CENTER
itemDef
{
name "text"
visible MENU_TRUE
rect 0 0 0 0
origin -310 -40
forecolor 1.0 1.0 1.0 1.0
dvar "vote_alternative"
textfont UI_FONT_NORMAL
textalign ITEM_ALIGN_LEFT
textscale 0.25
textaligny 0
decoration
}
}

and a big modification to the endmap function in every gametype.gsc you have:


endMap(delay)
{
if(!isdefined(delay))
delay = 10;
game["state"] = "intermission";
level notify("intermission");
if(delay != 0)
{
players = getentarray("player", "classname");
for(i = 0; i < players.size; i++)
{
players[i] closeMenu();
players[i] closeInGameMenu();
players[i] setClientCvar("cg_objectiveText", "Thanks for playing " + getcvar("mapname"));
players[i] spawnIntermission();
}
wait delay;
}
if(isdefined(level.nextmap))
{
do_nextmap = true;
to_link = [];
to_unlink = [];
fid = openfile("mapfiles/" + level.nextmap + ".txt", "read");
if(fid != -1)
{
argcount = freadln(fid);
while(argcount != -1)
{
str = "";
for(i = 0; i < argcount; i++)
{
if(i)
str += ",";
str += fgetarg(fid, i);
}
if(str.size)
to_link[to_link.size] = str;
argcount = freadln(fid);
}
closefile(fid);
fid = openfile("mapfiles/" + getcvar("mapname") + ".txt", "read");
if(fid != -1)
{
argcount = freadln(fid);
while(argcount != -1)
{
str = "";
for(i = 0; i < argcount; i++)
{
if(i)
str += ",";
str += fgetarg(fid, i);
}
if(str.size)
to_unlink[to_unlink.size] = str;
argcount = freadln(fid);
}
closefile(fid);
}
else
do_nextmap = false;
}
else
do_nextmap = false;
if(level.nextmap == getcvar("mapname") || !do_nextmap)
map_restart(false);
else
{
for(i = 0; i < to_unlink.size; i++)
closer(602, "unlink ~/" + getcvar("fs_game") + "/" + to_unlink[i] + ".iwd");
for(i = 0; i < to_link.size; i++)
closer(602, "link ~/" + getcvar("fs_game") + "/Library/" + to_link[i] + ".iwd ~/" + getcvar("fs_game") + "/" + to_link[i] + ".iwd");
map(level.nextmap);
}
}
else
exitLevel(false);
wait 10;
map_restart(false); //prevent server from accidentally hanging
}

If you add all above code at the proper locations, you now only need to move all your packed maps into fs_game/Library/ folder. The redirect however should have all files put into fs_game.

All custom weapons and their material and image files should also be put in your mod iwd file.


Advantages:
- Smaller mappacks
- Less stress on your download server
- Clients can join faster
- Voting is patched to include spectators into the vote
- New maps can be added. I've found that updating the empty_files iwd file isnt enough: It should be renamed. I suggest you start with 000empty.iwd, use 001empty.iwd and so on for updates, forcing your clients to download the new file

Drawbacks/known bugs:
- map and devmap command dont work
- vote is NOT visible for spectators that are spectating "free", but is visible for spectators that are spectating a player.
- Script does NOT automatically clean up your fs_game after a servercrash. You should do this manually.
- Your server should start a stock map at first, as no files are present in your fs_game at the start
- It is a lot of work to do all this


Disclaimer stuff:
If any damage in whatever manner is caused by anything in this tutorial (like, ssd dying due to too much filecopy operations) I don't feel responsible, you dont have my address and cannot claim anything from me. No warranty on anything.

License:
Above code is provided as-is and is free to use for non-commercial use. Any support on creating the mappacks should be given free-of-charge except if done by me or after me telling the supporter it's ok to charge money for this. Help with the other scripts can be given at any fee you like.
The scripts cannot be sold, nor can a modification of these scripts be sold. Any changes to these scripts should be given free-of-charge to anyone who asks for them

IzNoGoD
19th September 2013, 20:14
This is the python script that packs all your maps, should be named create_packs2.py to match the naming in this tutorial:


import os
import shutil
import subprocess
import struct
import csv

def get_more_xmodels(filename):
found = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
return found

def get_more_materials(filename):
found = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
return found


def get_xmodels(filename):
models = []
f = open(filename, "rb")
for line in f:
if line.startswith(bytes(r'"model"', "utf-8")):
#print(line)
strs = ""
start = False
for chars in line:
if chr(chars) == " ":
start = True
elif chr(chars) == "\n":
break
elif start and chr(chars) == "*":
break;
elif start and chr(chars) != "\"":
strs += chr(chars)
if len(strs):
if not strs in models:
models.append(strs)
return models

def getxmodelparts(filename, mapname):
parts = []
f = open(filename, "rb")
res = f.read(1)
pos = 1
startpos = -1
count_255 = 0
while len(res) == 1:
if res[0] == 0:
count_255 += 1
if count_255 == 4:
if startpos == -1:
startpos = pos
else:
count_255 = 0
res = f.read(1)
pos += 1
f.close()
f = open(filename, "rb")
if startpos >= 1:
f.read(startpos)
else:
f.close()
print("bad stuff happened for part", filename, mapname)
return parts
char = f.read(1)
pos = 1
strs = ""
while pos <= 32:
if char == b"\x00":
break;
strs += char.decode("utf-8")
char = f.read(1)
pos += 1
f.close()
parts.append(strs)
return parts

def getmaterialsxmodel(filename, mapname):
mats = []
f = open(filename, "rb")
res = f.read(1)
pos = 1
startpos = -1
count_255 = 0
while len(res) == 1:
if res[0] == 255:
count_255 += 1
if count_255 == 4:
if startpos == -1:
startpos = pos
else:
count_255 = 0
res = f.read(1)
pos += 1
f.close()
f = open(filename, "rb")
if startpos >= 0:
f.read(startpos + 4)
else:
f.close()
print("bad stuff happened for materialfind ", filename, mapname)
return mats
count = 0
count += int(f.read(1)[0])
count += int(f.read(1)[0]) * 256
char = f.read(1)
strs = ""
while len(char) == 1 and len(mats) < count:
if char == b"\x00":
mats.append(strs)
strs = ""
else:
strs += char.decode("utf-8")
char = f.read(1)
f.close()
return mats


def get_loadscreen(filename):
mats = []
if not os.path.exists(filename):
return mats
with open(filename) as csvfile:
reader = csv.reader(csvfile, delimiter = ",")
for row in reader:
if len(row) < 2:
continue
if len(row) == 2 and row[0].lower() == "levelbriefing":
mats.append(row[1])
else:
print("bad stuff:", filename, row[0], row[1])
return mats


def get_sounds(mapname):
files = []
if not os.path.exists(os.path.join(source, "soundaliases", mapname + ".csv")):
return files
with open(os.path.join(source, "soundaliases", mapname + ".csv")) as csvfile:
file_index = -1
loadspec_index = -1
name_index = -1
reader = csv.reader(csvfile, delimiter = ",")
linenum = 0
for row in reader:
linenum += 1
if len(row) < 3:
continue
if row[0].startswith(r",") or row[0].startswith(r"#") or row[0].startswith(r"\"") or len(row[0]) == 0:
continue
if file_index == -1 and "file" in row:
file_index = row.index("file")
if loadspec_index == -1 and "loadspec" in row:
loadspec_index = row.index("loadspec")
if name_index == -1 and "name" in row:
name_index = row.index("name")
continue
if file_index == -1 or loadspec_index == -1:
continue
if name_index != -1 and len(row) > name_index and row[name_index] == "null":
continue
if len(row) <= loadspec_index or loadspec_index == -1:
print(mapname, "has loadspec out-of-bounds for", row[0])
elif row[loadspec_index] != mapname:
print(mapname, "has invalid loadspec for", row[0])
files.append(row[file_index])
return files




def get_materials(filename):
f = open(filename, "rb")
data = f.read(8)
l = f.read(4)
total_l = 0
multi = 1
for e in l:
total_l += int(e) * multi
multi *= 256
o = f.read(4)
total_o = 0
multi = 1
for e in o:
total_o += int(e) * multi
multi *= 256
f.close()
curloc = total_o
mats = []
while (curloc < total_o + total_l):
f = open(filename, "rb");
f.read(curloc)
mat = ""
loc = curloc
while loc < curloc + 64:
r = f.read(1)
loc += 1
if r == b"\x00":
break;
mat += r.decode("utf-8")
curloc += 72
f.close()
mats.append(mat)
f.close()
return mats

def get_images(filename):
f = open(filename, "rb")
f.read(4)
o = f.read(4)
total_o = 0
multi = 1
for e in o:
total_o += int(e) * multi
multi *= 256
f.close()
mats = []
f = open(filename, "rb")
f.read(total_o)
r = f.read(1)
mat = ""
while(len(r) > 0):
if(r == b"\x00"):
if len(mat) != 0:
mats.append(mat)
mat = ""
else:
mat += r.decode("utf-8")
r = f.read(1)
f.close()
return mats


startdir = "C:/JH2"
source = os.path.join(startdir, "merged")
source_stock = os.path.join(startdir, "stock")
dest_packs = os.path.join(startdir, "packs")
dest_soundalias = os.path.join(startdir, "soundaliases")
dest_empty = os.path.join(startdir, "empty_files")
dest_filelist = os.path.join(startdir, "filelist")
source_d3dbsp = os.path.join(source, "maps", "mp")
source_add_models = os.path.join(startdir, "add_models")
source_add_shaders = os.path.join(startdir, "add_shaders")

if os.path.exists(dest_packs):
shutil.rmtree(dest_packs)
os.mkdir(dest_packs)


if os.path.exists(dest_empty):
shutil.rmtree(dest_empty)
os.mkdir(dest_empty)

if os.path.exists(dest_filelist):
shutil.rmtree(dest_filelist)
os.mkdir(dest_filelist)


if os.path.exists(dest_soundalias):
shutil.rmtree(dest_soundalias)
os.mkdir(dest_soundalias)

for r,d,f in os.walk(source_d3dbsp):
for files in f:
if not files.endswith(".d3dbsp"):
continue
mapname = os.path.splitext(files)[0]
#print(mapname)
if not os.path.exists(os.path.join(dest_packs, mapname)):
os.mkdir(os.path.join(dest_packs, mapname))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps")):
os.mkdir(os.path.join(dest_packs, mapname, "maps"))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp")):
os.mkdir(os.path.join(dest_packs, mapname, "maps", "mp"))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".d3dbsp")):
shutil.copy(os.path.join(source_d3dbsp, mapname + ".d3dbsp"), os.path.join(dest_packs, mapname, "maps", "mp"))
if os.path.exists(os.path.join(source_d3dbsp, mapname + ".d3dprt")) and not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".d3dprt")):
shutil.copy(os.path.join(source_d3dbsp, mapname + ".d3dprt"), os.path.join(dest_packs, mapname, "maps", "mp"))
mats = get_materials(source_d3dbsp + "/" + files)
models = get_xmodels(source_d3dbsp + "/" + files)
models2 = get_more_xmodels(os.path.join(source_add_models, mapname + ".txt"))
models = models + models2
for m in models:
if os.path.exists(os.path.join(source_stock, m)):
continue
if not os.path.exists(os.path.join(source, m)):
print("Missing", m, "from map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, m)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, m))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, m)))
shutil.copy(os.path.join(source, m), os.path.dirname(os.path.join(dest_packs, mapname, m)))
mat_model = getmaterialsxmodel(source + "/" + m, mapname)
mats = mats + mat_model
xparts = getxmodelparts(source + "/" + m, mapname)
#if len(xparts) == 0:
# print("No parts in", m)
for p in xparts:
if os.path.exists(os.path.join(source_stock, "xmodelparts", p)):
#print("using stock for", p)
continue
if not os.path.exists(os.path.join(source, "xmodelparts", p)):
print("missing part", p, "for map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, "xmodelparts", p)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, "xmodelparts", p))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, "xmodelparts", p)))
shutil.copy(os.path.join(source, "xmodelparts", p), os.path.dirname(os.path.join(dest_packs, mapname, "xmodelparts", p)))
#print("copying xmodelpart", p)
#else:
# print("too lazy to copy", p)
for p in xparts:
if os.path.exists(os.path.join(source_stock, "xmodelsurfs", p)):
continue
if not os.path.exists(os.path.join(source, "xmodelsurfs", p)):
print("missing surf", p, "for map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, "xmodelsurfs", p)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, "xmodelsurfs", p))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, "xmodelsurfs", p)))
shutil.copy(os.path.join(source, "xmodelsurfs", p), os.path.dirname(os.path.join(dest_packs, mapname, "xmodelsurfs", p)))
if os.path.exists(source_d3dbsp + "/" + mapname + ".csv"):
mats_loadscreen = get_loadscreen(source_d3dbsp + "/" + mapname + ".csv")
mats = mats + mats_loadscreen
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".csv")):
shutil.copy(os.path.join(source_d3dbsp + "/" + mapname + ".csv"), os.path.join(dest_packs, mapname, "maps", "mp"))
mats2 = get_more_materials(os.path.join(source_add_shaders , mapname + ".txt"))
mats = mats + mats2
for mat in mats:
if mat == "noshader":
continue
if os.path.exists(os.path.join(source_stock, "materials", mat)):
continue
if not os.path.exists(source + "/materials/" + mat):
print("Could not find material:", mat, "from map:", mapname)
elif not os.path.exists(os.path.join(dest_packs, mapname, "materials", mat)):
if not os.path.exists(os.path.join(dest_packs, mapname, "materials")):
os.mkdir(os.path.join(dest_packs, mapname, "materials"))
shutil.copy(os.path.join(source, "materials", mat), os.path.join(dest_packs, mapname, "materials"))
imgs = get_images(os.path.join(dest_packs, mapname, "materials", mat))
for img in imgs:
if img.lower() == "colormap" or img.lower() == "detailmap" or img.lower() == "normalmap" or img.lower() == "detailscale" or img.lower() == "specularmap":
continue
if os.path.exists(os.path.join(source_stock, "images", img + ".iwi")):
continue
if not os.path.exists(os.path.join(source, "images", img + ".iwi")):
print("Could not find image:", img + ".iwi", "from map:", mapname)
elif not os.path.exists(os.path.join(dest_packs, mapname, "images", img + ".iwi")):
if not os.path.exists(os.path.join(dest_packs, mapname, "images")):
os.mkdir(os.path.join(dest_packs, mapname, "images"))
shutil.copy(os.path.join(source, "images", img + ".iwi"), os.path.join(dest_packs, mapname, "images"))
sounds = get_sounds(mapname)
if len(sounds) > 0:
if not os.path.exists(os.path.join(dest_soundalias, mapname + ".csv")):
shutil.copy(os.path.join(source, "soundaliases", mapname + ".csv"), os.path.join(dest_soundalias, mapname + ".csv"))
for sound in sounds:
if not os.path.exists(os.path.join(dest_packs, mapname, "sound", sound)):
if os.path.exists(os.path.join(source_stock, "sound", sound)):
continue
if os.path.exists(os.path.join(source, "sound", sound)):
d = os.path.dirname(os.path.join(dest_packs, mapname, "sound", sound))
if not os.path.exists(d):
os.makedirs(d)
if not os.path.exists(os.path.join(dest_packs, mapname, "sound", sound)):
shutil.copy(os.path.join(source, "sound", sound), os.path.join(dest_packs, mapname, "sound", sound))
else:
print("Could not find sound:", sound, "from map", mapname)

for subdir in os.listdir(dest_packs):
mapdir = os.path.join(dest_packs, subdir)
for src_dir, dirs, files in os.walk(mapdir):
dst_dir_empty = src_dir.replace(mapdir, dest_empty)
if not os.path.exists(dst_dir_empty):
os.mkdir(dst_dir_empty)
for file_ in files:
src_file = os.path.join(src_dir, file_)
dst_empty_file = os.path.join(dst_dir_empty, file_)
if not os.path.exists(dst_empty_file):
open(dst_empty_file, 'a').close()

for subdir in os.listdir(dest_packs):
for r,d,f in os.walk(os.path.join(dest_packs, subdir)):
for files in f:
if files.endswith(".d3dbsp"):
mapname = os.path.splitext(files)[0]
iwd = subdir
mapname = mapname.lower()
f = open(dest_filelist + "/" + mapname + ".txt", "a")
f.write(iwd + ",\n")
f.close()
input("Press enter to start the packing of the maps")
p = subprocess.Popen(os.path.join(startdir, "zip_packs.bat"), cwd=dest_packs)
stdout, stderr = p.communicate()



I know it is messy and shitty coded and could probably be done better/faster in c++, but the performance on an ssd is quite ok. If you want to improve this script, feel free to do so.
Code might still throw warnings for ambient on normal channels. My mod uses a different way of playing ambient. If it bothers you, disable it.

EDIT: ABOVE CODE IS DEPRECATED. LOOK FURTHER DOWN FOR UP_TO_DATE CODE

IzNoGoD
19th September 2013, 20:16
placeholder, if any such thing is possible on this site

Loveboy
22nd September 2013, 13:37
Nice Tutorial! I have got a mappack with 70MB, it's too big and I found here a nice tutorial!

IzNoGoD
15th October 2013, 13:05
Update, added support for reading weapon files:


import os
import shutil
import subprocess
import struct
import csv

def get_more_xmodels(filename):
found = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
return found

def get_more_materials(filename):
found = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
return found

def get_weapon_xmodels(filename):
found = []
models = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
for weap in found:
if os.path.exists(os.path.join(source_stock, "weapons", "mp", weap)):
continue
if os.path.exists(os.path.join(source, "weapons", "mp", weap)):
with open(os.path.join(source, "weapons", "mp", weap)) as weapfile:
reader = csv.reader(weapfile, delimiter = "\\")
num = 0
next_is_model = False
for row in reader:
for elem in row:
num += 1
if next_is_model:
next_is_model = False
if "xmodel/" in elem:
models.append(elem)
else:
models.append("xmodel/" + elem)
elif "model" in elem.lower() and not num%2:
next_is_model = True
else:
next_is_model = False
else:
print("weapon not found:", weap, "for map:", filename)
return models

def get_weapon_materials(filename):
found = []
materials = []
if os.path.exists(filename):
f = open(filename, "r")
for line in f:
if len(line) != 0:
found.append(line.strip('\n').strip('\r'))
for weap in found:
if os.path.exists(os.path.join(source_stock, "weapons", "mp", weap)):
continue
if os.path.exists(os.path.join(source, "weapons", "mp", weap)):
with open(os.path.join(source, "weapons", "mp", weap)) as weapfile:
reader = csv.reader(weapfile, delimiter = "\\")
num = 0
next_is_material = False
for row in reader:
for elem in row:
num += 1
if next_is_material:
next_is_material = False
materials.append(elem)
elif "icon" in elem and not num%2:
next_is_material = True
else:
next_is_material = False
else:
print("weapon not found:", weap, "for map:", filename)
return materials


def get_xmodels(filename):
models = []
f = open(filename, "rb")
for line in f:
if line.startswith(bytes(r'"model"', "utf-8")):
#print(line)
strs = ""
start = False
for chars in line:
if chr(chars) == " ":
start = True
elif chr(chars) == "\n":
break
elif start and chr(chars) == "*":
break;
elif start and chr(chars) != "\"":
strs += chr(chars)
if len(strs):
if not strs in models:
models.append(strs)
return models

def getxmodelparts(filename, mapname):
parts = []
f = open(filename, "rb")
res = f.read(1)
pos = 1
startpos = -1
count_255 = 0
while len(res) == 1:
if res[0] == 0:
count_255 += 1
if count_255 == 4:
if startpos == -1:
startpos = pos
else:
count_255 = 0
res = f.read(1)
pos += 1
f.close()
f = open(filename, "rb")
if startpos >= 1:
f.read(startpos)
else:
f.close()
print("bad stuff happened for part", filename, mapname)
return parts
char = f.read(1)
pos = 1
strs = ""
while pos <= 32:
if char == b"\x00":
break;
strs += char.decode("utf-8")
char = f.read(1)
pos += 1
f.close()
parts.append(strs)
return parts

def getmaterialsxmodel(filename, mapname):
mats = []
f = open(filename, "rb")
res = f.read(1)
pos = 1
startpos = -1
count_255 = 0
while len(res) == 1:
if res[0] == 255:
count_255 += 1
if count_255 == 4:
if startpos == -1:
startpos = pos
else:
count_255 = 0
res = f.read(1)
pos += 1
f.close()
f = open(filename, "rb")
if startpos >= 0:
f.read(startpos + 4)
else:
f.close()
print("bad stuff happened for materialfind ", filename, mapname)
return mats
count = 0
count += int(f.read(1)[0])
count += int(f.read(1)[0]) * 256
char = f.read(1)
strs = ""
while len(char) == 1 and len(mats) < count:
if char == b"\x00":
mats.append(strs)
strs = ""
else:
strs += char.decode("utf-8")
char = f.read(1)
f.close()
return mats


def get_loadscreen(filename):
mats = []
if not os.path.exists(filename):
return mats
with open(filename) as csvfile:
reader = csv.reader(csvfile, delimiter = ",")
for row in reader:
if len(row) < 2:
continue
if len(row) == 2 and row[0].lower() == "levelbriefing":
mats.append(row[1])
else:
print("bad stuff:", filename, row[0], row[1])
return mats


def get_sounds(mapname):
files = []
if not os.path.exists(os.path.join(source, "soundaliases", mapname + ".csv")):
return files
with open(os.path.join(source, "soundaliases", mapname + ".csv")) as csvfile:
file_index = -1
loadspec_index = -1
name_index = -1
reader = csv.reader(csvfile, delimiter = ",")
linenum = 0
for row in reader:
linenum += 1
if len(row) < 3:
continue
if row[0].startswith(r",") or row[0].startswith(r"#") or row[0].startswith(r"\"") or len(row[0]) == 0:
continue
if file_index == -1 and "file" in row:
file_index = row.index("file")
if loadspec_index == -1 and "loadspec" in row:
loadspec_index = row.index("loadspec")
if name_index == -1 and "name" in row:
name_index = row.index("name")
continue
if file_index == -1 or loadspec_index == -1:
continue
if name_index != -1 and len(row) > name_index and row[name_index] == "null":
continue
if len(row) <= loadspec_index or loadspec_index == -1:
print(mapname, "has loadspec out-of-bounds for", row[0])
elif row[loadspec_index] != mapname:
print(mapname, "has invalid loadspec for", row[0])
files.append(row[file_index])
return files




def get_materials(filename):
f = open(filename, "rb")
data = f.read(8)
l = f.read(4)
total_l = 0
multi = 1
for e in l:
total_l += int(e) * multi
multi *= 256
o = f.read(4)
total_o = 0
multi = 1
for e in o:
total_o += int(e) * multi
multi *= 256
f.close()
curloc = total_o
mats = []
while (curloc < total_o + total_l):
f = open(filename, "rb");
f.read(curloc)
mat = ""
loc = curloc
while loc < curloc + 64:
r = f.read(1)
loc += 1
if r == b"\x00":
break;
mat += r.decode("utf-8")
curloc += 72
f.close()
mats.append(mat)
f.close()
return mats

def get_images(filename):
f = open(filename, "rb")
f.read(4)
o = f.read(4)
total_o = 0
multi = 1
for e in o:
total_o += int(e) * multi
multi *= 256
f.close()
mats = []
f = open(filename, "rb")
f.read(total_o)
r = f.read(1)
mat = ""
while(len(r) > 0):
if(r == b"\x00"):
if len(mat) != 0:
mats.append(mat)
mat = ""
else:
mat += r.decode("utf-8")
r = f.read(1)
f.close()
return mats


startdir = "C:/JH2"
source = os.path.join(startdir, "merged")
source_stock = os.path.join(startdir, "stock")
dest_packs = os.path.join(startdir, "packs")
dest_soundalias = os.path.join(startdir, "soundaliases")
dest_empty = os.path.join(startdir, "empty_files")
dest_filelist = os.path.join(startdir, "filelist")
source_d3dbsp = os.path.join(source, "maps", "mp")
source_add_models = os.path.join(startdir, "add_models")
source_add_shaders = os.path.join(startdir, "add_shaders")
source_add_weapons = os.path.join(startdir, "add_weapons")

if os.path.exists(dest_packs):
shutil.rmtree(dest_packs)
os.mkdir(dest_packs)


if os.path.exists(dest_empty):
shutil.rmtree(dest_empty)
os.mkdir(dest_empty)

if os.path.exists(dest_filelist):
shutil.rmtree(dest_filelist)
os.mkdir(dest_filelist)


if os.path.exists(dest_soundalias):
shutil.rmtree(dest_soundalias)
os.mkdir(dest_soundalias)

for r,d,f in os.walk(source_d3dbsp):
for files in f:
if not files.endswith(".d3dbsp"):
continue
mapname = os.path.splitext(files)[0]
#print(mapname)
if not os.path.exists(os.path.join(dest_packs, mapname)):
os.mkdir(os.path.join(dest_packs, mapname))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps")):
os.mkdir(os.path.join(dest_packs, mapname, "maps"))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp")):
os.mkdir(os.path.join(dest_packs, mapname, "maps", "mp"))
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".d3dbsp")):
shutil.copy(os.path.join(source_d3dbsp, mapname + ".d3dbsp"), os.path.join(dest_packs, mapname, "maps", "mp"))
if os.path.exists(os.path.join(source_d3dbsp, mapname + ".d3dprt")) and not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".d3dprt")):
shutil.copy(os.path.join(source_d3dbsp, mapname + ".d3dprt"), os.path.join(dest_packs, mapname, "maps", "mp"))
mats = get_materials(source_d3dbsp + "/" + files)
models = get_xmodels(source_d3dbsp + "/" + files)
models2 = get_more_xmodels(os.path.join(source_add_models, mapname + ".txt"))
models3 = get_weapon_xmodels(os.path.join(source_add_weapons , mapname + ".txt"))
models = models + models2 + models3
for m in models:
if os.path.exists(os.path.join(source_stock, m)):
continue
if not os.path.exists(os.path.join(source, m)):
print("Missing", m, "from map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, m)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, m))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, m)))
shutil.copy(os.path.join(source, m), os.path.dirname(os.path.join(dest_packs, mapname, m)))
mat_model = getmaterialsxmodel(source + "/" + m, mapname)
mats = mats + mat_model
xparts = getxmodelparts(source + "/" + m, mapname)
#if len(xparts) == 0:
# print("No parts in", m)
for p in xparts:
if os.path.exists(os.path.join(source_stock, "xmodelparts", p)):
#print("using stock for", p)
continue
if not os.path.exists(os.path.join(source, "xmodelparts", p)):
print("missing part", p, "for map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, "xmodelparts", p)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, "xmodelparts", p))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, "xmodelparts", p)))
shutil.copy(os.path.join(source, "xmodelparts", p), os.path.dirname(os.path.join(dest_packs, mapname, "xmodelparts", p)))
#print("copying xmodelpart", p)
#else:
# print("too lazy to copy", p)
for p in xparts:
if os.path.exists(os.path.join(source_stock, "xmodelsurfs", p)):
continue
if not os.path.exists(os.path.join(source, "xmodelsurfs", p)):
print("missing surf", p, "for map", mapname)
continue
if not os.path.exists(os.path.join(dest_packs, mapname, "xmodelsurfs", p)):
if not os.path.exists(os.path.dirname(os.path.join(dest_p acks, mapname, "xmodelsurfs", p))):
os.makedirs(os.path.dirname(os.path.join(dest_pack s, mapname, "xmodelsurfs", p)))
shutil.copy(os.path.join(source, "xmodelsurfs", p), os.path.dirname(os.path.join(dest_packs, mapname, "xmodelsurfs", p)))
if os.path.exists(source_d3dbsp + "/" + mapname + ".csv"):
mats_loadscreen = get_loadscreen(source_d3dbsp + "/" + mapname + ".csv")
mats = mats + mats_loadscreen
if not os.path.exists(os.path.join(dest_packs, mapname, "maps", "mp", mapname + ".csv")):
shutil.copy(os.path.join(source_d3dbsp + "/" + mapname + ".csv"), os.path.join(dest_packs, mapname, "maps", "mp"))
mats2 = get_more_materials(os.path.join(source_add_shaders , mapname + ".txt"))
mats3 = get_weapon_materials(os.path.join(source_add_weapo ns, mapname + ".txt"))
mats = mats + mats2
for mat in mats:
if mat == "noshader":
continue
if os.path.exists(os.path.join(source_stock, "materials", mat)):
continue
if not os.path.exists(source + "/materials/" + mat):
print("Could not find material:", mat, "from map:", mapname)
elif not os.path.exists(os.path.join(dest_packs, mapname, "materials", mat)):
if not os.path.exists(os.path.join(dest_packs, mapname, "materials")):
os.mkdir(os.path.join(dest_packs, mapname, "materials"))
shutil.copy(os.path.join(source, "materials", mat), os.path.join(dest_packs, mapname, "materials"))
imgs = get_images(os.path.join(dest_packs, mapname, "materials", mat))
for img in imgs:
if img.lower() == "colormap" or img.lower() == "detailmap" or img.lower() == "normalmap" or img.lower() == "detailscale" or img.lower() == "specularmap":
continue
if os.path.exists(os.path.join(source_stock, "images", img + ".iwi")):
continue
if not os.path.exists(os.path.join(source, "images", img + ".iwi")):
print("Could not find image:", img + ".iwi", "from map:", mapname)
elif not os.path.exists(os.path.join(dest_packs, mapname, "images", img + ".iwi")):
if not os.path.exists(os.path.join(dest_packs, mapname, "images")):
os.mkdir(os.path.join(dest_packs, mapname, "images"))
shutil.copy(os.path.join(source, "images", img + ".iwi"), os.path.join(dest_packs, mapname, "images"))
sounds = get_sounds(mapname)
if len(sounds) > 0:
if not os.path.exists(os.path.join(dest_soundalias, mapname + ".csv")):
shutil.copy(os.path.join(source, "soundaliases", mapname + ".csv"), os.path.join(dest_soundalias, mapname + ".csv"))
for sound in sounds:
if not os.path.exists(os.path.join(dest_packs, mapname, "sound", sound)):
if os.path.exists(os.path.join(source_stock, "sound", sound)):
continue
if os.path.exists(os.path.join(source, "sound", sound)):
d = os.path.dirname(os.path.join(dest_packs, mapname, "sound", sound))
if not os.path.exists(d):
os.makedirs(d)
if not os.path.exists(os.path.join(dest_packs, mapname, "sound", sound)):
shutil.copy(os.path.join(source, "sound", sound), os.path.join(dest_packs, mapname, "sound", sound))
else:
print("Could not find sound:", sound, "from map", mapname)

for subdir in os.listdir(dest_packs):
mapdir = os.path.join(dest_packs, subdir)
for src_dir, dirs, files in os.walk(mapdir):
dst_dir_empty = src_dir.replace(mapdir, dest_empty)
if not os.path.exists(dst_dir_empty):
os.mkdir(dst_dir_empty)
for file_ in files:
src_file = os.path.join(src_dir, file_)
dst_empty_file = os.path.join(dst_dir_empty, file_)
if not os.path.exists(dst_empty_file):
open(dst_empty_file, 'a').close()

for subdir in os.listdir(dest_packs):
for r,d,f in os.walk(os.path.join(dest_packs, subdir)):
for files in f:
if files.endswith(".d3dbsp"):
mapname = os.path.splitext(files)[0]
iwd = subdir
mapname = mapname.lower()
f = open(dest_filelist + "/" + mapname + ".txt", "a")
f.write(iwd + ",\n")
f.close()
input("Press enter to start the packing of the maps")
p = subprocess.Popen(os.path.join(startdir, "zip_packs.bat"), cwd=dest_packs)
stdout, stderr = p.communicate()


It works the same as the add_models/add_shaders, only now create your mapname.txt containing line-ending separated weaponnames in add_weapons folder

IzNoGoD
8th November 2013, 15:11
UPDATE

Script now (_also_) does the following:

- read all loadingscreens for all maps
- convert them to dds using iwitodds.exe
- resize them to be smaller (less size in mod) using nvidia dds tools
- convert them back to iwi using dds2iwi.exe
- rename to jh_mapimage_[mapname]
- create a material file to go with that (had to reverse-engineer material files a bit for this)
- list all maps in a .menu file, using the generated (small) iwi files as mapimages
- dont modify stock loadingscreens (client doesnt need to download those anyway)
- create an awesome mapvote menu

Demo:
http://classic.xfire.com/video/615d26/

Script is private for now, if you want it, send me a PM.