Left 4 Dead 2

Left 4 Dead 2

Not enough ratings
How to Randomize Infected Models in a Custom Campaign
By AlwaysToast
This guide explains how to randomize between multiple models for special infected on in a custom campaign.
   
Award
Favorite
Favorited
Unfavorite
Introduction
You can make infected (meaning Special Infected, Tanks, and Witches) randomize their models within a custom campaign. If the models have different skins, then the skin changes to match the model. But it's possible to change between totally different models.

Common infected are already randomized and controlled by the infected population system. So these system are not for common infected.

Note in order to randomize the skins in this manner, you actually need to randomize the models, with each model having a unique name, and its own skin. My model replacements all use the default model, but the model has been renamed and assigned a custom skin. So for the rest of this guide if I say “skin” i really mean a new model with a different skin.

In this guide, when I say “Infected” I only mean Special Infected: Boomers, Chargers, Hunters, Jockeys, Smokers, Spitters, Tanks and Witches.
Cache/Precache
Please be aware, you need to make all related models and skins load. Which uses up game resources. If you load more than 1 model for each infected, there will be less resources available for mods (meaning people using lots of mods will probably crash playing your campaign). When the game engine runs out of a resource, the game engine frequently just crashes to desktop.

It is best if you can limit how many infected actually have randomized skins.
Having 1 extra skin isn’t a big deal. People can probably play with a bunch of mods.
Having 16 extra skins is workable, but running mods might cause a crash.
Having 24 extra skins is a bit too much. This is where I started having crashes without any mods running.

So try to be conservative with how much stuff you randomize. You will be limiting player's to playing without mods if they don't want to crash.

