• Please make sure you are familiar with the forum rules. You can find them here: https://forums.tripwireinteractive.com/index.php?threads/forum-rules.2334636/

Code A How to guide

Vertical Loop

Member
May 30, 2015
20
10
Edit 10/1/2020: Pictures are small/low res if you don't login

I noticed there are a lot of general guides on how to get started in the KF2 modding sphere, but a severe lack of anything that goes completely in depth
This has frustrated me to no end, as any problems I run by have to be solved by hand, wasting hours of my time generally on simple solutions

So, I wanted to just post this here to vent my frustration and maybe help someone else. I will be giving a complete guide on how I created a new elite husk that freezes the player, including the code and how to make a few of FX from scratch.

But before that, I want to address something that really threw me off when I first started. The amount of files there are. There's a lot to dig into, but luckily it can be pretty easy to discern what they do based on the prefix of the filename.
For example: KFPawn are pawn entities (players or ai), and usually handle interactions with other pawn entities and the environment. KFProj are projectile entities which handle things like the what type of model to use for a projectile, the damage type and explosion archetype used for that projectile. KFDT are damage types, which handle things like affliction (freeze or burn amount) and actual damage ranges. You get the idea. I'm not going to list every prefix, but the lesson is that you can generally tell what a class does by it's prefix, which leads to an easier time following what the inner parts of a class actually does.

I would also recommend getting Agent Ransack. It is an extremely effective way to search within files for specific character sequences (ie. looking for a variable name in another file). you can simply setup agent ransack in the SRC file and search all files for a function call or variable name within a couple seconds.

Assuming we have setup all our modding tools and have a basic setup for some mutator we begin with creating a new human pawn. Every pawn has a special moves, some moves are inherited from their parent classes (many of the zeds inherit things like stunned or frozen special moves from KFPawn_Monster), wheras other types are given to the specific class (Like how a husk has a StandAndFire special move). These moves generally are called when a specific requirement is met. To begin, we will need to setup a new KFPawn_Human within our mutator.

Code:
    local KFGameInfo KFGI;
    KFGI = KFGameInfo(WorldInfo.Game);
    KFGI.DefaultPawnClass=class'ihd_edit.KFPawn_MyNewHuman';

I did this within PostBeginPlay(). PostBeginPlay is a function that handles the game actors after they have been initialized. This will initialize the human actor as your new class that you will create.

Now we want to create a new file called KFPawn_MyNewHuman.uc. UC filetypes must have the same name as the class name, otherwise the compiler throws a fit, likely because it uses the name as a pseudo-pointer. Within our new human file we put


Code:
class KFPawn_MyNewHuman extends KFPawn_Human;

defaultproperties
{
    // set the incap settings otherwise the affliction manager will think the player is immune
    IncapSettings(AF_Freeze)=(Vulnerability=(4.f), Cooldown=10.0, Duration=2.0)
    //`log("default properties run")
    // add a new special move for being frozen
    Begin Object Name=SpecialMoveHandler_0
        SpecialMoveClasses(SM_Frozen)=class'ihd_edit.KFSM_HumanFreeze'
    End Object
}

Look at that, comments. While I'm on the topic, please for gods sake put comments in your code. I code for a living and man it helps other people look at it. Even with the UC scripting files where variable names are pretty much required to be very specific (otherwise you may reference a parent function or variable by accident), it's hard to discern why coders did some of the things they do, whether it be workarounds, or the ever dreaded "I don't get why this works, but it does, please don't remove it".

Back to our actual program. You may see IncapSettings and wonder "Why is that there?". IncapSettings is a function that handles vulnarability and cooldown to an affliction (mind you, not duration, but that variable can be called on). It will make more sense later, but I put this here now as I don't want to go back and forth between files. What really matters is the new object. Now I'm going to assume you know what an object is, but for those of you who don't know, the TLDR breakdown is that it is a inventory of features that make up some 'thing'. In our case, we are appending a new feature to the human, the fact that he can be frozen.

Now, if you are observant, you may have noticed the 'KFSM' class. That's the prefixes that I was talking about earlier. This one stands for 'Killing floor special moves', which usually means the class has special moves as it's parent. This one is a custom one that I will be going over in a bit, after I address SM_Frozen.

SM_Frozen is a part of a enum list, where enum stands for "Enumeration". Enumerations are basically a way of asscociating a value (0-n) with a another type. This is strictly for the coder, as it allows you to input something that is easier to recognize than some integer value for something like a list/array index. Where does this enum come from? I have honestly forgotten while writing this, but this is why we have Agent Ransack(yay!). The original definition comes from KFPawn, but most of the actual work comes from the affliction manager. When the pawns freeze limit is exceeded and that pawn can also "Do SM_Frozen" then it allows for the special move to be processed. Hey, that's the same place where IncapSettings is messed with! yessir, that's exactly why we call incapsettings, vulnerability acts as a multiplier for this 'FreezePower', but if we don't handle incapsettings at all, then the affliction manager will assume that the pawn is immune to this affliction type.

Now, moving on to my super secret special move, KFSM_HumanFreeze. This is probably one of the larger files, so i'll kinda skim through it and hopefully the comments will clear some things up.

Code:
class KFSM_HumanFreeze extends KFSpecialMove;

var transient KFPlayerController OwnerController;
var class<EmitterCameraLensEffectBase> LensEffectTemplate; // a template effect for the user camera, ie; FullFrost

var protected ParticleSystem FrozenSteamTemplate; // particle system for frozen block
var ParticleSystemComponent FrozenHuman;

var AkEvent GrabbedSoundModeStartEvent; // victim grapple special move sound begin
var AkEvent GrabbedSoundModeEndEvent;
var AkEvent ShatterSound; // freeze grenade explosion sound

/** Notification called when Special Move starts **/
function SpecialMoveStarted(bool bForced, Name PrevMove )
{

    local KFPawn_Human KFPH;
    local KFGameInfo KFGI;
    super.SpecialMoveStarted( bForced, PrevMove );
    OwnerController = KFPlayerController( PawnOwner.Controller );
 
 
    KFPH = KFPawn_Human(PawnOwner);
    // if the owner is indeed a human
    if (KFPH != none)
    {
        if (PawnOwner.Role == ROLE_Authority)
        {
            KFGI = KFGameInfo(KFPH.WorldInfo.Game);
            if (KFGI != none && KFGI.DialogManager != none)
            {
                // play the grabbed sound dialogue
                KFGI.DialogManager.PlayPlayerGrabbedDialog(KFPH);
            }
        }
    }
    // if there is a controller
    if( OwnerController != none )
    {
        // check if it's client
        if( OwnerController.IsLocalController() )
        {
            // play grabbed sound and apply frost lense
            OwnerController.PostAkEvent(GrabbedSoundModeStartEvent,,,true);
            OwnerController.ClientSpawnCameraLensEffect(LensEffectTemplate);
        }
    }
    // check if the owner is crouched
    KFPOwner.ShouldCrouch( false );
    if( KFPOwner.bIsCrouched )
    {
        // if they are, uncrouch them
        KFPOwner.ForceUnCrouch();
    }
    // play the shatter sound to indicate that the freezing has started
    KFPOwner.PlaySoundBase(default.ShatterSound, true,,, KFPOwner.Location);
    // disable the players ability to crouch and jump
    KFPOwner.bCanCrouch = false;
    KFPOwner.bCanJump = false;
    KFPOwner.bJumpCapable = false;
    DoFreeze();
}

/** create timers to determine how long the player is frozen for **/
function DoFreeze()
{
    local float TimeUntilThaw;
 
    if ( KFPOwner.Role == ROLE_Authority )
    {
        // wait 2 seconds untill calling function that stops the special move
        TimeUntilThaw = 2.f;
        KFPOwner.SetTimer(TimeUntilThaw, false, nameof(DoThaw), self);
    }

    // client side
    if ( PawnOwner.WorldInfo.NetMode != NM_DedicatedServer )
    {
        // create a particle system, which is a frozen block
        FrozenHuman = new(self) class'ParticleSystemComponent';
        FrozenHuman.SetTemplate( ParticleSystem'KFP_CryoTrail.FX_Human_Freeze' );
        KFPOwner.Mesh.AttachComponentToSocket( FrozenHuman, 'root' );
        FrozenHuman.ActivateSystem();
    }
}

function DoThaw()
{
 
    if ( PawnOwner.Role == ROLE_Authority )
    {
        // end the special move
        KFPOwner.EndSpecialMove();
    }
}

