Another hotfix, this fixes an issue with the
id()
function not returning the targetname of the parent macro entity.I've also added another template behavior for setting the speed of scrolling textures in brush entities other than
func_conveyor
.id()
function not returning the targetname of the parent macro entity.func_conveyor
.+door1
and train -> newpos
instead of having to manually create a trigger_relay or trigger_changetarget. Advanced users can even make their own template entities and behaviors, and share them with others via .zip files.{_tb_group}
to entity names and targetnames inside linked groups to produce unique names per group.
Download links:
m_hEnemy->pev = pevAttacker
doesn't look right - you're not checking whether m_hEnemy
is null, but also, you're modifying the current target instead of switching to another target. If I understand things correctly then you need to set m_hEnemy
to GetMonsterPointer(pevAttacker)
.PushEnemy
function instead of changing m_hEnemy
directly.trigger
--(toggle)--> target entity
trigger
--(toggle)--> trigger_relay ('off')
--(off)--> target entity
trigger
--(toggle)--> trigger_relay ('off')
--(off)--> button_target
--(toggle, if button_target changed state)--> target entity
C:\Users\Alex\Documents\xash port\valve\maps\c1a0.wad
with Wally or HL Texture Tools to verify that it's a 'good' wad file?ripent.exe
(one of the programs that's included in ZHLT / VHLT) can export and import textures from/into bsp files:ripent.exe -textureexport "C:\where\is\your\valve\maps\mapname.bsp"
mapname.wad
file in the same directory.ripent.exe -textureimport "C:\where\is\your\valve\maps\mapname.bsp"
mapname.wad
file in the same directory, overwriting the embedded textures in the specified .bsp file.wadmaker.exe -remove "C:\where\is\your\valve\maps\mapname.bsp"
CheckTimeBasedDamage
seems like a reasonable spot), so it'll always work no matter which level you move to, and you'll avoid the rapid-health-reducing bug as well. This also means that mega-health items can delete themselves once they've been picked up, just like other items.{
"classname" "game_score"
"targetname" "strike_mm"
"points" "1"
}
{
"classname" "multi_manager"
"targetname" "strike_mm"
"usaflagdoc" "0"
"usaflagdoc#1" "45"
}
And this is how your sprite entity should look like:
{
"origin" "1 -949 -1440"
"scale" "1.0"
"model" "sprites/usaflagdoc.spr"
"rendercolor" "255 255 255"
"renderamt" "255"
"rendermode" "4"
"renderfx" "14"
"framerate" "20"
"classname" "env_sprite"
"targetname" "usaflagdoc"
}
"targetname" "usaflagdoc"
, then your multi_manager should contain "usaflagdoc" "0"
and "usaflagdoc#1" "45"
."classname" "env_glow"
to "classname" "env_sprite"
. An env_sprite with a targetname will initially be disabled, and it will become visible when triggered (and invisible when triggered again)."targetname" "strike_siren"
Or if you prefer the bunker door timing:
"targetname" "bunker_maindoor"
For custom timings, you could add a new multi_manager with the same name as the multi_manager that's handling the whole sequence, so they get activated at the same time:
{
"classname" "multi_manager"
"targetname" "strike_mm"
"your_sprite_targetname" "0"
"your_sprite_targetname#1" "10"
}
You can adjust the 0 and 10 to whatever times (in seconds) that you like. Just as with the siren or main door timings, this also requires your sprite to have a name, but now you should use a name that isn't used anywhere else, to ensure that your multi_manager will be the only entity that's triggering your sprite:
"targetname" "your_sprite_targetname"
flags
property to an entity and setting it to 33554432
(that'll set the 26th bit, which is the FL_WORLDBRUSH
flag).Can admin rename this thread to a more suitable name, so someone with the same problem can find this thread and script more easily?As you wish.
half_float(t)
before multiplying it with the texture height), and added a note about the minimum Python version. It turns out that formatted strings were introduced in Python 3.6. I haven't used Python much the past few years but I figured a Python script would be easier to share (and to analyze and modify!) than an executable. And it should run on almost any OS. input_path
and output_path
variables on lines 6 and 7 before you run the script):
import struct
def main():
# Change these paths depending on which Xash model you want to convert:
input_path = r'C:\your\models\folder\bpop2.mdl'
output_path = r'C:\your\models\folder\bpop2_converted.mdl'
# Read texture sizes:
print(f'Reading \'{input_path}\'.')
with open(input_path, 'rb') as file:
data = bytearray(file.read(-1))
texture_sizes = read_texture_sizes(data)
if len(texture_sizes) == 0:
# No texture information? Let's look for a *t.mdl file:
try:
texture_file_path = input_path[:-4] + 't.mdl'
print(f'Reading \'{texture_file_path}\'.')
with open(texture_file_path) as file:
texture_sizes = read_texture_sizes(file.read(-1))
except Exception as e:
print(f'Failed to read \'{texture_file_path}\', unable to obtain texture size information.')
raise e
print(f'Texture sizes: {texture_sizes}\n')
# Convert UV data from Xash' normalized half-float format to GoldSource's absolute int16 format:
converted_count = 0
print('Converting UV coordinates.')
bodypart_count, bodypart_offset = struct.unpack_from('<ii', data, 204)
for bodypart in range(bodypart_count):
model_count, model_offset = struct.unpack_from('<ixxxxi', data, bodypart_offset + (bodypart * 76) + 64)
for model in range(model_count):
mesh_count, mesh_offset = struct.unpack_from('<ii', data, model_offset + (model * 112) + 72)
for mesh in range(mesh_count):
vertex_offset, skin = struct.unpack_from('<ii', data, mesh_offset + (mesh * 20) + 4)
texture_size = texture_sizes[skin]
offset = vertex_offset
while True:
sequence_length = abs(struct.unpack_from('<h', data, offset)[0])
offset += 2
if sequence_length == 0:
break
for vertex in range(sequence_length):
s, t = struct.unpack_from('<HH', data, offset + 4)
struct.pack_into('<hh', data, offset + 4, round(half_float(s) * texture_size[0]), round((1 + half_float(t)) * texture_size[1]))
offset += 8
converted_count += 1
print(f'Converted {converted_count} UV coordinates.\n')
# Save the modified data to the output file:
print(f'Writing \'{output_path}\'.')
with open(output_path, 'wb') as file:
file.write(data)
print('Finished.')
#
def read_texture_sizes(data):
texture_count, texture_offset = struct.unpack_from('<ii', data, 180)
return [struct.unpack_from('<ii', data, texture_offset + (i * 80) + 68) for i in range(texture_count)]
#
def half_float(value):
isPositive = (value & 0x8000) == 0
exponent = (value & 0x7C00) >> 10
fraction = value & 0x03FF
if exponent == 0:
if fraction == 0:
return 0.0
else:
return (1 if isPositive else -1) * pow(2, -14) * (fraction / 1024.0)
elif exponent == 31:
if fraction == 0:
return float('inf') if isPositive else float('-inf')
else:
return float('nan')
else:
return (1 if isPositive else -1) * pow(2, exponent - 15) * (1.0 + (fraction / 1024.0))
#
if __name__ == '__main__':
main()
EDIT: Fixed the UV offset issue.For example, how to interpret 32.03125 ? It's not in the 0.0-1.0 range.That probably means I guessed the wrong texture size. Or there's something else going on. Perhaps you could share this model so I can do some proper testing?
28.638672 (U) x 512 (texture width) = 14663.000064 -> 14663 (raw model U value) -> 0.6601563 (when interpreted as a half-float)
40.001953 (V) x 512 (texture height) = 20480.999936 -> 20481 (raw model V value) -> 32.03125 (when interpreted as a half-float)
def interpret_as_half_float(value):
isPositive = (value & 0x8000) == 0
exponent = (value & 0x7C00) >> 10
fraction = value & 0x03FF
if exponent == 0:
if fraction == 0:
return 0.0
else:
return (1 if isPositive else -1) * pow(2, -14) * (fraction / 1024.0)
elif exponent == 31:
if fraction == 0:
return float('inf')
else:
return float('nan')
else:
return (1 if isPositive else -1) * pow(2, exponent - 15) * (1.0 + (fraction / 1024.0))
Disclaimer: I haven't tested this with an actual model, so I could be getting things wrong.macro_remove_ifSo yeah, you put it around brushes and entities just like you do with
Used inside templates. When an instance of a template is created, anything inside the bounding box of this entity is excluded from that instance if the removal condition is true.
Attributes
- Removal condition (condition) - The condition that determines whether the contents of this entity must be excluded.
none
(empty) and0
will prevent removal.
macro_template
. The condition attribute behaves the same as other attributes, so you'll need braces if you want to use scripting. Even though that's almost always what you want, I decided that consistent attribute behavior was more important than shaving off a few braces. Both macro_template
and macro_remove_if
only apply to things that are fully inside their bounding box.macro_remove_if
, take a look at examples\templates\rathunt\message_system.rmf
. It's a template map that omits certain entities based on the attributes of the instance-creating entity, and it also uses a macro_remove_if
to limit the recursion of a sub-template.wadmaker.config
file in your source image folder and adding a line like the following (where {input}
is the full input .xcf path, and {output}
is a .png file in a temporary folder):
*.xcf converter: '"C:\Tools\XcfConverter.exe"' arguments: '-in="{input}" -out="{output}"'
The above is just an example. Once I'm done with SpriteMaker I'll update the documentation with .xcf and .aseprite file conversion examples.always thought "that's way too much for me" (I was mostly used to CS mapping).I also started out as a mapper, years ago. I eventually started experimenting with the game code and I somehow managed to make the MP5 launch a grenade with each shot, but I couldn't really do much else. Now, years later, I do programming for a living... So yeah, give it a go, and don't get discouraged if things seem complicated at first. Feel free to ask for help whenever you're stuck.
Half-Life\yourmoddirectory\maps
directory (where yourmoddirectory
is the name of your mod's directory)? And are you starting hl.exe
with the -game yourmoddirectory
argument (you can also start your mod through Steam, which does the same thing)? If you're not doing that, then you're running the base game Half-Life (not your mod), which will only look for bsp files in the Half-Life\valve\maps
directory.func_door_rotating
's don't have origin brushes. I replied to your question about that, but what I didn't mention explicitly is that an origin brush must be part of an entity. Normally you'd create both the visible brushes and the origin brush, select all of them, and then turn them into an entity. You already have a func_door_rotating
entity, so in this case you'd create an origin brush, then select both the func_door_rotating
and the origin brush, then press the 'To Entity' button (Ctrl+T), and select 'Yes' when it asks you to add the selected solids to the existing entity.map roomone
in the console?