Skip to content

Instantly share code, notes, and snippets.

@kice
Last active March 7, 2021 23:57
Show Gist options
  • Save kice/ac6bc24fa554429a3fcf810013713da7 to your computer and use it in GitHub Desktop.
Save kice/ac6bc24fa554429a3fcf810013713da7 to your computer and use it in GitHub Desktop.
import os
import re
import json
import subprocess
import argparse
class Args(object):
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
vmf_template = """versioninfo
{
\t"editorversion" "400"
\t"editorbuild" "8075"
\t"mapversion" "1"
\t"formatversion" "100"
\t"prefab" "0"
}
visgroups
{
}
viewsettings
{
\t"bSnapToGrid" "1"
\t"bShowGrid" "1"
\t"bShowLogicalGrid" "0"
\t"nGridSpacing" "1"
\t"bShow3DGrid" "0"
}
world
{
\t"id" "1"
\t"mapversion" "1"
\t"classname" "worldspawn"
\t"detailmaterial" "detail/detailsprites"
\t"detailvbsp" "detail.vbsp"
\t"maxpropscreenwidth" "-1"
\t"skyname" "sky_dust"
}
"""
vmf_template_end = """cameras
{
\t"activecamera" "-1"
}
cordons
{
\t"active" "0"
}"""
def runcmd(cmd, silent = False):
try:
popen_params = {
"stdout": subprocess.PIPE if silent else None,
"stderr": None,
"stdin": subprocess.DEVNULL,
}
popen_params['env'] = os.environ
popen_params['env']['PATH'] += ';' + args.game + '\\..\\bin'
subprocess.run(cmd, **popen_params)
except Exception as err:
print(err)
return False
else:
return True
def parse(lines, i = 0):
__regexs = {
"key": re.compile(r"""(['"]?)(?P<key>([0-9a-z-_])*)(['"]?)""", re.I),
"key_value": re.compile(r"""(['"])(?P<key>((?!\1).)*)\1(\s+|)['"](?P<value>((?!\1).)*)\1""", re.I)
}
_mapper = {}
key = None
try:
while i < len(lines):
if lines[i].startswith("{"):
if not key:
raise Exception("'{{' found without key at line {}".format(i + 1))
m, i = parse(lines, i+1)
if key not in _mapper: _mapper[key] = []
_mapper[key] += [m]
continue
elif lines[i].startswith("}"):
return _mapper, i + 1
elif re.match(__regexs["key_value"], lines[i] + lines[i+1]):
groups = re.search(__regexs["key_value"], lines[i] + " " + lines[i+1])
if groups.group("key") in _mapper:
old_value = groups.group("value")
if isinstance(old_value, list):
_mapper[groups.group("key")] += [groups.group("value")]
else:
_mapper[groups.group("key")] = [_mapper[groups.group("key")], groups.group("value")]
else:
_mapper[groups.group("key")] = groups.group("value")
i += 1
elif re.match(__regexs["key"], lines[i]):
key = re.search(__regexs["key"], lines[i]).group("key").strip()
i += 1
else:
i += 1
except IndexError:
pass
return _mapper
def write_vmf(mapdata, i = 0):
vmf = ''
indent = ''.join(['\t' for _ in range(i)])
for cls, items in mapdata.items():
for item in items:
vmf += f'{indent}{cls}\n{indent}{{\n'
for key, value in item.items():
if isinstance(value, list):
if key == 'visgroupid':
for gid in value:
vmf += f'{indent}\t"visgroupid" "{gid}"\n'
else:
vmf += write_vmf({key:value}, i + 1)
else:
vmf += f'{indent}\t"{key}" "{value}"\n'
vmf += f'{indent}}}\n'
return vmf
def get_maxid(mapdata):
max_id = 0
for cls, items in mapdata.items():
for item in items:
for key, value in item.items():
if isinstance(value, list):
sid = get_maxid({key:value})
max_id = sid + 1 if sid > max_id else max_id
elif key == 'id':
max_id = int(value) + 1 if int(value) > max_id else max_id
return max_id
def add_prop_static(id, model, origin, angles, visgroupid=0):
entity = {
"id": str(id),
"classname": "prop_static",
"angles": angles,
"disableflashlight": "0",
"disableselfshadowing": "0",
"disableshadowdepth": "0",
"disableshadows": "0",
"disablevertexlighting": "0",
"disableX360": "0",
"drawinfastreflection": "0",
"enablelightbounce": "0",
"fademaxdist": "0",
"fademindist": "-1",
"fadescale": "1",
"ignorenormals": "0",
"maxcpulevel": "0",
"maxgpulevel": "0",
"mincpulevel": "0",
"mingpulevel": "0",
"model": model,
"preventpropcombine": "0",
"renderamt": "255",
"rendercolor": "255 255 255",
"screenspacefade": "0",
"shadowdepthnocache": "0",
"skin": "0",
"solid": "6",
"uniformscale": "1",
"origin": origin,
"editor" : [{
"color" : "255 255 0",
"visgroupshown": "1",
"visgroupautoshown": "1",
"logicalpos": "[0 0]",
}]
}
if isinstance(visgroupid, list) or int(visgroupid) > 0:
entity['editor'][0]['visgroupid'] = visgroupid
return entity
def add_prop_dynamic(id, model, origin, angles, visgroupid = 0):
entity = {
"id": str(id),
"classname": "prop_dynamic",
"angles": angles,
"body": "0",
"DisableBoneFollowers": "0",
"disableflashlight": "0",
"disablereceiveshadows": "0",
"disableshadowdepth": "0",
"disableshadows": "0",
"disableX360": "0",
"drawinfastreflection": "0",
"ExplodeDamage": "0",
"ExplodeRadius": "0",
"fademaxdist": "0",
"fademindist": "-1",
"fadescale": "1",
"HoldAnimation": "0",
"is_autoaim_target": "0",
"MaxAnimTime": "10",
"maxcpulevel": "0",
"maxgpulevel": "0",
"MinAnimTime": "5",
"mincpulevel": "0",
"mingpulevel": "0",
"model": model,
"PerformanceMode": "0",
"pressuredelay": "0",
"RandomAnimation": "0",
"renderamt": "255",
"rendercolor": "255 255 255",
"renderfx": "0",
"rendermode": "0",
"SetBodyGroup": "0",
"shadowdepthnocache": "0",
"skin": "0",
"solid": "6",
"spawnflags": "0",
"StartDisabled": "0",
"origin": origin,
"editor" : [{
"color" : "255 255 0",
"visgroupshown": "1",
"visgroupautoshown": "1",
"logicalpos": "[0 0]",
}]
}
if isinstance(visgroupid, list) or int(visgroupid) > 0:
entity['editor'][0]['visgroupid'] = visgroupid
return entity
def add_prop_physics(id, model, origin, angles, visgroupid = 0):
entity = {
"id": str(id),
"classname": "prop_physics",
"angles": angles,
"body": "0",
"damagetoenablemotion": "0",
"Damagetype": "0",
"disableflashlight": "0",
"disablereceiveshadows": "0",
"disableshadowdepth": "0",
"disableshadows": "0",
"disableX360": "0",
"drawinfastreflection": "0",
"ExplodeDamage": "0",
"ExplodeRadius": "0",
"ExploitableByPlayer": "0",
"fademaxdist": "0",
"fademindist": "-1",
"fadescale": "1",
"forcetoenablemotion": "0",
"inertiaScale": "1.0",
"massScale": "0",
"maxcpulevel": "0",
"maxgpulevel": "0",
"mincpulevel": "0",
"mingpulevel": "0",
"model": model,
"minhealthdmg": "0",
"nodamageforces": "0",
"PerformanceMode": "0",
"physdamagescale": "0.1",
"pressuredelay": "0",
"renderamt": "255",
"rendercolor": "255 255 255",
"renderfx": "0",
"rendermode": "0",
"shadowcastdist": "0",
"shadowdepthnocache": "0",
"skin": "0",
"spawnflags": "256",
"origin": origin,
"editor" : [{
"color" : "255 255 0",
"visgroupshown": "1",
"visgroupautoshown": "1",
"logicalpos": "[0 0]",
}]
}
if isinstance(visgroupid, list) or int(visgroupid) > 0:
entity['editor'][0]['visgroupid'] = visgroupid
return entity
parser = argparse.ArgumentParser(description='Auto extract propper model and compiling, then add them back')
parser.add_argument('--config', type=str, default='', help='Path to autoprop config file')
parser.add_argument('--propper', type=str, default='propper.exe', help='Path to propper.exe')
parser.add_argument('--game', type=str, help='Path game mod folder')
parser.add_argument('--nocompile', action='store_true', help='Disable propper.exe for compiling the model')
parser.add_argument('--novmf', action='store_true', help='Disable writing new VMF file')
parser.add_argument('--nolog', action='store_true', help='Disable the output of propper.exe')
parser.add_argument('vmfpath', type=str, help='VMF file that contain propper model infomation')
args2 = parser.parse_args()
if len(args2.config) > 0 and os.path.exists(args2.config):
with open(args2.config, 'w') as f:
cfg = json.load(f)
args = Args()
args.propper = cfg['propper']
args.game = cfg['game']
args.nocompile = args2.nocompile
args.novmf = args2.novmf
args.nolog = args2.nolog
else:
args = args2
vmfpath = args2.vmfpath
vmfpath, ext = os.path.splitext(vmfpath)
# Reading VMF
print('===============================')
print(f'Loading {vmfpath}.vmf ...')
with open(vmfpath + '.vmf', mode='r') as f:
vmfdata = parse([line.strip() for line in f.readlines()])
print('===============================')
if 'entity' not in vmfdata or len(vmfdata['entity']) == 0:
print('No entity in .vmf')
propper_class = [
'propper_attachment', 'propper_lod', 'propper_bodygroup',
'propper_physgun_interactions', 'propper_particles', 'propper_cables',
'propper_gibs', 'propper_skins', 'propper_model', 'propper_physics',
]
named_entity = {}
for entity in vmfdata['entity']:
if 'targetname' in entity:
named_entity[entity['targetname']] = entity
propper_phy = [] # physics prop
propper_col = [] # collision mesh
propper_list = [] # all propper model
propper_ignore = [] # the model ignore adding prop_*, like gib
for entity in vmfdata['entity']:
if entity['classname'] in propper_class:
propper_list += [entity]
targetname = ''
if 'targetname' in entity:
targetname = entity['targetname']
display = targetname + f"({entity['classname']})"
else:
display = entity['classname']
phys = 'no'
if 'physmodel' in entity:
phys = entity['physmodel']
if len(phys) > 0:
if phys not in named_entity:
print(f'Warning: {display} has a invaild physmodel: {phys}')
elif targetname != phys:
propper_col += [phys]
if entity['classname'] == 'propper_physics':
propper_phy += [entity['my_model']]
print(f"Found propper physics => id:{entity['id']}\t{display} for {entity['my_model']}")
elif entity['classname'] == 'propper_gibs':
propper_ignore += [entity['my_model']]
print(f"Found propper gibs => id:{entity['id']}\t{display} for {entity['my_model']}")
elif entity['classname'] == 'propper_model':
print(f"Found propper model => id:{entity['id']}\t{display} @ \"{entity['origin']}\", using {phys} collision mesh")
else:
print(f"Found propper entity => id:{entity['id']}\t{display}")
if len(propper_list) == 0:
print('No propper model was found. Exiting...')
exit(1)
print(f'Total found {len(propper_list)} (physics prop: {len(propper_phy)}) propper entity(s)')
# Writing propper VMF
propper_dir = vmfpath + '_propper'
if not os.path.exists(propper_dir):
os.mkdir(propper_dir)
basename = os.path.basename(vmfpath)
name, ext = os.path.splitext(basename)
propper_vmf = os.path.join(propper_dir, f'{name}_prop')
print('===============================')
print(f'Write propper vmf to {propper_vmf + ".vmf"}...')
print('===============================')
print('Building propper VMF file...')
pvmf = vmf_template
pvmf += write_vmf({'entity': propper_list})
print('Adding collision mesh to propper VMF...')
pvmf += write_vmf({'entity': [named_entity[x] for x in propper_col]})
pvmf += vmf_template_end
with open(propper_vmf + '.vmf', 'w') as f:
print('Writing propper VMF file...')
print(pvmf, file=f)
print('Done.')
# call propper
print('===============================')
print('Running propper.exe')
print('===============================')
if args.nocompile:
print('No propper.exe complie was set, ignoring...')
else:
if not runcmd([args.propper, '-game', args.game, propper_vmf], args.nolog):
print('Running propper.exe failed. Exiting...')
exit(1)
# Write new vmf
print('===============================')
print('Creating new VMF file for VBSP')
print('===============================')
if args.novmf:
print('No VMF was set, exiting...')
exit(0)
print('Moving all propper entity and collision mesh to hidden visgroup...')
model_visgroup = False
nohidden_visgroup = False
for visgroup in vmfdata['visgroups'][0]['visgroup']:
if visgroup['visgroupid'] == '9999':
print('Warning: already exists visgroup with id=9999')
nohidden_visgroup = True
if visgroup['name'] == 'Propper_Hidden':
print('Warning: already exists visgroup with name "Propper_Hidden"')
if visgroup['visgroupid'] == '2333':
print('Warning: already exists visgroup with id=2333')
model_visgroup = True
if visgroup['name'] == 'Propper_Model':
print('Warning: already exists visgroup with name "Propper_Model"')
if nohidden_visgroup:
print('No hidden visgroup was added.')
else:
vmfdata['visgroups'][0]['visgroup'] += [{
'name': 'Propper_Hidden',
'visgroupid': '9999',
'color': '152 177 204',
}]
if model_visgroup:
print('No propper model visgroup was added.')
else:
vmfdata['visgroups'][0]['visgroup'] += [{
'name': 'Propper_Model',
'visgroupid': '2333',
'color': '214 63 98',
}]
if 'hidden' not in vmfdata:
vmfdata['hidden'] = []
max_id = get_maxid(vmfdata) * 2
print(f'Propper model will begin with id={max_id}.')
props = []
new_entity = []
for entity in vmfdata['entity']:
if 'visgroupid' in entity['editor'][0]:
vgid = entity['editor'][0]['visgroupid']
if not isinstance(vgid, list):
vgid = [vgid]
else:
vgid = []
if entity['classname'] not in propper_class:
if 'targetname' not in entity or entity['targetname'] not in propper_col:
new_entity += [entity]
continue
if 'hidden' not in entity:
entity['hidden'] = []
if 'solid' in entity and isinstance(entity['solid'], list):
for solid in entity['solid']:
solid['editor'][0]['visgroupshown'] = '0'
entity['hidden'] += [{'solid': [solid]}]
del entity['solid']
entity['editor'][0]['visgroupid'] = vgid + ['9999']
entity['editor'][0]['visgroupshown'] = '0'
vmfdata['hidden'] += [{'entity': [entity]}]
continue
if entity['classname'] == 'propper_model':
entity['hidden'] = []
for solid in entity['solid']:
solid['editor'][0]['visgroupshown'] = '0'
entity['hidden'] += [{'solid': [solid]}]
del entity['solid']
targetname = ''
if 'targetname' in entity:
targetname = entity['targetname']
entity['targetname'] = '_' + targetname
if targetname in propper_ignore:
continue
elif targetname in propper_phy:
prop = add_prop_physics(
max_id,
'models/{}.mdl'.format(entity['modelname']),
entity['origin'],
'0 270 0',
vgid + ['2333']
)
elif 'dynamic_prop' in entity and int(entity['dynamic_prop']) == 1:
prop = add_prop_dynamic(
max_id,
'models/{}.mdl'.format(entity['modelname']),
entity['origin'],
'0 270 0',
vgid + ['2333']
)
else:
prop = add_prop_static(
max_id,
'models/{}.mdl'.format(entity['modelname']),
entity['origin'],
'0 270 0',
vgid + ['2333']
)
if len(targetname) > 0:
prop['targetname'] = targetname
props += [prop]
max_id += 1
entity['editor'][0]['visgroupid'] = vgid + ['9999']
entity['editor'][0]['visgroupshown'] = '0'
vmfdata['hidden'] += [{'entity': [entity]}]
vmfdata['entity'] = new_entity + props
final_vmf = os.path.join(propper_dir, f'{name}.vmf')
with open(final_vmf, 'w') as f:
print(f'Writing final VMF file to {final_vmf}...')
print(write_vmf(vmfdata), file=f)
print('Done.')
print('For hammer "Run Map": new .vmf will be at "$path\$file_propper\$file" e.g. $bsp_exe "-game $gamedir $path\$file_propper\$file"')
// Purpose: Easy entity definitions for Propper. v0.3
// See http://developer.valvesoftware.com/wiki/Propper
// @include "base.fgd"
@BaseClass = Propper
[
my_model(target_destination) : "Model to apply to" : "" : "Pick a propper_model entity for this property to apply to."
]
@BaseClass = PropperTargetname
[
targetname(target_source) : "Name" : : "The name that other entities refer to this entity by."
]
@BaseClass = PropperOrigin
[
origin(origin) : "Origin (X Y Z)" : : "The position of this entity's center in the world. Rotating entities typically rotate around their origin."
]
@BaseClass = PropperAngles
[
angles(angle) : "Pitch Yaw Roll (Y Z X)" : "0 0 0" : "This entity's orientation in the world. Pitch is rotation around the Y axis, " +
"yaw is the rotation around the Z axis, roll is the rotation around the X axis."
]
@PointClass base(PropperTargetname, PropperOrigin, PropperAngles, Propper) studio("models/editor/axis_helper.mdl") = propper_attachment : "An attachment is an arbitrary point you can define in a model. They have several uses."
[]
@PointClass base(Propper) iconsprite("editor/logic_case.vmt") = propper_bodygroup : "Bodygroups allow you to swap between parts of a mesh. For example, you could have a door with several different knobs and allow the mapper to pick one."
[
groupname(string) : "Bodygroup name" : "" : "A name for the bodygroup. This is required."
body01(target_destination) : "Body Part 01" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body02(target_destination) : "Body Part 02" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body03(target_destination) : "Body Part 03" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body04(target_destination) : "Body Part 04" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body05(target_destination) : "Body Part 05" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body06(target_destination) : "Body Part 06" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body07(target_destination) : "Body Part 07" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body08(target_destination) : "Body Part 08" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body09(target_destination) : "Body Part 09" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body10(target_destination) : "Body Part 10" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body11(target_destination) : "Body Part 11" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body12(target_destination) : "Body Part 12" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body13(target_destination) : "Body Part 13" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body14(target_destination) : "Body Part 14" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body15(target_destination) : "Body Part 15" : "" : "Pick a brush entity (such as func_brush)to be the body group"
body16(target_destination) : "Body Part 16" : "" : "Pick a brush entity (such as func_brush)to be the body group"
]
@PointClass base(Propper) studio("models/editor/cone_helper.mdl") = propper_particles : "Attach a particle emitter to this prop. This is for 2007 or 2009 engines only. Does not work with prop_static."
[
name(string) : "Particle system" : "" : "The name of a particle system. NOT the name of a .pcf file!"
attachment_type(choices) : "Attachment Type" : "follow_origin" : "'Spawn at' choices will start the particle system at the prop's position, but if the prop moves, the particle emitter stays in place. Good for short-term effects like explosions" =
[
"start_at_origin" : "Spawn at prop origin"
"start_at_attachment" : "Spawn at prop attachment"
"follow_origin" : "Follow prop's origin"
"follow_attachment" : "Follow prop at attachment"
]
attachment_point(string) : "Attachment point" : "" : "The attachment point at which the particle system should spawn. Use a propper_attachment for this."
]
@PointClass base(Propper) iconsprite("editor/multi_manager.vmt") = propper_cables : "Attach a visual cable to the selected prop. Does not work on static props."
[
StartAttachment(string) : "Cable Start Point" : "" : "The name of a propper_attachment"
EndAttachment(string) : "Cable End Point" : "" : "The name of a propper_attachment"
Width(float) : "Width" : 3 : "The width of the cable in units "
Material(material) : "Cable material" : "" : "The VMT (material) to use. Cable materials are located in 'materials/cables'."
NumSegments(integer) : "Cable segments" : 4 : "The number of segments in the cable. More segments make the cable more 'smooth'. Should be increased if the cable bends a lot."
Length(integer) : "Cable Length" : 4 : "The length of cable between the two attachment points. If the length is shorter than the distance between the two points then the cable will remain permanently straight. Note: Finding the optimal length between two points can be a frustrating task. Cables can go from super tight to extremely slack within a difference of just 10 units."
]
@PointClass base(Propper) iconsprite("editor/gibshooter.vmt") sphere(fademindist) sphere(fademaxdist) = propper_gibs : "When a prop breaks, You can cause one or more custom-made broken pieces to spawn in its place."
[
model(studio) : "Gib model" : ""
ragdoll(choices) : "Model Type" : "model" : "If the chosen gib model supports ragdoll physics, Use this setting to enable it." =
[
"model" : "Physics"
"ragdoll" : "Ragdoll"
]
debris(choices) : "debris" : "1" : "Debris won't collide with the player or other dynamic objects. Use if the gib is small" =
[
0 : "No"
1 : "Yes"
]
burst(integer) : "Burst Strength" : 0 : "The gib will receive a force out from the center of the prop if this is set."
fadetime(integer) : "Fade away time" : 0 : "The gib will begin to disappear after this many seconds."
fademindist(integer) : "Min Fade Distance (units)" : 0 : "Gib will begin to disappear at this distance from the player"
fademaxdist(integer) : "Max Fade Distance (units)" : 0 : "Gib will not render at this distance from the player"
]
@PointClass base(Propper) iconsprite("editor/color_correction.vmt") = propper_skins : "Propper can create alternate skins by replacing a texture that is on your prop. If you have multiple textures on your model that you want to change, you can use this entity more than once."
[
mat0(material) : "Material to replace" : "" : "Pick a texture that's present in the original model. This will be skin 0."
mat1(material) : "Skin 1" : "" : "This texture will replace the original when you pick skin 1."
mat2(material) : "Skin 2" : "" : "This texture will replace the original when you pick skin 2."
mat3(material) : "Skin 3" : "" : "This texture will replace the original when you pick skin 3."
mat4(material) : "Skin 4" : "" : "This texture will replace the original when you pick skin 4."
mat5(material) : "Skin 5" : "" : "This texture will replace the original when you pick skin 5."
mat6(material) : "Skin 6" : "" : "This texture will replace the original when you pick skin 6."
mat7(material) : "Skin 7" : "" : "This texture will replace the original when you pick skin 7."
mat8(material) : "Skin 8" : "" : "This texture will replace the original when you pick skin 8."
mat9(material) : "Skin 9" : "" : "This texture will replace the original when you pick skin 9."
mat10(material) : "Skin 10" : "" : "This texture will replace the original when you pick skin 10."
mat11(material) : "Skin 11" : "" : "This texture will replace the original when you pick skin 11."
mat12(material) : "Skin 12" : "" : "This texture will replace the original when you pick skin 12."
mat13(material) : "Skin 13" : "" : "This texture will replace the original when you pick skin 13."
mat14(material) : "Skin 14" : "" : "This texture will replace the original when you pick skin 14."
]
@SolidClass base(PropperTargetname, PropperOrigin) = propper_model : "This will create a model when Propper is run."
[
modelname(string) : "Model Name" : "props/generated_prop" : "The directory and name of the finished model. Relative to <yourmod>/models"
materialpath(string) : "Material path" : "models/props/generated_prop" : "Where the model's textures will be. Relative to <yourmod>/materials."
scale(float) : "Scale" : "1.0" : "Scale the model up or down by this factor."
surfaceprop(choices) : "Surface property" : "no_decal" : "The physical properties of the model--affects impact sounds and weight for physics props." =
[
"no_decal" : "no_decal"
"boulder" : "boulder"
"brick" : "brick"
"concrete" : "concrete"
"concrete_block" : "concrete_block"
"gravel" : "gravel"
"rock" : "rock"
"metal" : "metal"
"solidmetal" : "solidmetal"
"metalgrate" : "metalgrate"
"metalvent" : "metalvent"
"wood" : "wood"
"dirt" : "dirt"
"grass" : "grass"
"gravel" : "gravel"
"mud" : "mud"
"sand" : "sand"
"slipperyslime" : "slipperyslime"
"ice" : "ice"
"snow" : "snow"
"alienflesh" : "alienflesh"
"bloodyflesh" : "bloodyflesh"
"flesh" : "flesh"
"foliage" : "foliage"
"asphalt" : "asphalt"
"glass" : "glass"
"tile" : "tile"
"paper" : "paper"
"cardboard" : "cardboard"
"plaster" : "plaster"
"plastic" : "plastic"
"rubber" : "rubber"
"carpet" : "carpet"
"ceiling_tile" : "ceiling_tile"
"computer" : "computer"
"pottery" : "pottery"
]
physmodel(target_destination) : "Physics mesh" : "" : "Pick any brush entity (including this one) to represent the physical shape of the model. Leave this field blank for a non-solid model. If you leave this blank while making a physics prop, Propper will automatically use this entity for collisions."
dynamic_prop(choices) : "Create as prop_dynamic" : "0" : "(Autoprop) Insert this prop as prop_dynamic instead of prop_static. The targetname of this propper_model will be used for the corresponding prop_dynamic." =
[
0 : "No"
1 : "Yes"
]
mass(float) : "Mass" : "0.0" : "Weight of the prop in kg. Enter zero or lower to automatically calculate mass."
concave(choices) : "Concave collisions" : "1" : "Used to make a concave collision model. If you choose no, the model will be 'shrink-wrapped'." =
[
0 : "No"
1 : "Yes"
]
smoothing(choices) : "Smoothing mode" : "1" : "Determines how vertex normals are computed for lighting" =
[
0 : "completely faceted"
1 : "auto-smooth"
2 : "Use Hammer's smoothing groups only"
]
sourcefolder(string) : "Source folder" : "c:/propsource" : "Where you want to keep your .qc and .smd files. The final directory will be <sourcefolder>/<modelname>."
smoothangle(integer) : "Smoothing threshold" : 45 : "If auto-smooth is enabled, Edges flatter than this angle will appear smooth. Pick from 0 to 180."
snaptogrid(choices) : "Snap to Hammer grid" : "0" : "Causes every vertex to be snapped to the nearest grid point." =
[
0 : "No"
1 : "Yes"
]
weldvertices(float) : "Welding threshold" : ".01" : "Vertices will be snapped together if within this tolerance. A value of 0 is not recommended because there may be visible gaps in the mesh. Larger numbers will merge vertices together, and may be useful for optimizing your model."
autocenter(choices) : "Auto-Center" : "0" : "The model's origin point defines how it is lit and is how the prop is positioned in a map. Auto-center moves the model's origin to the center of its bounding box and is recommended for physics props. For static props, put the origin somewhere that lets you align the prop easily." =
[
0 : "No"
1 : "Yes"
]
mat_nonormal(choices) : "Disable normal mapping" : "0" : "This option removes all bump map info from the converted materials. This is useful if you wish to use -StaticPropLighting to light your props" =
[
0 : "No"
1 : "Yes"
]
disp_nowarp(choices) : "Don't warp displacement textures" : "0" : "In Hammer, when you move a displacement point, the texture moves with it. If you pick yes, the texture will not move." =
[
0 : "No"
1 : "Yes"
]
]
@PointClass base(PropperOrigin, Propper) iconsprite("editor/phys_ballsocket.vmt") = propper_physics : "Propper configuration for physics props. Using this entity will make your prop suitable for use as prop_physics."
[
base(choices) : "Health preset" : "Stone.Medium" : "Dictates how strong the prop is vs. different weapons." =
[
"Cardboard.Small" : "Cardboard.Small"
"Cardboard.Medium" : "Cardboard.Medium"
"Cardboard.Large" : "Cardboard.Large"
"Cloth.Small" : "Cloth.Small"
"Cloth.Medium" : "Cloth.Medium"
"Cloth.Large" : "Cloth.Large"
"Wooden.Tiny" : "Wooden.Tiny"
"Wooden.Small" : "Wooden.Small"
"Wooden.Medium" : "Wooden.Medium"
"Wooden.Large" : "Wooden.Large"
"Wooden.ExtraLarge" : "Wooden.ExtraLarge"
"Wooden.Huge" : "Wooden.Huge"
"Stone.Small" : "Stone.Small"
"Stone.Medium" : "Stone.Medium"
"Stone.Large" : "Stone.Large"
"Stone.Huge" : "Stone.Huge"
"Glass.Small" : "Glass.Small"
"Glass.Window" : "Glass.Window"
"Metal.Small" : "Metal.Small"
"Metal.Medium" : "Metal.Medium"
"Plastic.Small" : "Plastic.Small"
"Item.Small" : "Item.Small"
"Plastic.Medium" : "Plastic.Medium"
"Item.Medium" : "Item.Medium"
"Plastic.Large" : "Plastic.Large"
"Item.Large" : "Item.Large"
"Pottery.Small" : "Pottery.Small"
"Pottery.Medium" : "Pottery.Medium"
"Pottery.Large" : "Pottery.Large"
"Pottery.Huge" : "Pottery.Huge"
"Flesh.Tiny" : "Flesh.Tiny"
"Flesh.Small" : "Flesh.Small"
]
health(integer) : "Health" : -1 : "Overrides the prop's health. Set this to zero to give infinite health."
flammable(choices) : "Flammable" : "0" : "Will it burn? Picking this enables the 'ignite' options." =
[
0 : "No"
1 : "Yes"
]
ignite_at_half_health(choices) : "Ignite at half-health" : "0" : "Just like those barrels in HL2." =
[
0 : "No"
1 : "Yes"
]
explosive_resist(choices) : "Ignite from explosions" : "0" : "Won't break right away if something explodes near it, but will ignite."=
[
0 : "No"
1 : "Yes"
]
explosive_damage(float) : "Explode damage" : "0.0" : "Damage to do when breaking"
explosive_radius(float) : "Explode radius" : "0.0" : "Radius of explosion"
breakable_model(choices) : "Gibs" : "ConcreteChunks" : "Generic shards to spawn when the prop breaks" =
[
"WoodChunks" : "Wood"
"GlassChunks" : "Glass"
"ConcreteChunks" : "Concrete"
"MetalChunks" : "Metal"
]
breakable_count(integer) : "Gib count" : 5 : "How many gibs?"
breakable_skin(integer) : "Gib skin" : 0 : "Which skin to use on the gib models, if applicable."
]
@kice
Copy link
Author