/** called when the DoThaw timer ends **/
function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
    super.SpecialMoveEnded( PrevMove, NextMove );
    `log("I have thawed");
    if ( KFPOwner.WorldInfo.NetMode != NM_DedicatedServer )
    {
        // remove the particle system of the frozen block
        if( FrozenHuman!=None )
            FrozenHuman.SetStopSpawning( -1, true );
    }
 
    if( OwnerController != none )
    {
        if( OwnerController.IsLocalController() )
        {
            // end the player grabbed sound and the frost camera template
            OwnerController.PostAkEvent(GrabbedSoundModeEndEvent);
            OwnerController.ClientRemoveCameraLensEffect(LensEffectTemplate);
        }
    }
 
    // clear the timer for the thaw event
    KFPOwner.ClearTimer( nameof(DoThaw), self );
 
    // allow player full control
    KFPOwner.bCanCrouch = true;
    KFPOwner.bCanJump = true;
    KFPOwner.bJumpCapable = true;
}

defaultproperties
{
    Handle=KFSM_Frozen

    // when the parent DoSpecialMove super is called, these parameters lock the player in place
    bDisableMovement=true
    bDisableSteering=true
    bDisableWeaponInteraction=true
    bLockPawnRotation=true
    bDisableLook=true
 
    // templates for various FX
    GrabbedSoundModeStartEvent=AkEvent'WW_UI_PlayerCharacter.Play_Grab_Start'
    GrabbedSoundModeEndEvent=AkEvent'WW_UI_PlayerCharacter.Play_Grab_Stop'
    ShatterSound=AkEvent'WW_WEP_Freeze_Grenade.Play_Freeze_Grenade_Shatter'
    LensEffectTemplate=class'KFCameraLensEmit_FullFrost'
}

Where to even begin? well, lets start with defaultproperties. This is the stuff that's called when the class is initialized (I assume). A lot of the disable booleans should make sense (except lock pawn and disablesteering, honestly it feels like these do the same thing, which is nothing). These are variables that are created in the parent class and are used to determine what effects to apply to the player when the special move is called. Below that are the FX. AkEvents are sound banks that are played when these events are called. What are sound banks you may ask? They are a library of different sounds that can get called upon (This is why snow sounds different every time you step in the game). In this case we have something like 'WW_UI_PlayerCharacter' which stands for 'WWise' (The audio program) 'User Interface Player Character'. The dot DOES NOT INDICATE YOU ARE CALLING INTO THE OBJECT 'WW_UI_PlayerCharacter'. I program in C a lot and man, that was my first trip up that NO ONE addressed. The dot simply indicates that you are either entering a package (a packet of different stuffs, like AKBanks), or entering a new file (you can call into a specific file like HALOWEEN_). So this statements means that I am grabbing Play_Grab_Stop in the package WW_UI_PlayerCharacter.

KFCameraLensEmit is another class which simply consists of the effect of a lens effect that shows the player is frozen. (I'll go over that later I guess)

Okay, up to the top of the file, SpecialMoveStarted. This function is called, uh, pretty much whenever. You could, if you want, setup your own call for special move started, I instead hijacked KFAFflictionBase's Accrue function that already has a parameter setup for calling a special move when a threshold is met. This can be done by calling AccrueAffliction whenever your ZED or Weapon deals damage, and accruing your affliction type.

Theres something in this function I would like to address, which is that the super call. It's nothing complex, it simply calls the parent function of the same name, so that it can setup variables before everything else is done in the child. There is also the two local variable creations, these are essentially creating a variable that is the type of a local class that can be used as an object to call from, like creating a localc KFPawn_Human as KFPH, then setting that variable to the current controller. Now, I can just type KFPH instead of KFPawn_Human(PawnOwner). PawnOwner in this case is the owner of the special move, ie, the human pawn.

Hopefully the comments explain the rest of what StartSpecialMove is doing, so I'll move onto DoFreeze. SetTimer should hopefully explain itself, but if it doesn't, it's a function that ticks a timer that's in seconds down. When it goes to 0, the function specified is called, which in this case is DoThaw().

After setting the timer, we call the magic if statement, if ( x != NM_DedicatedServer ). All this statement does is check the side of the operation. If we wan't to save resources, why would we play an animation server side? The server doesn't care, it's not human (not yet atleast). So we check if it's not the dedicated server, and if not, we play the FX there.

Now, this is where KF2 SDK starts to really trickle in. Particle systems are a set animation that plays in either a burst, or a loop (or a loop of bursts). Fire is a particle system, which loops based on the current time since the program has started.

OKAY. Before I get yelled at for this, I realize that the prefix for the name of my package is 'KFP', which has no reasonable spot in a FX setting. Why is it called that then? well, welcome to KF2 SDK, where jankiness is king. For some god awful reason, I can't rename, move, delete or export packages, unless I wait long enough. Then I can rename one package, and ONLY one package, before it won't let me again. So I called it KFP at the start, and by golly, I'm too damn lazy to rename it or move files in and out of it.

Ignoring all that, what does these four lines of code do? Well, first we create a new class for a particle system and define it, which will limit everything to one particle system. then, we set that systems template to FX_Human_Freeze, which is a neat effect i added which is a 3rd person model to indicate when a human is frozen. I will go on a tangent on why I can't just freeze the human like the zeds freeze, but in a minute. Now, on the KFPOwner (our specific human) we attach to the mesh(the player model)'s root bone (a unmoving position on the character model) our new system, and then activate it.

TANGENT. Why you can't just freeze the player like you do the zed. A. no groundwork setup for freezing players. B I'll let my comments do the talking

//CharacterMICs[0].SetScalarParameterValue('Scalar_Ice', 1.f);
// What are scalar parameters, and why won't this work?
// scalar parameters are attached changes to the textures, based on the
// math operations done in a material, this is why they are called
// MIC's as they are Material Instance Constants, which allow constant changes to a materials parameters
// unfortunately, the base material that controls things like the
// EMP effect or burned effect (CHR_Basic_PM) cannot be changed in the code
// as it is not an MIC, but a material
// the only way around this would be to create a copy of the CHR_Basic_PM
// then re-reference a copy of all MIC's that reference the base material
// then copy all existing ARCH's that use these MIC's (Which is all the cosmetics, 400+ per character)
// this is possible but takes way too long and would force the player to load in basically reinstall
// all assets again just for freeze effect (please based tripwire, allow more customization)

Yea. I was pretty annoyed at the time.

Tangent mode off. We now go to the DoThaw() function. Literally all this does is call SpecialMoveEnded. I was kinda scared of putting a function like that within a timer, so I just attached it to another function. You can use logic described previously and my comments to infer what everything is doing within the function. What's important to note is that the super calls the parent SpecialmoveEnded function, which sets everything up to stop all the effects on the player.

So there we have it, a whole special move that freezes the player, everything from here on out is pretty much just busy work. I created my own damage type called ZedFreeze

Code:
class KFDT_ZedFreeze extends KFDT_Freeze;

// this class will determine what emmiter effects play what a damage type is dealt
defaultproperties
{
    CameraLensEffectTemplate=class'KFCameraLensEmit_Frost'
}

But all it really doees is add a camera effect for when the player is hit, so you can really just use KFDT freeze.
but if you really want to know what I did to make the effect appear

Code:
class KFCameraLensEmit_FullFrost extends KFEmit_CameraEffect;

defaultproperties
{
    PS_CameraEffect=ParticleSystem'KFP_CryoTrail.FX_Camera_FullFreeze'
    // disallos multiple instances of the emmiter, as we will not call this again
    bAllowMultipleInstances=false
    LifeSpan=2.0f
    bDepthTestEnabled=false
}

It's just setting a particle system right in front of your face, with a lifespan that matches the special move length.

Okay. So this is all great, we got a human setup that can be frozen, and we have a damage type that can actually freeze. What about the zed? Let's jump into that.

lets go back to our initial mutator

Before we get onto this, for full clarity, I basically stole a method of handling spawning from Forrest Mark X's Infernal Nightmare Mutator. Big Shout out to him as his source code made a lot of things easier to understand (but dude, comment your code, c'mon). I don't really want to go into that right now, as don't really fully understand it myself. I know, I know, kind of a cop-out when it comes to a guide, I may come back to it later but I'd rather describe What I know.

A MUCH easier method to doing this is not doing a mutator, but instead a game mode
Checkout : https://forums.tripwireinteractive....-how-to-mod-kf2-with-a-gameinfo-class.118960/

As a new game mode extends KFGameInfo_Survival.uc, all you need to do is replace indexes in the AIClassList under defaultproperties, as he describes in his tutorial


Code:
defaultproperties
{
    AIClassList(AT_Husk)=class'KFGameContent.KFPawn_ZedHusk_Myown'
}

now, we simply create our own husk class:.

Code:
class KFPawn_ZedHusk_Myown extends KFPawn_ZedHusk;

var LinearColor AVector;

simulated function PostBeginPlay()
{
    Super.PostBeginPlay();
 
    if( WorldInfo.NetMode!=NM_DedicatedServer )
    {
        UpdateGameplayMICParams();
    }
}

simulated function UpdateGameplayMICParams()
{
    Super.UpdateGameplayMICParams();
 
    if( WorldInfo.NetMode!=NM_DedicatedServer )
    {
        CharacterMICs[0].SetVectorParameterValue('Vector_GlowColor', AVector);
    }
}

DefaultProperties
{
    AVector=(B=1,G=0.2f)

    ElitePawnClass.Empty
    ElitePawnClass.Add(class'KFPawn_ZedFrozenHusk')
}

Now, I want you to ignore the spooky vector paramter value, it will make sense later what this is. However, it is important to note what UpdateGameplayMICParams() is. This is called through PostBeginPlay, (as described earleir, AFTER everything is initialized), the function itself will update the Material Instance Constant during the game. Material Instance Constant are a series of parameters that change how a material looks in game. Just keep this in mind for later.

Regardless, we added a new elite class. I emptied the list, as attached to the base husk are already the Edars. You don't really need to do this.

We can now create our new elite husk (Finally!)

Code:
class KFPawn_ZedFrozenHusk extends KFPawn_ZedHusk;

var LinearColor MainGlowColor;

simulated function PostBeginPlay()
{
    Super.PostBeginPlay();
 
    if( WorldInfo.NetMode!=NM_DedicatedServer )
    {
        UpdateGameplayMICParams();
    }
}

simulated function UpdateGameplayMICParams()
{
    Super.UpdateGameplayMICParams();
 
    if( WorldInfo.NetMode!=NM_DedicatedServer )
    {
        CharacterMICs[0].SetVectorParameterValue('Vector_GlowColor', MainGlowColor);
        CharacterMICs[0].SetVectorParameterValue('Vector_FresnelGlowColor', MainGlowColor);
        CharacterMICs[0].SetScalarParameterValue('Scalar_Ice', 0.25f);
    }
}

/** Turns medium range flamethrower effect on */
simulated function ANIMNOTIFY_FlameThrowerOn()
{
    if( IsDoingSpecialMove(SM_HoseWeaponAttack) )
    {
        KFSM_Husk_FreezeThrowerAttack(SpecialMoves[SpecialMove]).TurnOnFlamethrower();
    }
}

/** Turns medium range flamethrower effect off */
simulated function ANIMNOTIFY_FlameThrowerOff()
{
    if( IsDoingSpecialMove(SM_HoseWeaponAttack) )
    {
        KFSM_Husk_FreezeThrowerAttack(SpecialMoves[SpecialMove]).TurnOffFlamethrower();
    }
}

DefaultProperties
{
    MainGlowColor=(B=1,G=0.66f,R=0.33)
    FireballClass= class'ihd_edit.KFProj_Husk_Freezeball'
    // dinnae what does it, but some special seasonal handler adds HALLOWEEN_ to the beginning
    // based on the SEI_ID
    // A. very poor coding practice, why not just hold the seasonal contents in a folder and then
    // assign index values to folder names in a native function? pretty common practice in C
    // B. I suspect it's the boss cache as that's the only function that messes with MonsterArchPath
    // but it's native(C++) so whatever
    MonsterArchPath="Frozen_ZED_ARCH.ZED_Husk_Archetype"
 
 
    Begin Object Name=ChestLightComponent0
        LightColor=(R=86,G=167,B=255,A=255)
    End Object
 
    // I am the master of copy codes
    // speaking of poor coding practice, I basically hijacked this from the freeze grenades
    // Grenade explosion light
    Begin Object Name=ExplosionPointLight
        LightColor=(R=128,G=200,B=255,A=255)
    End Object

    // explosion
    Begin Object Class=KFGameExplosion Name=ExploTemplate0
        Damage=60   //100
        DamageRadius=500//600
        DamageFalloffExponent=0.5f  //2
        DamageDelay=0.f

        // Damage Effects
        MyDamageType=class'KFDT_Husk_FreezeSuicide'
        FractureMeshRadius=200.0
        FracturePartVel=500.0
        ExplosionEffects=KFImpactEffectInfo'WEP_Freeze_Grenade_Arch.FreezeGrenade_Explosion'
        ExplosionSound=AkEvent'WW_WEP_Freeze_Grenade.Play_Freeze_Grenade_Explo'
        MomentumTransferScale=1

        // Dynamic Light
        ExploLight=ExplosionPointLight
        ExploLightStartFadeOutTime=0.5
        ExploLightFadeOutTime=0.25
        ExploLightFlickerIntensity=5.f
        ExploLightFlickerInterpSpeed=15.f

        // Camera Shake
        CamShake=CameraShake'FX_CameraShake_Arch.Grenades.Default_Grenade'
        CamShakeInnerRadius=200
        CamShakeOuterRadius=900
        CamShakeFalloff=1.5f
        bOrientCameraShakeTowardsEpicenter=true
    End Object
    ExplosionTemplate=ExploTemplate0
 
    Begin Object Name=SpecialMoveHandler_0
        SpecialMoveClasses(SM_HoseWeaponAttack)= class'KFSM_Husk_FreezeThrowerAttack'
    End Object
 
    ElitePawnClass.Empty
}

Now, your first impression of this code may be "Wow, you hijacked a lot of stuff". Yes, yes I did. The SDK is sometimes really frustrating to use, and it's also a massive time sink to make FX. So I just hijacked some (Mind you, I created a lot of my own)

So, in this, I would like to go from the top, down, as I would like to address what ANIMNOTIFY is. Now, I'm not going to be as thorugh in the rest of these classes, as I'm going to assume stuff is staring to make sense. In the game, there are animation notifications that take place in animation libraries, so during a specific point in an animation, you can notify the back end when some event has finished. That's all this is.

I would also like to address what an archetype, or ARCH is. An archetype is a culmination of different properties that makes up a character (which are usually attached to pawns). They have the mesh, FX's added to the mesh and animation trees.

Finally, I will talk about impactinfo. This class called contains a bunch of information of velocty distributed to the pawn, damage type and also extra functions added on top that are called when a pawn is impacted.

Moving on, we will finally talk about the damage types and moves that are called within this class:
KFSM_Husk_FreezeThrowerAttack
KFProj_Husk_Freezeball
KFDT_Husk_FreezeSuicide


Code:
class KFSM_Husk_FreezeThrowerAttack extends KFSM_PlaySingleAnim;

/** The Archetype to spawn for our fire spray actors. */
var KFSprayActor                         FlameSprayArchetype;
var KFSprayActor                        MyFlameSpray;
/** Emitter to play when firing stops. */
var ParticleSystemComponent             PSC_EndSpray;
/** Replicated flag to turn off the flamethrower effect on clients. */
var bool                                  bFlameThrowerActive;

/** Pilot light sound play event */
var AkEvent                             FlameAttackPlayEvent;

/** Pilot light sound stop event */
var AkEvent                                FlameAttackStopEvent;

protected function bool InternalCanDoSpecialMove()
{
    local vector HitLocation, HitNormal;
    local Actor HitActor;

    if( KFPOwner.IsHumanControlled() )
    {
        return KFPOwner.IsCombatCapable();
    }

    if( AIOwner == none || AIOwner.MyKFPawn == none || AIOwner.Enemy == none )
    {
        return false;
    }

    if( !KFPOwner.IsCombatCapable() )
    {
        return false;
    }

    // Make sure we have line of sight
    HitActor = PawnOwner.Trace(HitLocation, HitNormal, AIOwner.Enemy.Location, PawnOwner.Location, true);
    if ( HitActor != None && HitActor != AIOwner.Enemy )
    {
        return false;
    }

    return super.InternalCanDoSpecialMove();
}

function SpecialMoveStarted( bool bForced, name PrevMove )
{
    super.SpecialMoveStarted( bForced, PrevMove );

    if( AIOwner != none )
    {
        `AILog_Ext( self@"started for"@AIOwner, 'Husk', AIOwner );
        AIOwner.AIZeroMovementVariables();
    }
}