Also note: Different mods use different amounts of resources. Model replacement mods (especially if they use more polygons in the base models) are going to have serious problems. Other mods may have no problems at all (because they don't need lots of extra resources).

How to Set this up
The first step is to make sure all the files cache or pre-cache so the game doesn’t crash or lag when you try to load a new model. I’m not an expert at this stuff, but I can get it to work.

The default models for each infected will load by default if you don’t do anything with the mission file. If you use the mission file to load a different infected model with a Variant option, then that model will load, but the default model will not. Any variant model listed in the mission file should cache when the map is loaded. They do not need to be added to a pre-cache list. You can also just replace models without randomizing them with additional models.

If you place a prop_dynamic with an infected model you want to use inside the map. That model will load when the map loads (hence it will be cached, and does not need to be in a pre-cache script). Generally you just want to build a little cube off the map somewhere to hold your prop_dyanmic for extra skin(s) (this is from the explanation of how to get the witch-bride to spawn). This is most useful when using Mr.Funreal's method of randomizing specific infected spawned at specific locations. If you are not using that method, I'd suggest using a pre-cache script instead of using entity data on models.

For any additional models you want to use, you need to pre-cache them in some way.
If you set up a point entity logic_auto to run a script, you can use that script to pre-cache the models you need.

The pre-cache script will look something like
function precacheExample1Models(){ local models_to_precache = [ "models/infected/at_tank01/at_tank01.mdl" "models/infected/at_tank02/at_tank02.mdl" "models/infected/at_witch_z01/at_witch_z01.mdl" "models/infected/at_witch_z02/at_witch_z02.mdl" "models/infected/at_charger08/at_charger08.mdl" "models/infected/at_hunter03/at_hunter03.mdl" "models/infected/at_jockey01/at_jockey01.mdl" "models/infected/at_smoker02/at_smoker02.mdl" "models/infected/at_spitter05/at_spitter05.mdl" ] foreach(model in models_to_precache) { if(!IsModelPrecached(model)) { PrecacheModel(model); } } } precacheExample1Models(); // this causes the function above to run. if ( !IsModelPrecached( "models/infected/at_tank01/at_tank01.mdl" ) ) PrecacheModel( "models/infected/at_tank01/at_tank01.mdl" ); if ( !IsModelPrecached( "models/infected/at_tank02/at_tank02.mdl" ) ) PrecacheModel( "models/infected/at_tank02/at_tank02.mdl" ); if ( !IsModelPrecached( "models/infected/at_witch_z01/at_witch_z01.mdl" ) ) PrecacheModel( "models/infected/at_witch_z01/at_witch_z01.mdl" ); if ( !IsModelPrecached( "models/infected/at_witch_z02/at_witch_z02.mdl" ) ) PrecacheModel( "models/infected/at_witch_z02/at_witch_z02.mdl" ); if ( !IsModelPrecached( "models/infected/at_charger08/at_charger08.mdl" ) ) PrecacheModel( "models/infected/at_charger08/at_charger08.mdl" ); if ( !IsModelPrecached( "models/infected/at_hunter03/at_hunter03.mdl" ) ) PrecacheModel( "models/infected/at_hunter03/at_hunter03.mdl" ); if ( !IsModelPrecached( "models/infected/at_jockey01/at_jockey01.mdl" ) ) PrecacheModel( "models/infected/at_jockey01/at_jockey01.mdl" ); if ( !IsModelPrecached( "models/infected/at_smoker02/at_smoker02.mdl" ) ) PrecacheModel( "models/infected/at_smoker02/at_smoker02.mdl" ); if ( !IsModelPrecached( "models/infected/at_spitter05/at_spitter05.mdl" ) ) PrecacheModel( "models/infected/at_spitter05/at_spitter05.mdl" );
Note this is just how I did it, and I am not an expert on pre-cache scripts. There may be significantly better ways to do it. You would obviously replace the file names for each model with the ones you want to use.

This is also 9 extra models being pre-cached. This is for example purposes.
System 1: Randomizing on Spawn
You can cause special infected models to randomize when they spawn. You need a piece of code running to detect when an infected of the type you want to randomize spawns, and then to randomize that skin. Note Witches are special (they can’t be players), so they need their own unique code. All other infected can be grouped together.

I absolutely did not come up with this code myself. You can see the origin of the code in this forum discussion. By Nescius with optimization by Everice. I did however setup this layout so it can be used for everything. I also worked out the witch script myself once I had the example for the other infected.

Note this will detect when a given type of infected spawns, and automatically randomize the model (each model should therefore have a different skin). When the infected dies (for infected except the boomer) the corpse of the infected will have the same model for the ragdoll at death.

I suggest not using randomization of skins in Versus (or any mode where players actually play infected). Primarily because you can randomize the skin of the infected, but I don’t know how to randomize the arm model of the infected to match. The exception to this is witches, because players don’t play as the witch normally, so the witch can be randomized in Versus.
Witch Randomization
function OnGameEvent_witch_spawn(params) { local witchz = EntIndexToHScript(params["witchid"]); m7_arr <- [ //list all of your model in strings here "models/infected/at_skin01witch01/at_skin01witch01.mdl", "models/infected/at_skin01witch02/at_skin01witch02.mdl" ] witchz.SetModel(m7_arr[RandomInt(0, m7_arr.len() - 1)]); }
Why m7_arr? Because when infected are identified by numbers, witches are number 7. So I set the array number to the number each infected uses. And I wasn’t sure if all the arrays had the same name if they would mess with each other.

To customize this for yourself, you only need to change the models listed inside the array to match the models you want to spawn.

You can put any number of skins into the array. You can list the same skin multiple times if you want that skin to show up more frequently.
All Non-witches Randomized Together
This is an example of what it would look like to randomize every special infected. I don’t suggest you ever do this. But it’s easy to see what the entire code looks like. And you can just cut out the parts you don’t need.
Msg("Initiating Infected Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 8) // tank { m_arr <- [ //list all of your model in strings here "models/infected/at_tank16/at_tank16.mdl", "models/infected/at_tank19/at_tank19.mdl", "models/infected/hulk.mdl" ] player.SetModel(m_arr[RandomInt(0, m_arr.len() - 1)]); } if(player.GetZombieType() == 6) // charger { m6_arr <- [ //list all of your model in strings here "models/infected/at_charger01/at_charger01.mdl", "models/infected/at_charger02/at_charger02.mdl" ] player.SetModel(m6_arr[RandomInt(0, m6_arr.len() - 1)]); } if(player.GetZombieType() == 3) // hunter { m3_arr <- [ //list all of your model in strings here "models/infected/at_hunter01/at_hunter01.mdl", "models/infected/at_hunter05/at_hunter05.mdl" ] player.SetModel(m3_arr[RandomInt(0, m3_arr.len() - 1)]); } if(player.GetZombieType() == 5) // jockey { m5_arr <- [ //list all of your model in strings here "models/infected/at_jockey_da01/at_jockey_da01.mdl", "models/infected/at_jockey_da02/at_jockey_da02.mdl" ] player.SetModel(m5_arr[RandomInt(0, m5_arr.len() - 1)]); } if(player.GetZombieType() == 1) // smoker { m1_arr <- [ //list all of your model in strings here "models/infected/at_smoker_da03/at_smoker_da03.mdl", "models/infected/at_smoker_da04/at_smoker_da04.mdl" ] player.SetModel(m1_arr[RandomInt(0, m1_arr.len() - 1)]); } if(player.GetZombieType() == 4) // spitter { m4_arr <- [ //list all of your model in strings here "models/infected/at_spitter01/at_spitter01.mdl", "models/infected/at_spitter02/at_spitter02.mdl" ] player.SetModel(m4_arr[RandomInt(0, m4_arr.len() - 1)]); } }
You can put any number of skins into each array. You can list the same skin multiple times if you want that skin to show up more frequently.

To customize this for yourself, you can change any of the models listed inside any array to match the models you want to spawn. You can also remove entire arrays to only randomize specific special infected.
Example: Tank & Hunter Randomized
This is the same as the above, but I removed all the specials except for the Tank and Hunter.

Msg("Initiating Tank Hunter Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 8) // tank { m_arr <- [ //list all of your model in strings here "models/infected/at_tank16/at_tank16.mdl", "models/infected/at_tank19/at_tank19.mdl", "models/infected/hulk.mdl" ] player.SetModel(m_arr[RandomInt(0, m_arr.len() - 1)]); } if(player.GetZombieType() == 3) // hunter { m3_arr <- [ //list all of your model in strings here "models/infected/at_hunter01/at_hunter01.mdl", "models/infected/at_hunter05/at_hunter05.mdl" ] player.SetModel(m3_arr[RandomInt(0, m3_arr.len() - 1)]); } }
Randomizing Tanks on Spawn
Msg("Initiating Tank Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 8) // tank { m_arr <- [ //list all of your model in strings here "models/infected/at_tank01/at_tank01.mdl", "models/infected/at_tank02/at_tank02.mdl", "models/infected/hulk.mdl" ] player.SetModel(m_arr[RandomInt(0, m_arr.len() - 1)]); } }
Randomizing Boomers on Spawn
Please note changing a boomer model does not change the legs that appear after the boomer explodes. So I generally don’t randomize boomers or boomlettes, as I haven’t figured out how to make the legs that appear after it explodes change to match.
Msg("Initiating Boomer Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 1) // Boomer { m4_arr <- [ //list all of your model in strings here "models/infected/at_boomer01/at_boomer01.mdl", "models/infected/at_boomer02/at_boomer02.mdl" ] player.SetModel(m4_arr[RandomInt(0, m4_arr.len() - 1)]); } }
Randomizing Chargers on Spawn
Msg("Initiating Charger Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 6) // charger { m6_arr <- [ //list all of your model in strings here "models/infected/at_charger01/at_charger01.mdl", "models/infected/at_charger02/at_charger02.mdl" ] player.SetModel(m6_arr[RandomInt(0, m6_arr.len() - 1)]); } }
Randomizing Hunters on Spawn
Msg("Initiating Hunter Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 3) // hunter { m3_arr <- [ //list all of your model in strings here "models/infected/at_hunter01/at_hunter01.mdl", "models/infected/at_hunter05/at_hunter05.mdl" ] player.SetModel(m3_arr[RandomInt(0, m3_arr.len() - 1)]); } }
Randomizing Jockeys on Spawn
Msg("Initiating Jockey Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 5) // jockey { m5_arr <- [ //list all of your model in strings here "models/infected/at_jockey_da01/at_jockey_da01.mdl", "models/infected/at_jockey_da02/at_jockey_da02.mdl" ] player.SetModel(m5_arr[RandomInt(0, m5_arr.len() - 1)]); } }
Randomizing Smokers on Spawn
Msg("Initiating Smoker Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 1) // smoker { m1_arr <- [ //list all of your model in strings here "models/infected/at_smoker_da03/at_smoker_da03.mdl", "models/infected/at_smoker_da04/at_smoker_da04.mdl" ] player.SetModel(m1_arr[RandomInt(0, m1_arr.len() - 1)]); } }
Randomizing Spitters on Spawn
Msg("Initiating Spitter Randomization script \n"); function OnGameEvent_player_spawn(p) { local player = GetPlayerFromUserID(p.userid); if(player.GetZombieType() == 4) // spitter { m4_arr <- [ //list all of your model in strings here "models/infected/at_spitter01/at_spitter01.mdl", "models/infected/at_spitter02/at_spitter02.mdl" ] player.SetModel(m4_arr[RandomInt(0, m4_arr.len() - 1)]); } }
If it doesn't run
Depending on how you are implementing it, you may also need this at the end of your script.
__CollectEventCallbacks(this, "OnGameEvent_", "GameEventCallbacks", RegisterScriptGameEventListener);
Ways to Start the on Spawn Randomization
You could run a basic script with a logic auto. If the map runs in both coop and versus (or any other pvp mode) you may need to set up the script so it doesn’t run in those modes.
if ( Director.GetGameModeBase() == "versus" ) { Msg("Initiating Versus no model randomization \n"); } else { // your randomizer here } __CollectEventCallbacks(this, "OnGameEvent_", "GameEventCallbacks", RegisterScriptGameEventListener);

You can run a basic Script off the info_game_mode OnCoop.

You can run a script off of a trigger (like a trigger_once or a button press). This works best if you only want randomization to occur after a certain point in the map. Note the randomization could just be one skin, so you could have one skin for the first half of the map, then after a certain point, all of a given infected would spawn with a different skin. This may also need the script above to not turn on during versus if your map also runs in versus.

I don’t know of a way to turn randomizing off.
System2: Specific ForceSpawned Infected Skin Replacement
If you ForceSpawn an infected at a specific location on a map using a commentary_zombie_spawner you can change it to a specific skin after it spawns. This is ideal if you want a tank or witch to have a specific skin at a specific location. This will also override other randomization that occur when the infected spawns, because it occurs after an infected spawns. So you can use the on-spawn randomization for all tanks, but then force a tank to spawn at a specific location with a totally different model.

This system is based on MrFunreal's Guide, so I suggest you just read the original.
https://steamhost.cn/steamcommunity_com/sharedfiles/filedetails/?id=2265618734

I will expand upon a few things.
MrFunreal’s system can be activated entirely within a single script, so long as the appropriate entities (commentary_zombie_spawner, and a prop_dyanmic with the desired model) are in the map.

Example_tank1
function ConvertTank_t1(){ local Tank01 = Ent("cache_tank1") local targetTank_n01 = Ent("convert_t1") NetProps.SetPropInt(targetTank_n01, "m_nModelIndex", NetProps.GetPropInt(Tank01, "m_nModelIndex")) } EntFire( "t1", "SpawnZombie", "Tank,convert_t1", 0 ) // this spawns the tank and gives it the name convert_t1 EntFire("!self", "CallScriptFunction", "ConvertTank_t1", 0.3); //This triggers the function above after the tank has spawned.
The name of the prop_dynamic in the level which is the tank model that is desired to be used is named cache_tank1. It could be any name you want, this is just an example.
The name of the commentary_zombie_spawner is t1.

So you can send the director a command of RunScriptFile, Example_tank1, and it would spawn the tank and change the model.

You can change the names of the tank, commentary_zombie_spawner, and prop_dynamic if you change the script names to match.

You can further complicate this by randomizing which model you want to select.
function ConvertTank_t1a(){ Tank_t1Case1 <- RandomInt(1,2); if ( Tank_t1Case1== 1 ) { local Tank01 = Ent("cache_t1") local targetTank_n01 = Ent("convert_t1") NetProps.SetPropInt(targetTank_n01, "m_nModelIndex", NetProps.GetPropInt(Tank04, "m_nModelIndex")) } if ( Tank_t1Case1 == 2 ) { local Tank01 = Ent("cache_t2") local targetTank_n01 = Ent("convert_t1") NetProps.SetPropInt(targetTank_n01, "m_nModelIndex", NetProps.GetPropInt(Tank01, "m_nModelIndex")) } }
The first one switches it to the prop_dynamic cache_t1, and the second one switches it to another prop_dynamic cache_t2.