This tutorial is meant for people who have a lot of expirience mapping for Half-Life and at least some expirience with SoHL.
I Allways use SoHL Version 1.54. The entities discribed should be similar in other versions but I can not guarantee that they will work.
I want to tell you about the motion entities. Those entities that make the stuff in your map move. They are complicated but once you master them allmost anything you can think of is possible in half life. People have made gravity-guns with SoHL. At the end I will also give you three examples of how you can use these entities and you can try them out yourself.
The tutorial starts here
movewith calc- and motion- entities
Movewith is one of the most famous features of SoHL. However, once you start working with it you will find out that its possibilites are very limited. What movewith does is copy the movement of one entity and apply that movement to another.
You have a func_train and some func_breakable that moves with the train? If the train starts moving the breakable will move into the same direction. However movewith does not work correctly with rotating objects. If you have a func_tracktrain that takes a turn left, the window will follow the train but not turn accordingly. If the window was at the frontpart of the train in the beginning, now after the turn it is on the left.
Movewith only works with entities that move in a predetermined way; like a train, a door or an elevator. Movewith does NOT work with monsters, pushables or other entities that move "freely".
Movewith has a bug. When the moving entity is being blocked(by a player or a monster...etc.) and comes to a stop. The entities that movewith will keep going and the whole thing will get out of sync.
The movewith function is good for simple movement like:
- An elevator
- A car that follows a straight road
- A door
When using movewith make sure the moving entity wont get stuck:
- Make sure no monsters or player get in the way or
- Make it crush everything that comes in its way or
- Make it not solid
For any kind of complex movement with rotation, with dynamic not predetermined moving you should use the calc entities.
The calc entities:
This entity lets you determine the position of another entity.
Lets say you want a sprite to stick to a monster_human_grunt. Give the grunt a name and then enter its name in the field "Entity to use[LE]" in the calc_position.
You can choose where exactly on that grunt you want the sprite to be. Look at the "Position to calculate" field. Most options like "Eyes" or "Centre" are self explanatory.
Attachment points are special points in models. Not every model has these. But the human_grunt has an attachment point at its hand. So you can place a sprite(or anything you want) in the grunts hand. Use a model-viewer to find out if and where a model has attachment points.
The option "Random" chooses a random point somewhere inside your grunt. This random position changes all the time. After a second the random position wont be same as it was a second earlier.
Use an env_shooter to spawn firesprites at a grunts random position and it will look like the grunt is on fire!
The last field "Add offset[LV]" lets you alter the determined position. Lets say you choose the eyes position and an offset of 0 0 50. Now your position will be 50 units ABOVE the eyes. If you move your sprite to this calc_positions position it will be 50 units above the grunts eyes.
In the "Add offset[LV]" field you can also enter the name of another calc_entity...to make things more complicated.
The calc_position works not only with monsters but with ALL point- and brush-based entities. Of course not all entities have eyes...
Of course the calc_entity only determines a position. To actually move your sprite to that position you will need a motion entity, which I will explain later.
This entity lets you determine the facing or the movement of other entities. In the field "Entity to use[LE]" enter the name of the entity you want to observe.
"Value to calculate from" has several options:
- Movement velocity: Find out how fast and in what direction another entity is moving. If a scientist is running east with a speed of 50 units/second this vector will be 50 0 0(or something like that. I allway get confused with the axis). If the scientist ist running uphill with the same speed the vector might be something like 50 0 10. The 10 represents the upward movement.
- View angle: Find out what direction the player is looking. Enter *player in the "Entity to use[LE]" field. You might use this to make a laserpoint for the player. Create a env_laser and in the "Fire towards" field enter the name of a calc_subvelocity that checks the players view angle. That laser will now aim in the same direction as the player. You still have to make some adjustments to make it look correct and actually move with the player. After reading all my tutorials about calc and motion entites you should know how.
- Angle: The same but for all other entities. Find out what direction a tracktrain, a func_tank or a monster is facing("looking").
- Attachment points: Im not sure what these do with a subvelocity. If you know, please let me know.
The value calculated by the calc_subvelocity is allways a vector. You can alter it with the fields "Scale factor[LR] "(multiply) and "Add offset[LV]". In the flags you can tick "Discard X", "Discard Y" and "Discard Z" which will nullify these axis in the vector. "Flip vertical" makes -Z out of Z.
The flag "Normalize" makes the vectors lenth equal 1 but wont change its direction.
(The gun in this picture is supposed to be a func_tank, but it could be any other entity)
Just experiment with these settings you will see how they work.
The calc_velocity_path lets you find out how far it is from point A to point B. Example: You want to know how far away and which direction an enemy is from the player? Enter *player in the "Start position[LP]" field and the monster´s name in the "Destination" field. The calc_velocity_path now creates a vector that is as long as the distance between player and monster, and it points in the direction the player has to go to find that monster.
Again you can make some math alterations to that vector. In "Length Calculation" you can choose some formulas like "Square (X=X*X)", meaning as the distance between monster and player grows the vector will not grow linear but exponentially. Those are all self explaining. "Length factor [LR]" lets you scale the vector by any factor you want.
"Line is blocked by" Here you can choose whether your vector will pass through all obstacles like a ghost(default) or not.
calc_ratio and calc_velocity_polar
I dont know enough about these entities because I hardly ever use them. I cant say much about them.
calc_ratio lets you check values like a monsters health or a watchers count and then scale sprites, sound volumes or other stuff acordingly. Just look at the Attributes, most is self explaining.
calc_velocity_polar I think this lets you rotate an existing vector. Lets say you use a calc_subvelocity(see above) to find out what direction the player is looking. Now enter the subvelocities name at the "Based on velocity[LV]" field of the calc_velocity_polar. Enter 0 45 0 at the "Rotated by angle Y Z X" field.
When the player looks north the calc_subvelocity will point north too. The calc_velocity_polar will rotate this vector by 45 degrees and thus point northeast. I did not test this but it should work.
If you know more about these two entities let me know!
All calc_entities just get values. Motion entities use these values.
The motion_manager when turned on will move an entity the way you want it.
- "Target to affect[LE]" Here you enter the name of the entity you want to move.
- "Position(blank = no change)" Here you can enter something like 30 -4 0 (meaning 30 units to the east and 4 units to the south) or the name of a calc-entity.
"Meaning of position" Here you decide how your value will affect the target. Lets asume that in the "Target to effect[LE]" field you entered the name of a func_pushable and in the "Position" field 30 0 -4.
- "Set Position[LP]" Your func_pushable will be teleported to the coordinates 30 -4 0. If there is a wall at that position the pushable will be teleported into the wall. If there is void at that position the pushable will be teleported into the void. "Set Position[LP]" doesnt care if it is realistic or not. Even if 30 -4 0 is a good position(no wall, just plenty of air) the pushable will still be stuck in this position. If you try to push or pull it somewhere else it will instantaniously be teleported back to 30 -4 0. You have to turn the motion_manager off before the func_pushable will be free again. All entities can be moved using this setting.
- "Offset Position[LV]" As long as the motion_manager is on the func_pushable will be moved 30 units east and 4 units south EVERY SECOND. If your pushable starts at 0 0 0 after one second it will be at 30 -4 0. After two seconds at 60 -8 0. Just like "Set Position[LP]" this setting doesnt care about realism. Your pushable will pass through all obstacles and it cant be pushed or pulled as long as the motion_manager is on. All entities can be moved using this setting.
- "Set velocity[LV]" This setting will give the func_pushable speed. When the player pushes a pushable it gains some speed. After a while the friction will slow it down until it comes to a stop. "Set velociy[LV]" will fake that the pushable is being pushed. It will follow realistic rules. If a wall is in the way the pushable will stop. This setting does set the pushables speed to 30 -4 0 as long as it is turned on. Think of the consequences. When you push this pushable over the edge of a cliff will it fall down? No! Because normaly when an object falls down it gets accelerated by gravity. That means its downward-speed gets greater and greater. When the motion_manager is on it will set the pushables downward speed to 0 all the time. That means it wont fall down. Only entities that move "freely" can be moved with this setting. See below.
- "Accelerate by[LV]" Similar to "Set velocity[LV]". But this setting will ADD to the targets speed instead of SETTING it. That means the pushable will move 30 units faster to the east and 4 units faster to the south every second. It might get really fast but it will still follow realistic rules like clipping and gravity. If you give your pushable an upward acceleration it might start to fly. Note that most entities like pushables and monsters have a friction. That means a minimum acceleration might be needed to move them. If your acceleration is too small they wont move at all. Only entities that move "freely" can be moved with this setting. See below.
- "Follow Position[LV]" The name says it all. Your pushable will try to get to 30 -4 0. It will start moving by itself but it will follow realistic rules. If your pushable is at 30 -4 0 nothing will happen. If the player pushes it away it will return to 30 0 -4 as if it was magneticly attracted. Only entities that move "freely" can be moved with this setting. See below.
What does moving "freely" mean? It is a little difficult to define what this means. But think of the trigger_push. The trigger_push is usually used to create "wind". Entities that move "freely" will be pushed by this wind. Monsters? Yes. Players? Yes. Grenades? Yes. Pushables, gibs, healthkits? Yes. Yes and Yes. But func_walls? No. func_doors? No. Just remember if your entity can me moved by a trigger_push and you will know which setting to use in the motion_manager.
Back to the motion_manager´s options:
- "Axes to Modify" Should be selfexplanatory. An example: You want to change an entities horizontal position but dont care about or dont want to change its height. Then select "Not Z(XY only)".
- "Facing(blank = no change)" Again you can enter a value or the name of a calc_entitiy. Use this to turn an entity.
- "Meaning of Facing" Again a bunch of option to choose. All selfexplanatory. Just like the position-thing you can set, add or accelerate facing.
- "Axes to Modify" Same as the position thingy.
In the flags you can select:
- "Debug" Very usefull when your motion_manager doesnt do what you want it to. Type "developer 2" in console and the motion_manager will tell you what it does.
- "swap axes" Use these if you aren´t confused but want to be. Just use "trial and error" and they might eventually do what you want them to do.
So much for the motion_manager. Remember that instead of 30 -4 0 you can use any other set of numbers or, even better, a calc entity to make things really dynamic and cool. Remember that sprite that we wanted to move with the grunts eyes in the calc_position tutorial? Use the motion_manager and you will be able to do that.
When you give a motion_manager a name it will be OFF by default. Remember to trigger it when you start your map.
The trigger_motion works allmost the same. The only difference is that it wont update all the time. Trigger it once and it will change the targets position/facing once. You can use a looped multi_manager to trigger the trigger_motion every 0.1 seconds. Then it will act allmost like a motion manager.
motion_managers are good for steady continous movement like:
- A pushable being moved in one dircetion
- A monster being atracted by another entity
- A func_breakable following a train.
trigger_motions are good for onetime changes like:
- A pushable getting a "kick" into a certain direction
- A func_wall being teleported to some other place.
One last note: When using brush-based entities in SoHL its allways a good idea to give them an origin.
Three examples how to use calc- and motion-entities
An UFO flying above the players head
Make a func_wall that looks like a UFO. Give it an origin brush and a name. Place the UFO somewhere in your map. Where is not important but it should be high enough. Now use a calc_position to determine the players position. Give the calc_position a name. Now place a motion_manager in your map. Make it affect the UFO. In the "Position" field enter the name of the calc_position. Use "Set position[LP]" and in the "Axis to Modify" field use "Not Z". This is to prevent the UFO from changing its altitude. Give the motion_manager a name. Use a trigger_auto to turn on your motion_manager.
Get ingame: After the motion_manager has be activated your UFO should follow you everywhere. Remember your UFO does not change its height. That means if you climb high enough you might even touch it.
If you want to learn more try:
- using offsets in the calc_position
- making the UFO more than just a func_wall. Give it a weapon or something.
A laserpointer for a human grunt
This will give a monster_human grunt a cool laserpointer for his gun. How does this work? The laserpointer will be a env_laser. It will start at the tip of the grunts gun and will point into the direction the gun is pointed.
Place a monster_human_grunt in your map and give it a name. Now we must find out where the tip of his gun is. The human_grunt model has an attachment point at the tip of the gun. Use a calc_position. The "Entity to use" is your grunt and the "Position to calculate" is "Attachment point 0". You now know where you laser has to start.
What direction does the gun point? The grunt has another attachment point at his right hand. When you think of an arrow(or a vector) that starts at his hand and goes to the tip of the gun you know that this arrow will point in the direction he is aiming. Just think how you would hold a gun and how an arrow from your hand to the tip of the gun would look like.
Make another calc_position. This time use "Attachment point 1". Thats his hand.
Now to create the "arrow" we use a calc_veloctiy_path. In the "Start position" enter the name of your "hand" calc_position and in the "Destination" field enter you "guntip" calc_position.
You now have all the information your laserpointer needs. Create a env_beam. In the "Start At(blank = here)[LP]" field enter the name of your "guntip" calc_position and in the "Fire towards" field the name of your calc_velocity_path. In "Meaning of fire towards" choose "Direction [LV]". In the "Projection mode" choose "Extend past endpoint". That means your laser keep going until it hits a wall or something. If you dont choose "Extend past endpoint" your laser will only be as long as the distance between the hand and the guntip...and that means really short and totally not-laser-like.
Youre allmost done. Get ingame and try your laser. To be save make it be off be default and turn the laser on after the map has loaded. You will notice that the laser does not aim exactly right. It will probably go a bit too far up. This is because the hand of the grunt is a little lower than the guntip. You can fix this easily. Get back into worldcraft and change your "hand" calc_positions "Offset". Play around with it until your laser aims correct.
Now comes the fun part. You can design your laser as you want it. Red yellow or green? Really thin(like a laser) or frickin thick? Change the Render Fx to make it flicker or something. You can even give it some damage. Now your grunt can kill stuff by just aiming at it!
Important note: If this grunt dies and gets gibbed(grenade explosion) HL will crash. This will hopefully be fixed in one of the the next spirit versions. Until then make sure your grunt cant be gibbed...dont ask me how...you cant really stop a grunt from blowing himself up with a grenade...they do that alot actually...
If you want to learn more:
Go to the "Entity Guide" which comes with every version of SoHL and look up the env_beams "End sprite" Think how you can use this. Remember you can make stuff appear/happen at a sprites position now.
Picking up objects
Look at this video. Just like in HL² I can pick up items and carry them around.
Writing this tutorial I notice that this is pretty easy to map, but hard to explain. Just hang in there and try to follow what im writing. Ill make plenty of pictures at the end.
To understand this tutorial you should:
- Be expirienced with calc- and motion-entities
- Have good knowledge of mapping in general.
- Understand how the locus system works. There is a document about that in one of the older spirit versions.
This is what we need:
- The player must be able to choose what object to pick up, by looking at it.
- The player must decide to pick an object up by pressing a button.
- The object must then move with the player as if he was holding it.
First of all we need a point infront of the player. This is the position that carried objects will follow. Create a calc_position to get the players position. Add a calc_subvelocity to get the players viewangle. Enter the subvelocitys name at the "Offset" field of the calc_position. Now your calc_position is a point in front of the player. Use the "Scale Factor" field to determine how far from the player that point is. Enter a "Scale factor" of 50 and you will hold objects 50 units infront of you. Experiment with this value a little. If you choose a point to far from the player, it looks not realistic. If you choose point to close you might get problems picking up huge crates.
To "carry" objects around you will need a motion_manager. You can use it to make objects follow the point in front of the player. But how does the motion_manager know what object to affect/how does the player "choose" what to carry around?
The answer is a trigger_multiple. Create a trigger_multiple and give it an origin-brush. Use a motion_manager to set its position to that point in front of the player. This trigger_multiple will now allways stay in front of the player. An object inside this trigger will be selected as the one to be picked up. So by looking at an object from close distance you will "wrap" the trigger_multiple "around" that object. In the "Target" field of the trigger multiple enter the name of a motion_manager.
So if an object gets into your trigger_multiple it will trigger the motion_manager and thus be the locus. In the "Target to affect" field of the motionmanager enter *locus. Your motion_manager now knows what entity to affect.
There are different methods on how to "carry" an object but I prefer this one. Use a calc_velocity_path to get the distance from the locus(the object you carry) to the point in front of the player(reffer to the calc_position here). Make the "Length Calculation" "Square". Then have the motion_manager "Set velocity" of the object according to your calc_velocity_path. Meaning: If the object you carry is not where it should be(should in front of the player) it will get some speed so it gets there.
You can now pick up objects by looking at them. Looking at them? Thats not what you wanted, you wanted to press a button to pick them up.
Well its simple. So far I said enter the name of the motion_manager in the target field of the trigger_multiple. So of course as soon as an object is in the trigger_multiple it will get picked up. You can change this by leaving the target of the trigger_multiple blank. Now nothing happens when you look at an object. Add a trigger_changetarget. Make it so that this trigger_changetarget changes the target of the trigger_multiple to the motion_manager. When you trigger the trigger_changetarget your trigger_multiple will now target the motion_manager.
In spirit you can trigger entities by typing fire in the console. This only works when sv_cheats is 1. You can now bind a key so that it will trigger the trigger_changetarget. Type bind "k" "fire ".
Look at an object you want to pick up. Nothing happens because your trigger_multiple has no target. Press k and your trigger_multiple will get a target: the motion_manager.
You must add another trigger_changetarget that will set the trigger_multiples target back to blank shortly after the first trigger_changetarget. This is to make sure the pickup function works more than just once.
You can now pick up objects by looking at them and pressing k. Dropping items is very simple. Just deactivate the motion_manager and the object you were carrying will act normal again...fall down. Bind another key to deactivate the motion_manager. This will be your drop key.
If youre feeling fancy you can use a couple of trigger_relays and trigger_changetargets and make it so that you have one key for both picking up items and dropping them again. Im leaving you to figure it out by yourself. If you can´t do it just use two keys.
Is that all? No. In spirit things are never that easy. The problem is that a trigger_multiple only recognizes objects that move. A crate that just stands still wont be recognized and that means you wont be able to pick it up. Solution. Add a trigger_push with the same size as your trigger_multiple. Give it very little push speed and set its angle to "up". Trigger it when you want to pick up an item. The object you want to pick up will move allmost unnoticable but just enough for the trigger_multiple.
(Man! Huge vectors coming out of a guys pelvis! Scale Factors! I should sell penis enlargement products!)
So far I have allways talked about picking up "objects". But in HL there are no "objects" only entities. The question is what entities can be picked up?
Your trigger_multiple controls what entities can be picked up. Look at the flags first.
- "Monster": Do you want to be able to pick up monsters? If yes then activate this.
- "No clients": Picking up other players in multiplayer? Could be possible but: Nobody plays SoHL in multiplayer. And also there is a possibility that you might accidently try to pick up yourself. And that would be bad. Knowing spirit I say it would probably crash...Activate "No clients" if you want to be save.
- "Pushables" Definitely a must. Picking up pushables is cool. You can stack them to get over high obstacles or you can block an enemies path with them.
- "Everything else" When they say "Everything else" they mean Everything else. If you tick this flag you can pick up nearly every entity in game. Func_doors? Yes. The skull from a gibbed scientist? Yes. The func_water from a swimming pool? Yes. A hornet that an alien_grunt fired at you? Yes. You have to understand that this setting really refferes to all entities. BUT your motion_manager that you use to move the objects uses the "Set speed" setting. And if you recall my tutorial about motion_managers you will remember that only entities that move "freely" will be affected by this. That means your trigger_multiple will recognize func_doors. But the motion_manager cant move them because they dont move freely.
The trigger_multiple also has a "Triggered only by entity" field. Here you can enter a name. Now only entities with that name can be picked up. Enter something like "pickmeup" here and then name some entities "pickmeup". The player will only be able to grab these. This is good if you want the pick up option in your map, but dont want the player to become too powerfull.
That is all folks! If you have questions, write me! Also check out my Project Half-Life: Legion which is done entirely in SoHL!