/** Turns the flamethrower on */
// why not change name?
// well I can, but the name is linked to the ANIMNOTIFY in the animation library
// and man I am lazy
simulated function TurnOnFlamethrower()
{
    local KFPawn_ZedHusk HuskOwner;

    HuskOwner = KFPawn_ZedHusk( PawnOwner );

    if( HuskOwner == none || !HuskOwner.IsAliveAndWell() || bFlameThrowerActive )
    {
        return;
    }

    if( MyFlameSpray == none )
    {
        MyFlameSpray = HuskOwner.Spawn(FlameSprayArchetype.Class, HuskOwner,, HuskOwner.Location, HuskOwner.Rotation, FlameSprayArchetype, TRUE);

        // Use a particular ImpactProjectileClass that will scale damage by difficulty
        MyFlameSpray.ImpactProjectileClass = class'KFProj_HuskGroundFrost';
        MyFlameSpray.OwningKFPawn = HuskOwner;
        MyFlameSpray.SetBase(HuskOwner,, HuskOwner.Mesh, MyFlameSpray.SpraySocketName );

        if( HuskOwner.WorldInfo.NetMode != NM_DedicatedServer && PSC_EndSpray != None )
        {
            if( PSC_EndSpray != None)
            {
                PSC_EndSpray.SetTemplate(MyFlameSpray.SprayEndEffect);
            }
            HuskOwner.Mesh.AttachComponentToSocket( PSC_EndSpray, MyFlameSpray.SpraySocketName );
        }


        if( HuskOwner.Role < ROLE_Authority )
        {
            // Set these to be visual only as we do the damage on the server versions
            MyFlameSpray.bVisualOnly=true;
        }
    }

    bFlameThrowerActive = true;

    if( HuskOwner.Role == ROLE_Authority || HuskOwner.IsLocallyControlled() )
    {
        HuskOwner.SetWeaponAmbientSound(FlameAttackPlayEvent);
    }

    if( MyFlameSpray != none )
    {
        // Apply rally boost damage
        MyFlameSpray.SprayDamage.X = HuskOwner.GetRallyBoostDamage( MyFlameSpray.default.SprayDamage.X );
        MyFlameSpray.SprayDamage.Y = HuskOwner.GetRallyBoostDamage( MyFlameSpray.default.SprayDamage.Y );

        // Start flames
        MyFlameSpray.BeginSpray();
    }
}

function SpecialMoveEnded(Name PrevMove, Name NextMove)
{
    TurnOffFlamethrower();

    super.SpecialMoveEnded( PrevMove, NextMove );

    if( AIOwner != none )
    {
        `AILog_Ext( self@"ended for"@AIOwner, 'Husk', AIOwner );
    }
}

/** Turns the flamethrower off */
simulated function TurnOffFlamethrower()
{
    local KFPawn_ZedHusk HuskOwner;

    HuskOwner = KFPawn_ZedHusk( PawnOwner );

    if( HuskOwner == none || !bFlameThrowerActive )
    {
        return;
    }

    bFlameThrowerActive = false;

    if( HuskOwner.Role == ROLE_Authority || HuskOwner.IsLocallyControlled() )
    {
        HuskOwner.SetWeaponAmbientSound(FlameAttackStopEvent);
    }

    // play end-of-firing poof.  will stop itself.
    if( PSC_EndSpray != None )
    {
        PSC_EndSpray.ActivateSystem();
    }

    if( MyFlameSpray != none )
    {
        MyFlameSpray.DetachAndFinish();
    }
}

/**
* Can a new special move override this one before it is finished?
* This is only if CanDoSpecialMove() == TRUE && !bForce when starting it.
*/
function bool CanOverrideMoveWith( Name NewMove )
{
    if ( bCanBeInterrupted && (NewMove == 'KFSM_Stunned' || NewMove == 'KFSM_Stumble' || NewMove == 'KFSM_Knockdown' || NewMove == 'KFSM_Frozen') )
    {
        return TRUE; // for NotifyAttackParried
    }
    return FALSE;
}

DefaultProperties
{
    // SpecialMove
    Handle=KFSM_Husk_FreezeThrowerAttack
    bDisableSteering=false
    bDisableMovement=true
    bDisableTurnInPlace=true
       bCanBeInterrupted=true
       bUseCustomRotationRate=true
       CustomRotationRate=(Pitch=66000,Yaw=100000,Roll=66000)
       CustomTurnInPlaceAnimRate=2.f

       // Animation
    AnimName=Player_Flame
    AnimStance=EAS_FullBody

    // Flamethrower
    FlameSprayArchetype=SprayActor_Flame'ZED_HuskFrozen_ARCH.Husk_Freezethrower_Freeze'

    Begin Object Class=ParticleSystemComponent Name=FlameEndSpray0
        bAutoActivate=FALSE
        TickGroup=TG_PostUpdateWork
    End Object
    PSC_EndSpray=FlameEndSpray0

    FlameAttackPlayEvent=AkEvent'WW_WEP_Cryo_Gun.Play_Cryo_Gun_3P_Start'
    FlameAttackStopEvent=AkEvent'WW_WEP_Cryo_Gun.Play_Cryo_Gun_3P_Stop'
}

"Wow, vertical, you basically copied KFSM_Husk_FlameThrowerAttack.uc". Yup. I didn't really need to. Let this be a lesson for effeciency, as I could have easily extended KFSM_Husk_FlameThrowerAttack and simply simulated the TurnOnFlamethrower function. I did this instead because this was the last thing I needed to do, and I knew this worked. The issue with the base TurnOnFlamethrower is that:
MyFlameSpray.ImpactProjectileClass = class'KFProj_HuskGroundFire';
This class is not set in the defaultproperties, which means if I wanted to change the damage type, gatta change that function up. I thought to myself "well, whatever" and just copy pastad the whole code over. Definitely going to refactor this when I'm not exhausted of SDK.


Code:
class KFProj_Husk_Freezeball extends KFProj_Husk_Fireball;

DefaultProperties
{
    // Grenade explosion light
    Begin Object Name=ExplosionPointLight
        LightColor=(R=128,G=200,B=255,A=255)
    End Object

    // explosion
    Begin Object Name=ExploTemplate0
        Damage=10

        MyDamageType=class'KFDT_Husk_FreezeSuicide'
        ExplosionEffects=KFImpactEffectInfo'ZED_HuskFrozen_ProjExp.FX_FreezeBall_Projectile_Explosion'
        ExplosionSound=AkEvent'WW_WEP_Freeze_Grenade.Play_Freeze_Grenade_Shatter'

    End Object
 
    Begin Object Name=FlamePointLight
        LightColor=(R=128,G=200,B=255,A=255)
    End Object

    Begin Object Name=ExploTemplate1
        Damage=1
        // Damage Effects
        MyDamageType=class'KFDT_ZedFreeze'
        ExplosionEffects=KFImpactEffectInfo'WEP_CryoGun_ARCH.GroundCryo_Impacts'
    End Object
 
    GroundFireExplosionActorClass=class'KFExplosion_GroundIce'
    ProjFlightTemplate=ParticleSystem'KFP_CryoTrail.FX_Husk_projectile_frozen'
 
    AmbientSoundPlayEvent=AkEvent'WW_WEP_SA_Crossbow.Play_Bolt_Fly_By'
    AmbientSoundStopEvent=AkEvent'WW_WEP_SA_Crossbow.Stop_Bolt_Fly_By'

}

Thankfully, I had the consciounce to do this better.

Code:
class KFDT_Husk_FreezeSuicide extends KFDT_ZedFreeze;

static function PlayImpactHitEffects( KFPawn P, vector HitLocation, vector HitDirection, byte HitZoneIndex, optional Pawn HitInstigator )
{
    local float ParamValue;
    local int MICIndex;

    MICIndex = 0;
    if (P.GetCharacterInfo() != none)
    {
        MICIndex = P.GetCharacterInfo().GoreFXMICIdx;
    }

    // If we're dead and not already frozen (prevents re-shattering)
    if ( P.bPlayedDeath
        && P.CharacterMICs.Length > MICIndex
        && P.CharacterMICs[MICIndex].GetScalarParameterValue('Scalar_Ice', ParamValue))
    {
        if (ParamValue == 0)
        {
            PlayShatter(P, false, `TimeSinceEx(P, P.TimeOfDeath) > 0.5f, HitDirection * default.KDeathVel);
            return;
        }
    }

    if(P !=None && P.Health>0 && P.AfflictionHandler!=None )
        // accrue some freeze
        P.AfflictionHandler.AccrueAffliction(AF_Freeze,60.f);
 
    Super.PlayImpactHitEffects(P, HitLocation, HitDirection, HitZoneIndex, HitInstigator);
}

defaultproperties
{
    KDeathVel=300
}

This is really the more important of the three, as it finally brings everything full circle. PlayImpactHitEffects is called when the projectile impacts an object. A lot of the above stuff is fluff, which shatters enemies or applies some scalar_ice paramater to the MIC. What's important is the AccrueAffliction. AF_Freeze is the affliction type asscociated with the freeze affliction, what we are doing is accruing 60 freeze_power to the AF_Freeze affliction type. because our human is vulnerable to 4X freeze, this will instantly overreach the threshold and wabam, our human is now locked in place for 2 seconds.

I'm going to end the guide here. I know I said I would talk about SDK effects, I will still do that, but probably as an addendum at anotehr time. This was a lot longer than I expected. post questions! Point out my Mistakes! HUZZAH!
 
Last edited:
Part 2: The guide boogaloo

I mentioned before that I would talk about sdk stuff. This is where most of my toubles lied in, as the unreal 3 guides are sometimes vague and don't actually explain things.

Let's begin.

I would like to start this out by explaining some useful tools that I wish I noticed when messing around in the packages. List Referenced Objects and List Objects that Reference this object. Right click any item within a package and you can see these two available options. Just like Agent Ransack, if you are trying to make your way through a tree of objects to see how a particle effect of mesh is used where (like the flamethrower effect on the husk), then you can just check the referenced objects. I did it the hard way, which was to learn what everything did and where something should be used. make sure to fully load everything, just right KFGame in the content browser and hit "Fully Load", otherwise it won't be ablee to see the referenced objects>

let's begin with the basics: A 2d texture. A lot of people should know what this is, I would hope. It's simply a 2d series of pixels that have information attributed to each pixel (RGBA). Now, that A at the end may give you pause. It stands for alpha, or alpha channel. It's essentially the see through pixels in a image.

These 2d textures are used in Materials, or Mats. A material is where the magic happens, as you can sample a texture and then apply certain effects to it. It helps (me atleats) to think of it as matrix manipulation. All your doing is taking that 2d vector of a bunch of values, and then smashing it together with other 2d vector values. Did I mention theres math involved. Joy! Yes, if you want to really get anywhere with materials, your going to need to understand how to use mathematical properties to manipulate texture samples. OR, you could just stick a bunch of stuff in and see how it works out. I would actually recommend this as it gives you a good idea of what does what.

Before we look at a material, let me just set this down right here:

Heres what a material looks like:
Top_half_mat.PNGHalf_Material.PNG
It coulsn't fit all in one screen.

Now, at first glace, you may want to just run for the hills. But it's actually a lot simpler than it looks. Let's break it down and start with the basic stuff.
What are the blocks. These are simply representations of an operators and operatees. Most of the time they have some input, and they always have an output. You can think of something like y = x+b, where the y = is the output, the + is a operator block (Which I will refer to as nodes from now on), and the x and b are operatee nodes. lets get a look at this in action

Add_Material.PNG
I just want you to look at the texture sample, sqrt and add node. Lots of nasty images going on here, but you can really ignore the add and sqrt. All the sqrt is doing, is atking the square root of each pixel in the image before, and outputting that as another 2d image. Okay, you may ask, but if I look at the whole material, the image that's inputted into this sqrt operator is dimmer. But if each pixel has RGB values from 0-255, shouldn't the sqrt'ed image be even dimmer than the previous? Well, my friend, each of those pixels are not 0-255, but instead 0-1. They are normalized to a 0-1 ratio, so when I take the square root of a half bright pixel, 0.5 we get as an output 0.707, a brighter pixel. This can be confusing, so be careful!

That's important info and all, but what's more important is we have a 2d image of pixel values from that square root, and another 2d array of pixel valuess from a texture sample. Then we add them together. Simple as that, every pixel is just added together. Take a gander at this one:

1600913288416.png
Gross, ewwww. More math. But this is really as complex as it gets. Lets bring this back into the context of an fx of a frozen screen (What this material is). Well, what do I want it to do? I want it to have an open hole that the player can see through, power and divide are pretty superflous and only make it a little less jarring than having a perfect circle form on your screen. That's really what this begins to break down into to. What do I want out of this. What is the output? I want a hole to appear in a texture, how can I do that? Subtract a texture that has a circle in it from my base texture. Okay, I want to make this less rounded. Okay, multiply it with some very bright texture that has bits taken out of it. Well, now it's too dim. Okay, then square root it, bring the pixel brightness up, or maybe add a constant value to it (you can do that). It's less math and more intuition on what works and what doesn't.

Now, this isn't really the best way of doing it. Ideall I would have gone through and actually erased parts of my original texture that i wanted out, then subtracted that from my original texture. You could do this, Use a tool like Krita and erase parts of the textures using an erasure with a different opacity levels. But this texture, for me, what tutorial for myself more than anything else. That's why I mention earlier I recommend playing around with these operations, see what they do. It also takes a lot of time to do this, rather than just knowing how to add textures together that already made (yay hijacking!).

Let's move to whats going on at the top
Top_sin.PNG
Now, the previous stuff was chick peas compared to what is going on here, I really do want to break this down peice by peice.
1600914165793.png
These two are the most important bits, time and dynamic parameter. Functionally in my case they do very similar things. Time is a node that generates a value based on seconds from 'start time'. Start time in this case is when the sdk was started up. You can verify this by opening up the particle systems that use fire materials and the materials themselves and see they are synced exactly. In game this is simply when the game starts up. So what's the deal with Dynamic Parameter? Okay, this is a good time to go over parameters and why we like Material Instance Constants. Parameters are constant values that can be set externally to the material. That means other packages and objects can access the parameter and set something so that it changes in game on the fly. This came up in the coding section of this guide because the material that is a parent for all pawns, CHR_Basic_PM, does not have the scalar parameter for frost. Alternatively you can also assume that every zed has a parent material that holds the scalar parameter for frost, which when changed adds a frost effect to the materal on the model. There are also vector parameters, or multiple constant parameters that have effects on multiple planes, like RGB. This is what the Vector parameter was for glowing in the UpdateGameplayMICParams. It's all coming together baby.

In my case, I have a dynamic parameter. This can be accessed in a particle system. Why not just use time, you may ask? Well, time is based on when the game starts. That means if I want a material to always start at the same state, I can't use it, as it might grab a start time that will put the material in the middle of it's animation. This will hopefully make more sense in a bit

1600914690327.png
Nothing crazy complex going on here. I'm adjusting time on two seperate fronts. Essentially what I want is a delayed effects, so all I need to do is add some constant time to start time. This added value is multiplied by another param, called speed, as I want this delay to scale with how fast the whole animation needs to go (for example, I want a 3 second delay with a 10 second aniation, but I don't want a 3 second delay with a 20 second animation if i slow it down by twice the amount). So why did I multiply it again? Because IDK, I wasn't really thinking and it worked out(teehee). Listen, if it ain't broke, don't fix it, I'm modding not applying for google. Regardless, the other path that time is going down is not delayed just the static time.

1600915068308.png
Heres where the magic starts to happen. Ya'll remember sin and cosin from geometry? Soh Cah Toa?
1600915143435.png
Yeah, this jazz. Very important. A sinusoidal is a repeating waveform. It means whatever value you put in, always going to be between -1 and 1. So time, as it were, big number. You run your game for 50 minutes, it's going be in the thousands. Doesn't matter, the sinusoidal will clamp that baby in place and still create something that can be manipulated with time. Cosine is very similar, only difference being that it's offset by 90 degrees. Math stuff, not my job to really explain it, what is important is what I'm doing in that top path. I've added cosine and sine together to make an extended wave form that stays abound away from 0 for a longer period of time. I don't really need to do this, as this can be accomplished by just multiplying time by 1 radian or something, but it's cool, you know. After we make this new wave form, I take the absolute value of it and then clamp it from 0 -1 (just to be sure). This keeps it always positive.


The bottom path has some very similar stuff going on, should be easy to follow. To be continued as I can only attach 10 files.
 

Attachments

  • Top_half_mat.PNG
    Top_half_mat.PNG
    96.3 KB · Views: 0
Upvote 0
Cont.

I'm just ganna set this down right here:

1600915562729.png1600915648925.png
Two seperate images. The one on the left is when the sinusoids are at their peak (ae 1). They multiply my frost texture that I imported by 1 , which basically chanes nothing, then I subtract my whole in the center, and bam! we got a frost effect on the edges of the screen. But that's not all! On the right, we have the effect when the sinusoid is at 0(or near it, hard to get a snippet), the texture is multiplied by 0, then nothing is outputted anyways! This creates the effects of frost entering in on the characters screen, and then leaving.

Okay, you might say. But what is that constant clamp doing. Well, my creation is not prefect. The subtraction actually brings the texture into the negatives, leaving a white blotch on the center of the screen. This is a good time to address Previewmaterials
1600916065192.png
These are what the material asscosiates with the new texture you have created. In this case we have the emmissive and the opacity material inputs. Emmissive is the actual texture viewed, wheras opacity tells us what should actually be seen. The inner rung of the opacity texture is black, meaning that the center is completely clear, wheras the outer rung is whiter, meaning it is opaque. Now, you could take a sqrt of that to make the outside more opaque (theres that intuition kicking in), but my frost looks a little better when it's slightly see through.

This bad boy explains what everything does pretty well

Okay. That's materials. Hopefully that will clear up what they are and what they do.

Moving on the MIC's
1600916475642.png
Suprisingly, Not much to go over. A lot of this stuff I don't even really know what it does. The two most important things here are the parent (the material you are messing around with) and the parameters (as discussed earlier). These MICS are what are modified in the code, and a lot of this stuff can be enabled and disabled on the fly(I think, don't qoute me on that). You'll see Speed in there, the paramater I was talking about, and also my material that I made under parent.

Now, we move onto another hefty section, particle systems
This is what really takes up the most time, as theres a lot of finangaling and moving small bits around to make it look nicer. I'll be going over two seperate ones. My husk shot and my screen frost.

Freeze_Settings.PNG
This right here, is a single emmiter. It's a customizable point in 3d space where you can spawn pretty much anything and attach to it parameters which effect things like size, speed and color. Everything listed effects the emitter in specific ways, so I'll be going through them one by one and talking about the important bits

EmitterDelay.PNG
The required 'tab" (I guess I'll call them that) contains a bunch of info about the emmitter itself, and not the entities it spawns out of it. For example, I can set the delay at which the emmiter spawns, or if I declare low, it will set it within a range.
EmitterDuration.PNG
The duration determines how long this emitter lasts for, so if I wanted to spawn a bunch of entities over 2 seconds, I would set the duration to 2. The loops are the amount of times this emitter will loop before stopping. Setting this to zero will make it run infinite times. (This really got me when I first started, as I couldn't figure out why an effect would stop but another would run forever).
UseLocalSpace.PNG
Use local space assigns the emmiter and it's entities to be oriented around a local space, as opposed to the world space. This means when a enemy turns with the attached particle system, everything will turn with it. As this is supposed to be attached to out screen, it would be pretty silly if we turned and the effect dissapeared. The material is the MIC, ignore the fact that it is an instanced time varying material, as I believe I forgot to change it back when I was messing with stuff. Screen allignment dictates how the entities are viewed to the screen, if it's velocity, then the entities will warp dependent on their trajectory, for example.

Moving onto spawn:
1600917448316.png
This is your burst count, if defined, it will spawn that number of entities, a random number from countlow to count, at period time.
1600917537867.png
This is the rate. It will spawn entities dependend on the rate within the alloted emmiter time. setting it to zero means it is wholly dependent on burstlist

continued as I am only allowed 10 images
 
Upvote 0
Cont.

I'm just going to drop this right here

Moving on to store spawn time. This is a pretty special tab, as i believe it overrides relative time with the spawn time of the material. Not too sure. This is important for the next bit

1600918065047.png
This is dynamic, or dynamic parameter. remember that? Yea, it is set here, For clarities sec, let's first explain what we are even looking at. This is a constant curve, There are constants, constant curves, vectors and vector curves (theres also uniform, which indicates arange of values to randomly pick from, it's a dumb name). Constant curves allow for points to be added which change the OutVal at time InVal. In this case, point one indicates that at time 0, scalar parameter time for my frost screen material should be 1.21 (when the effect is off), then at point 2, at time 1 the scalar is 2.92 (again off, but it will have gone through the whole animation). Important to note, a lot of the values are relative. The inVal ranges from 0-1 (well actuall it can go after 1) which indicates the entities lifetime of 0-x, where x is however long the lifetime of the entity is

Another thing to note, I don't think the order of the tabs actually matter.

Moving from the Scalar, we go to lifetime. Nothing important here. The lifetime uniform range or constant determine how long each entity lives within the emitter. Now, for times sake I'm going to skip over the initalizer ones, as those should now be fairly obvious. Lock axis locks the axis of the entities to one plane. if someone views it from the size it's invisible.

We move onto color over life. This one confused me for a bit when I first got started
1600918854797.png
This one has two parts of it. At first I thought it was jsut for alpha, as my coloroverlife section was collapsed. Again, remember, some of this stuff is relative. If you set a inital color (which you should), and it's set to X = 1, Y = 2, Z = 3, changing the color to X=2,Y=2,Z=2 at 0.5 seconds will DOUBLE the initial color. What these axis's correspond to are the RGB values of the entity. This can be played around with in the husks fireball cannon. The flare materials change drastically with color over life. For some reason my material doesn't. I don't really understand why.

Edit: (10/9/2020), On further reflection, and more work in the SDK, The reason why the MIC is not effected by either color or alpha is because the base material does not have a VectorColor Node attached anywhere in it. This node is controled by the particle system, and if set up correclty, will control the alpha and color of the material.

Moving past this, we go to parameter, which I don't really understand either, as it doesn't allow you to edit the parameter.

And thar she blows:
1600919263837.png1600919286016.png
Okay. This should have been an okay rundown on important things with particle systems, but theres one more part I think is important. Meshes in particle systems.

1600919365674.png
As you can see, a lot going on here. I will not be explaining everything I did step by step. Just some tips: when you put an entity spawner down, put another on it that has lower opacity but higher velocity. This gives it a nicer transition.

I want to go over the mesh data, the cylinder tab, the emmiter initloc and the subimage index (last two out of frame)

But, I think I'll stop here for now, abd come back to it tomorrow. Hopefully someone can use this in the future.

As an ending footnote to other modders out there. Write guides. Please. This modding situation is so sparse because there's nothing out there. The sdk is hated because theres nothing explaining how to work with it anywhere. For gods sakes I almost gave up because I couldn't figure out why the freeze steam from the sharpshooter grenade ended but fire didnt end. The devs aren't going to put anything out, so it's really left to us to make it easier to transition into.
 
Last edited:
  • Like
Reactions: simplecat
Upvote 0
You put a lot of effort into these guides. well done!
I don't insist but perhaps you could port your work to kf2 wiki
that seem to be a bit better place to get your guides noticed. It has visual editor for text formatting and shouldn't be hard at all to move all the text and images. Anyway it's up to you, good job.
 
Upvote 0
Fair, very fair. But, I would like to point out two things as to why I probably won't move it to the wiki
A: I actually have never seen the linked guides before. In fact, the only guides I have really found have been on tripwire forum and steam (and mind you, I spent A LOT of time looking for guides). So I don't know if it will really get the guides noticed by other people who need to use them.
B: These guides are SUPER informal. I already write technical papers for work, I ain't got time to format shtuf for a all slick professional wiki. If someone else want s to like, put in the time to put in bullet points and fix my spelling errors and remove all my stupid jokes, go crazy. I'm doing this for myself, it's kind of therapeutic, and I know that if I have to start format stuff and start checking for "geramor" it's going to really kill my buzz. I'm more hoping that someone who is actually looking to get some sort of start beyond the initial setup will find this here, and will be willing to look past teh amateur nature of the posts and find the technical information they are looking for.

Thank you though. I appreciate compliments as it validates me and makes it feel as though this writing helped at least someone.

Before I start, I want to cover two small things that I think are important.
A, in the first post I said " Let this be a lesson for effeciency, as I could have easily extended KFSM_Husk_FlameThrowerAttack and simply simulated the TurnOnFlamethrower function." but I never explained what that meant. When you extend a class, you can basically overwrite a function already defined in that class by using the same function name. This is why supers are important if you want to call the parent class function of the same name.

Second the properties window:
1601003003857.png
This bad boy is where all your settings for each node are messed with. Want to change the name of the parameter? it's in this bad boy. Need to change the speed of a rotator node? There it is. Along with this, if you click off of all your nodes, it fills with other important information. I believe the materials overview page goes over what the stuff does. But the most important thing that you need to change when in this is blend mode. It disallows and allows certain preview materials from activating. In my case, I wanted opacity, so I needed to select BLEND_TRANSLUCENT.

hello, 30 minutes into the future vertical here. Now, I sad two things, but I'm coming back here from later on and putting another thing I thought would be helpful:
1601005082537.png
The arrow is a drop down selection menu, which is usually for switching between constant, uniform and vector points. The brackets remove all points ({} in C indicate an empty list), and the plus button adds a point. The three squares copy the point you have made with all the data in it. The X deletes the point and the block with the arrow inserts a point below this one. Why mention these? if you hover over them it tells you all this. Well, I forget to use them, quite often, and instead I just hit the plus button and change the information in all my points. Don't do that! These make life easier.
1601005356833.png
Same with this. Arrow grabs a object from the content browser (The window you use to search packages). The magnifying glass finds the object currently in the text box in the content browser. The box clears the text box. if you see a material you like in another particle system, you can click magnifying glass to find the material, than use the arrow in your particle system to copy it over. Or you can just copy paste, either way.

With that done, I will begin covering the stuff that I said I would meshdata, cylinders, initloc, and subimageindex
1601003928231.png
The two most important bits here are bOverrideMaterial and mesh. bOverrideMaterial overrides the material put on the mesh originially, so we can get the ice like effect on the mesh. Mesh itself is the mesh that you want to use. In this case I hijacked a rock from outpost and then changes the size so that it looked like a projectile.

1601004100042.png
This is cylinder. The naming is pretty unconventional, but it's essentially an invisible object where your emmitter will place its entites. You can imagine an invisible cylinder where the emitter will random select a point within it to place an entity. height axis will change which direction it is facing (heightaxis x in this case means that the cylinder is on its side). Negative X, Y, Z and Positive X, Y, Z prevent the entities from spawning in that coordinate space. I believe the convention is exclusive, so if you were to say, disable negative x and negative y, it will only appear is the area which include both positive y and positive x, not just an area that is either positive x or positive y. Start height is the height of the cylinder , and start radius is the radius of the cylinder. This can be a constant, a range or even a curve that changes with time. Surface only spawns entities on the surface instead of any point within the cylinder. Velocity will transfer the velocity originally given in the direction of the angle within that cylinder (ae, if my entity spawns like, 45 degrees between the x and y axis, then the velocity initially declared will be sqrt(2)/2 the amount in those directions, lookup a pi chart if you need a reference). The velocity scale will scale this multiplier.

1601004785996.png
Here is the other section of the emmitter for the frostball that was cut off.
1601004923635.png
This is InitLoc. it will initialize the location based on another emmitter. All you need to the do is type the name of that emmitter (rocks, which is the right most emmitter) and it will attach itself to it.

Finally, theres the sub image index:
1601005517908.png
Now, you may say "Wait a minute, thats not the sub image index, that in required". This is because I need to explain what SubUV images are.

Cont. as thread only allows 10 images.
 

Attachments

  • 1601003883454.png
    1601003883454.png
    128 KB · Views: 0
Last edited:
Upvote 0
Cont.

Sub UVs are indications that you want the image to be cut into pieces. As you see in the rocks emmitter, there are four images in it, two vertical and two horizontal. Likewise, in required, I have specified that there are two horizontal Sub UV's and two vertical Sub UV's. To get the images to actually be flipped through, we need a subimageindex.

1601006046075.png
This bad boy essentially just tells the emmitter that we want to flip through the outval 0 (sub UV 0,0) and go through to image 3 (sub UV 1, 1).

I'm just ganna set this down right here:

That's all for today, unfortunately. Much shorter but I don't have much time. Next time I want to go over how KF2 handles animations and animation trees. Then maybe I can go into archetypes. Heck maybe I'll put some tips on how to make systememmitters look nicer and some cool effects you can make. Who knows?
 
Upvote 0
This part (or atleast this post) will just be appear artsy fartsy stuff. I thought it important to leave some intuition on how to make a system particle look pretties and not so jarring. Also because I wanted to.

Okay, so I explained a bunch of stuff about particle systems, but I never really touched up on why I am using what I am using. So let's bring things into perspective. I want a husk that is ice/frost based, so obviously, it needs to shoot out a ball of ice. First thing I did is to copy the package for the husk emitters so I can get a reference for scaling. All these packages are located under steamapps/common/killingfloor2/KFGame/BrewedPC/Packages.
1601083008089.png
Let's bring this bad boy back into the picture. My first emmiter is my rock mesh, which was resized so that it is the same size as the reference husk fireball. now an important thing to go over is your burst lists or your burst rate. In this case, we want a burst list of 1 item at 0 seconds. but it also ahs a emiter loop of 0, so it runs forever.
1601083130842.png
Without any added effects, it's just a rock. (for reference, there are three boxes under each emmiter name, the first disables the effect. The second will change the effect into either dots or lines). A husk projectile works by taking the emmitter and literally just dragging it along the world space. If you don't enable benablelocalspace, the mesh will sit where the husk originally fired it from. So, if the emitter is literally just dragged along, we want some sort of trail to go along with it.
1601083373155.png
This is the second emmiter. What I have done with it is applied a high negative x velocity and a low negative Z acceleration to give it the look of like, fog. The initial size is set to be similair to the mesh object so it looks like it's trailing behind it. Then we changed the color over life to a 1, 1.5, 2 to give it that light sky blue color to be similar to the mesh itself. We also set the alpha over life to fade in quickly and diminish slowly and the color over life to diminish quickly at the end of its life, as we want it to fade out and not appear in and then dissapear suddenly. Theres also a gradual increase in the size by life, as steam and stuff dissapates. We also set the initial rotation to be between 0-1 (randomly) and the rotation rate to be between 0 and 1. This way the sprites don't all look the same when they come out. Finally we have a cynalder that has the same radius as the back of the mesh, so that it will spawn all around it.

1601083732345.png
The third emmiter is basiically the same as the second emitter. But it A: dissapates Faster. B: has a larger initial size. C: has a lower initial transparancy and lower initial color D. Has a much higher spawn rate of entities. This lends to a nice transition effect. The image above this one is quite jarring, with floating, high opacity sprites that can easily be differentiated with the background. This has a nicer slow transition, making the trail seem more fluid.
1601083978074.png
the sixth emmiter does basically the same thing. this time, it has a higher velocity on the x and y axis, and a slow dissapation. Again, lending more to that nice taransition, but not taking away from the direct trail of the mesh.
1601084696639.png
The mesh still sticks out quite a bit, so we want to put some particle effects around it to try and smooth it out and make it less discernable. Put two of the same emitters as 1 and 2 out front and out x and y velocity, to make it look like the fog is gliding along the mesh.
1601084966004.png
The last three emitters are for the idea that the rock is breaking apart while in flight. I added a material that has a lot of noice and changed it the screen alignment in required to be based on velocity. This way it doesn't turn to face the user. we simply to the same thing as the first emitter, only this time with a subUV as it's a material that has four sub images within it (4 horizontal, 1 vertical). Then we add another material that has four sub images, but is just rocks. Put a high initial Z velocity on it but also a high negative Z accelleration. This keeps the rocks in air long behind the mesh but will still make them feel like real objects as opposed to fog, which glides slowly down. We then attach a third emitter to the rocks using emitter initloc, which provides the fog trail (makes the rocks seem cold, or atleast, were a part of the ice as opposed to blue rocks coming out). These can inherit velocity or not. I Didn't on mine as I wanted them to stay at the same spot.

now, its important to note that I coul enable buselocalspace on most of the smoke trails. this would means that this trail sticks with the mesh along it's flight path, as opposed to simply spawning in one location and only following it's initial velocity. I discovered, however, quite by accident, that it looks much better if I don't. IF YOU MAKE A MISTAKE AND IT LOOK GOOD, RUN WITH IT. Happy accidents, as Bob Ross says. It's not really a mistake if it works out better, yeah?

And there we have it! pretty system particle emitter thingy.

Even shorter this time. And from now on these will probably be shorter. I will continue this, as I feel better getting this all down as opposed to stopping and leaving all the knowledge sitting in my head. Will anyone actually use it? Who knows, but if it helps one person, then it was all worth it. Next I will talk ACTUALLY talk about animations, animation trees and archetypes. I swear.
 

Attachments

  • 1601084074666.png
    1601084074666.png
    570.3 KB · Views: 0
Last edited:
Upvote 0
Okay, continuing on. Animations. In the SDK these are called AnimSet.|
Before we begin, I would like to put a reminder here. If you want to edit something, COPY IT. Don't edit the original.

1601176538922.png
This is the halloween husk, as I am creating stuff during the beta preview of 2020's halloween. On the top you have access to the mesh and anim. The mesh is the asscociated mesh model that you want the animations to be performed on (In this case, the halloween husk) as a preview. This will not actually change the model of the pawn.
1601178875237.png
The animation tab consists of all the animations that have been created for the animation set. Now, I haven't quite figured out how to make an animation yet. It seems like all the tools are there to do it, but I just can't quite figure out how to do it.
1601179272544.png
below the Animation and mesh tabs is the skeleton tree. The skeleton tree shows how the bones in the mesh are connected, beginning from the root of the mesh. Each bone that is higher in a tree will control the bones below it (for example, the right foot will move if the rightupleg is moved, but not vice versa)
1601179345069.png
Below the skeleton tree is the properties menu. This holds the mesh and AnimSequence. The AnimSequence is what we really care about, but the mesh tab is the tab that holds information about initial mesh properties, like the original position, the pitch, the material type, etc.
1601179710475.pngThis is the real jam. Theres a lot to go over, so let's take it in steps:
On the left, is the AnimSequence tab in properties. It holds specific points where effects and VFX will play within the animation. For example, At 0.512 seconds, the animation sequence will play a ZedVoiceAkEvent (We know this to be sound banks). This pulls from the package event WW_ZED_Husk_VOX_Attack, which is just a voice clip of the husk making a garbling sound. On the right, is a timeline of the animation sequences. This includes when they happen and what they are. The animation can be controlled with the blue dingy, moving it left or right will change the animation, and move along with it the white arrow in the actual animation frame. This white arrow is the literal correspondance, and will match when the animation sequences play.
1601180941537.png
For example, I moved the blue dingy to the right, and the white arrow moves with it. When the white arrow crosses over the AkVoxAttack, it will play that animation Sequence.

Now, you may have noticed on the right, an ANIMNOTIFY. Yup, there it is. This can be used in the UC code as a notification for when an animation has started, or something maybe occured that you want some code to run when it happens. In this case, ANIMNOTIFY_WarnZedsOfFireball is a warning to zeds within the path of the fireball to move out of the way.

What we want to change is the VFX that plays in this animation, the fireball charge. Why isn't this called in the code as a emitter attachment, is there a good reason? Well, I don't think it really matters. You could call an emitter attachment when the zed begins it's attack, and just attach it to the mesh somewhere. This is just easier as it only occurs on this mesh and it's much more robust.

So all we need to do is select out animation sequence where the VFX is played, and replace the emitter attachment with our own premade one. And will you look at that.
1601180310848.png
We got our own Ice ball that is now being played instead of a fireball (I have another emitter for this.)

Now, there is something special within this animation set. The flamethrower attack.
1601180406815.png
Why is there no SFK for this? Well, we use an archetype to create a flamethrower effect in the code instead of attaching it to the husk in the animation set. This is because the game relies on the mesh connection in a spray attack (I believe), so we instead have an archetype that attaches a bunch of sfx's onto a mesh.

I will talk about this when I get back to it. Unfortunately, I stayed up very late last night and have had to squeeze everything together today. But, it's a weeekend so i'm allowed to do this. I'll be away for the rest of the weekend, so I may not be posting anything about the SDK. I will probably go back to the UC code and talk about some things that i skipped over. Or maybe I'll go on a tangent, who knows.
 

Attachments

  • 1601179572101.png
    1601179572101.png
    899.7 KB · Views: 0
Last edited:
Upvote 0
And we are back.

Continuing with animation trees

1601435969714.png

Now, I'm not too savvy on these guys, as I haven't actually done anything to them. However, I'm pretty sure their most prominent feature is the different gradients between animations and conditionals that cause them. Basically, It mostly controls how an animation transitions from one animation sequence to another, and when they should happen based on in code calls like bPlayPanicked or bPlayShambling. This isn't really important to us as we are not messing with animations right now, but it's something to keep in mind as it is needed to make the whole mesh

Earlier, I mentioned the flamethrower animation sequence, and how it had no VFX play for the actual flamethrower. I will now go over the archetype of the flamethrower as it is distinctly different from the archetype of the husk itself.

But first, let's define what an archetype is. An archetype is basically the culmination of all front end properties to make some, 'thing'. For example, the husk, or a flamethrower spray. Whenever you want some sort of special property that will interact with a mesh, an archetype is basically required for the back end UDK code to operate with it. otherwise you will have to rely on default interactions between the player and static meshes, like the ground. Archetypes also control things like animation and sound effects for specific actions.
1601437955735.png
Here is the archetype for the cryothrower (as I have named it, freeze thrower. I'm not very good at naming things.) As you can see, there are 14 somethings in a list here. These are the bones of the mesh that is defined further down in the archetype. You can specify special attributes of the bone using this, which in our case is a added VFX. Adding a VFX to every bone in the cryothrower mesh gives us the adde particle systems effect of particles of fog flying off the mesh as opposed to the mesh itself, which is just this
1601438229511.png
This mesh comes from the cryogun provided to the player(yay hijacking!) The animation and animation tree that I created are literally just copies of the flamethrower animation and animation trees for the husk, which is under the Wep_flamethrower package. I simply replaced the mesh model for the flamethrower with the mesh model for the flamethrower in the freezethrower animation
1601438529082.png
Then created a new animation tree calling this new animation. You could also just use the cryo animation tree and animation. ( I was still learning at the time and didn't realize this). Be careful when making a new animation like this. For some odd reason, it will sometimes revert the skeletal mesh back, without me authorizing it. This will change the mesh for the animation in game. This confuses me also as the archetype will need a specific mesh to use regardless. Strange stuff.
Back to the archetype.

I don't know if the MyDamageType matters in game at all, as you specify the impact damage for the projectile in game.

1601438796727.png
The spray end effect is the end particle system that is played when the player (or husk, in this case) has ended their attack.

The skelmesh is the specific mesh that the archetype should use. The spray animation set is the animation it should play (just one in this case) and the animation tree is the transition effects the animation will use.

The spraysplash in this case are the different VFX particle systems and AkSoundBanks that are used whenever a impact occurs. Each has a different condition attached to it and will be called on if the conditions are met.

This is why the mesh is used instead of a basic particle system, as there are a lot of special properties. Hence, the use of an archetype.

Now, the husk archetype is a bit different, as they use a different archetype type. Tripwire stated many time they use a custom version of UDK, and this is really where I see it the most (Maybe I'm just blind, as I have never actually used the base UDK), custom archetypes

1601439152925.png
Some similarities here. Attachment of particle systems to bone sockets. however, there are some differences. Doorhit sounds are the aksoundbankds that are used when the zed hits a door. Impact skills are the different mesh and particle effects when they are hit.

1601439324792.png
here's what really matters, and why I wanted to go over this archetype. The animation sets are more than one. You can make a bunch of different, distinct animation types and use them inbetween different archetypes. Laziness prevails!

And there you have it. From textures all the way to archetypes, we have a bunch of packages in front end UDK to be called upon by the back end UC. It all works together to make KF2. So what do we get for all our hard work?


Now, obviously, there is some stuff in these videos that I haven't actually pointed out. I can go over this stuff (like the suicide effect and why the husk glows different), but it's mostly just extra fluff stuff. So unless someone requests that I delve into them, I really don't want to. However, there is UC stuff hat I haven't touched, and I AM going to go back over those. I will bring everything back up and now that everythings laid out, connect the dots to everything so that it all makes sense.

But for now, I'm cooking and want some ice cream.
 
Upvote 0
Last post! I think.

Now that we've expained the backdrop of whats going on, ti will be easier to slide through the UC script. i will be starting farther along, as some things have already been explained. What's more important is starting with code where SDK packages are used in the UC. Most of this post is me going over the connections, with nothing too important unless you wanted to cement understanding of what's being called where.

For example: In KFSM_HumanFreeze
Code:
        // create a particle system, which is a frozen block
        FrozenHuman = new(self) class'ParticleSystemComponent';
        FrozenHuman.SetTemplate( ParticleSystem'KFP_CryoTrail.FX_Human_Freeze' );
        KFPOwner.Mesh.AttachComponentToSocket( FrozenHuman, 'root' );
        FrozenHuman.ActivateSystem();

It should be fairly easy to draw a conclusion as to what this does. Defines a new particle system (a emitter or series of emitters), sets the template for the particle system (A block of ice that surrounds the player. As a side note, if you want to make it look like something is melting, don't use a burst list of one mesh, instead remove the burst list and allow the spawn rate to take over about 3-4 meshes. When it collapses on itself it will make it look like the mesh is melting.).
1601608463776.png
Then it attaches the particle system to the root of the mesh (The very first bone socket, to which all other bones are connected to), then activates it.

We also have camera effects like this:
Code:
LensEffectTemplate=class'KFCameraLensEmit_FullFrost'
And if we look at the class itself for the came lens effect:
Code:
class KFCameraLensEmit_FullFrost extends KFEmit_CameraEffect;

defaultproperties
{
    PS_CameraEffect=ParticleSystem'KFP_CryoTrail.FX_Camera_FullFreeze'
    // disallos multiple instances of the emmiter, as we will not call this again
    bAllowMultipleInstances=false
    LifeSpan=2.0f
    bDepthTestEnabled=false
}
1601609023466.png

This essentially just sticks the particle emitter (FX_Camera_FullFreeze) to the front of the users screen. because it uses buselocalspace, it will always center on the users screen (I think if you extended the FOV enough, you may be after to see past the particle system).

With this we move onto our main husk
Code:
simulated function UpdateGameplayMICParams()
{
    Super.UpdateGameplayMICParams();
 
    if( WorldInfo.NetMode!=NM_DedicatedServer )
    {
        CharacterMICs[0].SetVectorParameterValue('Vector_GlowColor', MainGlowColor);
        CharacterMICs[0].SetVectorParameterValue('Vector_FresnelGlowColor', MainGlowColor);
        CharacterMICs[0].SetScalarParameterValue('Scalar_Ice', 0.25f);
    }
}

The parameter values in this case is a change in the color of the backdrop for the material (I forget whether the glowcolor or fresnel flow color handle this). basically, anywhere there is alpha channel, it will show this glow color . Changing the parameter will change the color of the backdrop on the fly, you could even make a Tick function that makes zeds eyes into rainbows if you wanted to (Which would be pretty sick). This is why my husk has a blue core when he opens his chest plate. The GlowColor controls the color of this core. The scalar ice is a scalar parameter that adds a frost effect to the material. I wanted my husk to have bits of frost on it, so I set it to 0.25. This can break, however, as when a zed gets frozen, it will reset the scalar_ice back to 0 when it un-freezes.

Code:
simulated function ANIMNOTIFY_FlameThrowerOn()
{
    if( IsDoingSpecialMove(SM_HoseWeaponAttack) )
    {
        KFSM_Husk_FreezeThrowerAttack(SpecialMoves[SpecialMove]).TurnOnFlamethrower();
    }
}

/** Turns medium range flamethrower effect off */
simulated function ANIMNOTIFY_FlameThrowerOff()
{
    if( IsDoingSpecialMove(SM_HoseWeaponAttack) )
    {
        KFSM_Husk_FreezeThrowerAttack(SpecialMoves[SpecialMove]).TurnOffFlamethrower();
    }
}

we can also see my totally not copied ANIMNOTIFYs. I have mentioned them several times before, but now you can understand how it works. The husk Special move starts, starting the animation. The animation reaches the ANIMNOTIFY, and calls back to start the husk attack. This begins the freezethrower attack, which in turn begins the animation sequence for the animtree (which is simply just the cryo guns like, freezy mesh extending out, or whatever). Mesh out, collision now applies due from the archetype, so whenever you run into the mesh, it will do KFDT_ZedFreeze damage. Then the animation ends, ending the FreezeThrower attack, in turn ending the animation and then the husk ends it's animation. It's a big back and forth.


Code:
    FireballClass= class'ihd_edit.KFProj_Husk_Freezeball'
    // dinnae what does it, but some special seasonal handler adds HALLOWEEN_ to the beginning
    // based on the SEI_ID
    // A. very poor coding practice, why not just hold the seasonal contents in a folder and then
    // assign index values to folder names in a native function? pretty common practice in C
    // B. I suspect it's the boss cache as that's the only function that messes with MonsterArchPath
    // but it's native(C++) so whatever
    MonsterArchPath="Frozen_ZED_ARCH.ZED_Husk_Archetype"

i didn't really go over this before, but whenever a update comes out, it will set the SEI_ID (Seasonal Event Index i think) to a value from 0-3 ( with non definition being no seasonal event). When that happens, SOMETHING adds a string literal to the beginning of the MonsterArchPath, in this case "HALLOWEEN_". This allows it to use the "HALLOWEEN_Frozen_ZED_Arch" as opposed to the "Frozen_ZED_Arch". I will make a quick point to say that this is poor coding practice, as it forces a literal to always be required as a precursor to package name ALWAYS. Of the top of my head I can't think of negative effects time wise, string creation manipulation is slower than indexing, but not enough for it to matter. It just becomes annoying and can bloat files if the required folder becomes more integral to the framework of a program. This is of course, off topic and not really important. (I also practice a lot of code copying in the mod, so I'm a bit of a hypocrite when it comes to bad coding practice).

Regardless, the main point of the code snippet is the FireballClass and the MonsterArchPath. The FireballClass is the class used to create the fireball, including damage type and particlesystem, and the MonsterArchPath is the archetype the mod uses, animations and all. As you can see these have been change. one is my own class and the other is the husk archetype that I have shown before.

and if you look into the FireballClass we have...
Code:
    MyDamageType=class'KFDT_Husk_FreezeSuicide'
    ExplosionEffects=KFImpactEffectInfo'ZED_HuskFrozen_ProjExp.FX_FreezeBall_Projectile_Explosion'
    ExplosionSound=AkEvent'WW_WEP_Freeze_Grenade.Play_Freeze_Grenade_Shatter'


    GroundFireExplosionActorClass=class'KFExplosion_GroundIce'
    ProjFlightTemplate=ParticleSystem'KFP_CryoTrail.FX_Husk_projectile_frozen'

Eyyy. My own impact effect info for my projectile explosion. This is a seperate archetype created for the freezeball that determines the effect that plays on hit. Yea, nothing else to that. I'm assuming the reasoning behind this is the allow for more generality. Use an arch for all projectiles, as some need more special effects of sounds than a basic one like the husk fireball. The effect I'm using for it is basically just a ice ball created from before that quickly shirinks. A word of advice, you have a mesh that has a sudden change of size or simply just dissapears, add a bunch of noise around it to cover it up. The transition looks a lot better if you have some effect that plays along with the sudden change in size.
1601610930679.png
We also have the ProjFlightTemplate. This is the particle system used in the flight path for the projectile. In my case, the husk freezeball. (as shown earlier)

Now, moving on the flamethrower, there were some important things here that I skipped over (mostly because I was tired)

Code:
       // Animation
    AnimName=Player_Flame
    AnimStance=EAS_FullBody

    // Flamethrower
    FlameSprayArchetype=SprayActor_Flame'ZED_HuskFrozen_ARCH.Husk_Freezethrower_Freeze'

There is the animation name for the flamethrower attack. If you look back at the animations sets, you will notice that this matches the gives animation for the flamethrower animation (No duh). But it also matches teh base husks animation name. This means that there is an allowed override for the halloween husks animation to overtake the base husk_master animation sets animations.

There is also the arch for the flamethrower archetype we made.

Code:
        MyFlameSpray = HuskOwner.Spawn(FlameSprayArchetype.Class, HuskOwner,, HuskOwner.Location, HuskOwner.Rotation, FlameSprayArchetype, TRUE);

        // Use a particular ImpactProjectileClass that will scale damage by difficulty
        MyFlameSpray.ImpactProjectileClass = class'KFProj_HuskGroundFrost';
        MyFlameSpray.OwningKFPawn = HuskOwner;
        MyFlameSpray.SetBase(HuskOwner,, HuskOwner.Mesh, MyFlameSpray.SpraySocketName );

        if( HuskOwner.WorldInfo.NetMode != NM_DedicatedServer && PSC_EndSpray != None )
        {
            if( PSC_EndSpray != None)
            {
                PSC_EndSpray.SetTemplate(MyFlameSpray.SprayEndEffect);
            }
            HuskOwner.Mesh.AttachComponentToSocket( PSC_EndSpray, MyFlameSpray.SpraySocketName );
        }

So when we look above, we can see our flame archetype being used in a template spray effect, attached to a socket name which is kept in the archetype. There is also the huskgroundfrost damage type, which I did not talk about before.


Code:
class KFProj_HuskGroundFrost extends KFProj_Groundice
    hidedropdown;

DefaultProperties
{
    // explosion
    Begin Object Name=ExploTemplate0
        Damage=6
        MyDamageType=class'KFDT_Husk_FreezeThrower'
    End Object

    DamageInterval=0.25f
}

All this is is another projectile type. A much simpler one that simply has a damage amount, an interval, and a damage type. If you're wondering about whether KFDT_Husk_Freezethrower is special:


Code:
class KFDT_Husk_FreezeThrower extends KFDT_ZedFreeze;
// freezethrower damage for the husk
static function PlayImpactHitEffects( KFPawn P, vector HitLocation, vector HitDirection, byte HitZoneIndex, optional Pawn HitInstigator )
{
    local float ParamValue;
    local int MICIndex;

    MICIndex = 0;
    if (P.GetCharacterInfo() != none)
    {
        MICIndex = P.GetCharacterInfo().GoreFXMICIdx;
    }

    // If we're dead and not already frozen (prevents re-shattering)
    if ( P.bPlayedDeath
        && P.CharacterMICs.Length > MICIndex
        && P.CharacterMICs[MICIndex].GetScalarParameterValue('Scalar_Ice', ParamValue))
    {
        if (ParamValue == 0)
        {
            PlayShatter(P, false, `TimeSinceEx(P, P.TimeOfDeath) > 0.5f, HitDirection * default.KDeathVel);
            return;
        }
    }

    if(P !=None && P.Health>0 && P.AfflictionHandler!=None )
        // accrue some freeze
        P.AfflictionHandler.AccrueAffliction(AF_Freeze,14.f);
   
    Super.PlayImpactHitEffects(P, HitLocation, HitDirection, HitZoneIndex, HitInstigator);
}

defaultproperties
{
}

I assure you, it's just the same as the rest of the damage types.

And I think that's it! Quite the long guide, but surprisingly, this is less than I wanted to put. I would like to put more in depth stuff in here, but I really just don't have the knowledge, or the time and patience to get that knowledge.

All this info cobbled together? Took two weeks of time to figure out. Imageine if something like this guide was out before, if someone just explained how to make a special move, or idk, how affliction works. I could have finished this in a week, or even less! I've seen a lot of complaints about how few modders there are, and some of them are justifyable. Tripwire has dropped the ball, theres things that should have been fixed 5 years ago that are still issues today. Not even hard issues to fix, simple stuff. But, the modding sphere is a very scary place to get into. Not because of the issues alone, but because of how little documentation there is. I believe this is the most in depth guide out there right now, though I may be wrong, and that baffles me. This guide took me an hour a day for maybe, 6 days to write. If all the modders out there, who have now quit modding, left bread crumbs of what they did, maybe a small guide on how a certain file operates, or something, then I believe this issue would have been mitigated, atleast a bit. This is mostly my justification for writing this whole piece. if it helps one person get into modding, even when many believe the game is on it's downhill spiral, maybe they could be someone who would create a mod to bring interest back in. many people are more interested in bashing the game and simply abandoning it. Unfortunately, it seems like the era of co-op wave shooters is over, atleast to a degree, so there's not much else to turn to.

But, this is just my opinion, I like to be optimistic to a degree. Regardless, there's probably many things incorrect in the guides. Point them out, or if you want, ask a question. Even if you don't see anything, leave a nice comment or something. It would justify doing more of these.
 
Upvote 0
Good stuff, but there's a caveat here for materials.

You absolutely cannot use custom materials on then workshop unless you are making a map, they don't function. KF2 was compiled with the --noeditor argument, and so custom materials do not compile at runtime for the game, so anything using one uploaded to the workshop will get purple and white checkerboard textures.

Additionally, some of the base game materials also exhibit this behavior, such as the masked first person weapon material.
 
Upvote 0