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?┌──────┐ ┌──────┐ ┌──┬───┐ ┌──────┐ ┌──┬───┐
└──┐ │ splits into: └──┬───┤ or └──┤ │ or └──┬───┤ or └──┤ │
┌──┘ │ ┌──┴───┤ ┌──┴───┤ ┌──┤ │ ┌──┤ │
└──────┘ └──────┘ └──────┘ └──┴───┘ └──┴───┘
As shown above, there are multiple ways in which this corridor can be split up into vis nodes. In the first 3 cases, every node is visible from any other node. But in the last case, the top-left node is not visible from the bottom-left node, and vice versa. With a hint brush, you can force the compile tools to produce that particular space partitioning.return DefaultDeploy...
), so the second line (EMIT_SOUND(...
) is never executed.-1.0f
.FireBulletsPlayer
is a CBaseEntity
member function, so it doesn't know about CBasePlayer
fields like m_flDoubleDamageBonusTime
(it can be called on any entity, after all). One way to work around that is by creating a virtual float GetDamageBonus()
function in CBaseEntity
that returns 1.0f
, but which you override in CBasePlayer
to return 2.0f
if HasDoubleDamageBonus()
is true (you can look at the CBaseEntity::IsPlayer
and CBasePlayer::IsPlayer
functions for an example of how a virtual function can be overridden in a child class). You can then use that function inside FireBulletsPlayer
as following:pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg9MM * GetDamageBonus(), vecDir, &tr, DMG_BULLET);
TraceAttack
directly, and since weapons contain a pointer to the player (CBasePlayer* m_pPlayer
), you should be able to obtain the damage factor for them with m_pPlayer->GetDamageBonus()
. For explosives I think you'll need to look at the RadiusDamage
function, probably using CBaseEntity::Instance(ENT(pevInflictor))->GetDamageBonus()
to get the damage bonus, if I understand how that works correctly.g_iSkillLevel
, you'll see that the CGameRules::RefreshSkillData
function in gamerules.cpp
forces the skill-level between 1 and 3 before it looks up skill-specific health and damage values. But there are also a few places throughout the game-code where the skill-level is checked specifically, so it's a bit more complicated than just increasing the maximum skill-level value.intromm
. When you look at the properties of that entity (press the SmartEdit button so you can see the raw keyvalues) then you'll see that it triggers multiple other entities, each at a specific time:
targetname intromm -- the name of this multi_manager
intro .5 -- someone at Valve forgot to remove this, because there's no entity with the name 'intro'
td1 15 -- the door to the HEV suit room
td1l 14.5 -- the light above the door to the HEV suit room
holo1light 0 -- the hologram light
sent_intro 0 -- the scripted_sentence entity for the hologram welcome speech (sentence: !HOLO_WELCOME)
sent_button 14.5 -- the scripted_sentence for the hologram speech about buttons (sentence: !HOLO_BUTTON)
butlight 15.1 -- the light above the button in front of the hologram
butsprite 15.2 -- the twinkling sprite that shows up above the button
J.A.C.K. can display arrows between entities that trigger each other, but unfortunately that doesn't work for multi_managers. Fortunately J.A.C.K. does have a way to search for entities with specific names: in the menu, go to Map -> Entity report, then in the Filter area of that window, enable 'by key/value', enter 'targetname' as key and the name of the entity you're looking for as value.{
"origin" "-1328 -1484 -4"
"butsprite" "15.2"
"butlight" "15.1"
"sent_button" "14.5"
"sent_intro" "0"
"holo1light" "0"
"td1l" "14.5"
"td1" "15"
"intro" ".5"
"targetname" "intromm"
"classname" "multi_manager"
}
The key/value order is different, but that doesn't matter. Now change the 14/15 second delays to something suitable and save the map. Just be sure not to remove or add any double-quotes, otherwise Half-Life won't be able to read these entities anymore.