kice commented Jun 25, 2019

AutoProp

A command line tool that extract propper_model from a normal VMF file, using propper compile them, and add prop_* back to it.

You can run autoprop standalone, and you can also integrate into Hammer "Run Map" command.

Setup propper

  1. Follow the tutorials CONFIGURING PROPPER FOR STEAMPIPE by tophattwaffle

  2. Replace propper.fgd using this. This file fixed crash while the hammer for CSGO SDK open normal map with propper.fgd. And add a new keyvalue Create as prop_dynamic for autoprop to propper_model.

Install autoprop

Download exe file

Download this exe and drop it into the same folder as propper.exe

Install from source

  1. Install python 3.7, and PyInstaller via pip install pyinstaller.
  2. download autoprop.py
  3. create exe using PyInstaller by pyinstaller --onefile autoprop.py
  4. copy the exe to the same folder as propper.exe

Setup Hammer "Run Map"

  1. Change to "Expert" mode
  2. Go to "Edit" and copy an existing compile configuration for normal source map compiling, like Full compile - HDR only
  3. Select the configuration from the drop down menu
  4. Click "New", to "Command" area and click "Cmds -> Executable". Navigate and select autoprop.exe, which should be the same directory as propper.exe.
  5. To "Parameters", add --game $gamedir $path\$file. use --game instead of -game
  6. Check the box in the Compile/run commands section and move it up to the top.
  7. Change the parameters of $bsp_exe, $vis_exe, $light_exe and Copy File. By replacing $path\$file to $path\$file_propper\$file.

About the final VMF file

All prop_* entity have angle 0 270 0, which using angle 0 0 0 is the wrong direction.

All propper_* entity will be placed inside a visgroup call "Propper_Hidden". You can toggle them on/off to make sure everything is placed correctly.

All prop_* entity will be placed inside a visgroup call "Propper_model".

If you cannot compile the map, try to open the final VMF locates in "./<VMF_name>_propper/<VMF_name>.vmf" and "Save" by hammer to correct any error.

For hidden propper entity, it will not be copied to the VMF for propper and will be copied to the final VMF.

Options for autoprop

Run autoprop.exe -h or python autoprop.py -h to see more.

--game: The path to your Game Directory
--nocompile: Skip propper compile the model, This will only create a new .vmf file with only the bursh/entity info for propper and the final .vmf file with prop_* replacing propper_model
--novmf: Skip writing the final .vmf file
--nolog: Discard the output of propper.exe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment