• 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/

Question about converting custom gamemode to a mutator

Bardyss

Member
Dec 29, 2018
13
7
29
Hello, I would like to make Versus balancing mutator. Just go with basic and simple numeric changes for a start and sake of keeping this request relatively concise(like damage multipliers or SprintSpeed). I know just some basics of C++ and watched a beginner's series on UnrealScript.

I managed to do so as a custom gamemode but would like to turn it into a mutator*.


Code:
class VersusBalance extends KFGameInfo_VersusSurvival;
 
DefaultProperties
{          
PlayerZedClasses(AT_Bloat)=class'VersusBalanceMod.[B]KFPawn_ZedBloat_VersusBalanced[/B]'
….
}

Code:
class [B]KFPawn_ZedBloat_VersusBalanced[/B] extends KFPawn_ZedBloat_Versus;
 
defaultproperties
{
DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Submachinegun',        DamageScale=(0.35)))
SprintSpeed=9999999999999
….
}

KFGameInfo_VersusSurvival has plenty of stuff already in it's default properties and especially PlayerZedClasses which allows me to point the gamemode to my own Zed classes derived from base versus ones.

KFMutator ->Mutator->Info->Actor… and other core game classes. No noob-obvious way to get to the good stuff.

Is it even possible to change what I intend or are mutators meant for changing core gameplay/game engine things? If yes could someone push me in the right direction?(like show how somebody would go about changing versus player bloat's SprintSpeed in a mutator)?

Thank You


* Custom Gamemode shows in the server browser as "ANY” in MODE type. This will drastically limit server's exposure to already severely limited Versus playerbase when filtering for "Versus” servers.
 
  • Like
Reactions: simplecat
Gamemodes are easier to work with as a beginner, but often you will want to instead use a mutator for compatibility reasons. As you're likely aware, you can run multiple mutators simultaneously, but only a single gamemode. However, compatibility issues are really not a major concern for a versus mod.

Ideally, for compatibility reasons, avoid overriding classes like zeds and weapons - although few mods replace these. If possible, use mutator hooks such as NetDamage and CheckReplacement to modify damage or other properties of zeds, weapons, players, or any Actor. See https://wiki.beyondunreal.com/Legacy:CheckReplacement for an example of the CheckReplacement pattern. The additional damage-type modifier you have shown can easily be added to any spawned bloat in CheckReplacement:
Code:
function bool CheckReplacement(Actor Other)
{
    local KFPawn_Monster.DamageModifierInfo DamageTypeModifier;
    local array<float> DamageScale;
    
    DamageScale.AddItem(0.35);
    
    DamageTypeModifier.DamageType = class'KFDT_Ballistic_Submachinegun';
    DamageTypeModifier.DamageScale = DamageScale;
    
    //If this is a versus bloat.
    if(KFPawn_ZedBloat_Versus(Other) != None)
    {
        //Add our custom damage-type modifier.
        KFPawn_ZedBloat_Versus(Other).DamageTypeModifiers.AddItem(DamageTypeModifier);
    }
    
    return Super.CheckReplacement(Other);
}
Of course you would want a more concise and automated approach for multiple such modifiers, e.g. by making a list of modifiers in the mutator itself which could be configurable. See Project One for an example of this approach: https://forums.tripwireinteractive....115125-mutator-project-one/page15#post2294939. I can provide source code for the relevant class in the update version of the mod, KFC, if you wish.

As for modifying the speed of zeds, it looks like TW have thankfully added NormalGroundSpeed and NormalSprintSpeed variables which you can modify externally, in addition to the current GroundSpeed and SprintSpeed.

Some things will be easy to do with a mutator without having to replace classes, while others will be impossible without replacing classes. It will require some digging to figure out if what you want to achieve can be done easily. Typically, variables changing values during play without corresponding mutator hooks will make it more difficult. You can change quite a lot with mutators if you can wrap your head around how best to inject your desired functionality into what events you have to play with. One of the largest mods for KF2, ServerExt, is in fact a mutator - but it replaces most of the major classes so it loses the flexibility of compatibility anyway.
 
  • Like
Reactions: Bardyss
Upvote 0
Greatly appreciated. I now have another dilemma however. This is how dmg multipliers look for alpha clot:

Code:
Alpha Clot

    // Base
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Submachinegun',     DamageScale=(1.5)))  //3.0
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_AssaultRifle',     DamageScale=(1.0)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Shotgun',             DamageScale=(1.0)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Handgun',             DamageScale=(1.01)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Rifle',             DamageScale=(1.0)))  //0.76
    DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing',                     DamageScale=(0.85))) //0.75
    DamageTypeModifiers.Add((DamageType=class'KFDT_Bludgeon',                     DamageScale=(0.9))) //0.75
    DamageTypeModifiers.Add((DamageType=class'KFDT_Fire',                         DamageScale=(1.0)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Microwave',                     DamageScale=(0.25)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Explosive',                     DamageScale=(1.0)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Piercing',                     DamageScale=(1.0)))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Toxic',                         DamageScale=(1.0))) //0.88
    DamageTypeModifiers.Add((DamageType=class'KFDT_Bleeding',                     DamageScale=(1.0)))


    // Special Case
    DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing_Knife',              DamageScale=(1.0)) //0.95

Code:
Alpha Clot Versus

    //Base
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Submachinegun',     DamageScale=(0.8)))  //3.0
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_AssaultRifle',     DamageScale=(0.7)))  //1.0 //0.5
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Shotgun',             DamageScale=(0.5)))  //0.9 //0.4
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Handgun',             DamageScale=(0.4)))  //1.01
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Rifle',             DamageScale=(0.5)))  //0.76
    DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing',                     DamageScale=(0.8)))  //0.5
    DamageTypeModifiers.Add((DamageType=class'KFDT_Bludgeon',                     DamageScale=(0.8)))  //0.5
    DamageTypeModifiers.Add((DamageType=class'KFDT_Fire',                         DamageScale=(1.0)))  //0.8 //0.5
    DamageTypeModifiers.Add((DamageType=class'KFDT_Microwave',                     DamageScale=(0.35)))  //0.25
    DamageTypeModifiers.Add((DamageType=class'KFDT_Explosive',                     DamageScale=(0.35)))  //0.85
    DamageTypeModifiers.Add((DamageType=class'KFDT_Piercing',                     DamageScale=(0.4)))   //1.0
    DamageTypeModifiers.Add((DamageType=class'KFDT_Toxic',                         DamageScale=(0.5)))  //0.88 //1.0

    // special case
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_AR15',              DamageScale=(1.0))
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_MB500',              DamageScale=(1.0)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Rem1858',              DamageScale=(0.75)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Colt1911',          DamageScale=(0.65)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_9mm',                  DamageScale=(1.6)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Pistol_Medic',      DamageScale=(1.5)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Ballistic_Winchester',          DamageScale=(0.6)))  //0.9 0.7
    DamageTypeModifiers.Add((DamageType=class'KFDT_Fire_CaulkBurn',              DamageScale=(2.0)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_ExplosiveSubmunition_HX25',      DamageScale=(0.6)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing_EvisceratorProj',      DamageScale=(0.3)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Slashing_Eviscerator',          DamageScale=(0.3)))  //0.9
    DamageTypeModifiers.Add((DamageType=class'KFDT_Bludgeon_Crovel',              DamageScale=(1.2)))  //0.8

I want to set all zeds to multipliers from base Survival as a starting point for balance tweaking. In the special dmg cases put in VS the only way to get rid of them is to let's say set 'KFDT_Ballistic_AR15' in VS to the value of 'KFDT_Ballistic_AssaultRifle' from Survival?
 
  • Like
Reactions: simplecat
Upvote 0
You can remove elements of a dynamic array in the defaultproperties by replacing Add with Remove. In this case you would have:
Code:
DamageTypeModifiers.Remove((DamageType=class'KFDT_Ballistic_Submachinegun',     DamageScale=(1.5)))  //3.0
DamageTypeModifiers.Remove((DamageType=class'KFDT_Ballistic_AssaultRifle',     DamageScale=(1.0)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Ballistic_Shotgun',             DamageScale=(1.0)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Ballistic_Handgun',             DamageScale=(1.01)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Ballistic_Rifle',             DamageScale=(1.0)))  //0.76
DamageTypeModifiers.Remove((DamageType=class'KFDT_Slashing',                     DamageScale=(0.85))) //0.75
DamageTypeModifiers.Remove((DamageType=class'KFDT_Bludgeon',                     DamageScale=(0.9))) //0.75
DamageTypeModifiers.Remove((DamageType=class'KFDT_Fire',                         DamageScale=(1.0)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Microwave',                     DamageScale=(0.25)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Explosive',                     DamageScale=(1.0)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Piercing',                     DamageScale=(1.0)))
DamageTypeModifiers.Remove((DamageType=class'KFDT_Toxic',                         DamageScale=(1.0))) //0.88
DamageTypeModifiers.Remove((DamageType=class'KFDT_Bleeding',                     DamageScale=(1.0)))
I believe if the DamageScale of any modifier is altered, then it won't be found and thus removed from the array. This means your mod will be sensitive to damage resistance changes, which the devs might decide to revisit. Alternatively, you could do:
Code:
DamageTypeModifiers.Empty
to clear the array, and then add whatever you want after that.
 
  • Like
Reactions: Bardyss
Upvote 0
In the case of gamemode I could do it from defaultproperties but here I don't inherit from appropriate stuff. How can I remove it in there instead of CheckReplacement function? I'm confused.

Edit: To avoid confusion: the code in my earlier post is the source code from game base files, not something I had in mutator
 
Last edited:
  • Like
Reactions: simplecat
Upvote 0
Okay, I did the generic multipliers but i still can't remove the specific ones. Whether I use "DamageTypeModifiers.Remove….." or "DamageTypeModifiers.Empty" it doesn't seem to work.

Either "Warning, Unknown property in defaults:” when I put any of the two in defaultproperties or other errors if I try elsewhere like "KFPawn_ZedClot_Alpha_Versus(Other).Empty;”

https://drive.google.com/open?id=1vDPAsp3FSS8cCimgS0XGk9fmONtoPJfc
 
  • Like
Reactions: simplecat
Upvote 0
I've tried to compile your file and it does not give me any errors (haven't tried it in game though).

Have you tried to use
Code:
MyArray.Length=0
instead?

Also, i don't mean to highjack your thread or to point what's wrong and what is right, but your code does look like it could use some optimizations. Here's my view on how i'd make it, all the ZED resistances go to default properties block. That's just an example, it's up to you fill it with content.

https://pastebin.com/YxqzXkJ4

Spoiler!

I'm not much of the coder, don't judge :(
 
Last edited:
  • Like
Reactions: Bardyss
Upvote 0
.Length=0 seems to work, thanks.
Is it not optimal just in terms of readability or performance/stability? Honestly if it works I just want to keep going with other stuff :)


Does somebody know what is the difference between "spawnzedv" and "spawnzedvc"œ commands(except for the one that spawnzedv can be used to possess)? Both spawn zeds affected by my multipliers but not exactly 1:1 which seems strange.
Also on "spawnzedv" works hp change which I did with "KFPawn_ZedClot_Alpha_Versus(Other).Health=10;" but it doesn't on "spawnzedvc"œ and VS zeds normally spawned during gameplay.
Can I trust the "spawnzedvc"œ to show me real effects my mutator will make?

How should I go about HP change-body, head(and maybe Husk backpack health too please?), somehow in defaultproperties?

Could you also tell me how to create cash earning multiplier? Would like to make humans earn it a bit quicker.
 
Last edited:
  • Like
Reactions: simplecat
Upvote 0
I don't think that .empty is gonna work anywhere outside of the defaultpropetries block which if different from the rest of the code syntax-wise.

As for command difference, you can always check these in files. Cheat commands are stored in KFCheatManager.uc

From what i can tell they both identical. The difference being is that SpawnZedV spawns dummy pawn with PlayerController over it, similar to SpawnHumanPawn. And SpawnZedVC spawn controller-less dummy unless you willing to posses it. Can't tell what are the pros or cons of having controller over the zed pawn spawned in such manner. Must have it usage somewhere.
 
Last edited:
  • Like
Reactions: Bardyss
Upvote 0
How should I go about HP change-body, head(and maybe Husk backpack health too please?)

I would suggest grabbing Versus game info and replacing its PlayerZedClasses with your own classes where later you could adjust whatever you want but this variable is protected hence this can only be done via making custom (child) versus game.

I dunno, guess i'd stick with CheckReplacement. From what i see it kicks in after instance of pawn is spawned so you might want to re-run its essential functions again. If you willing to modify health of your ZEDs you'd need to do something like that

Spoiler!


i.e. grab gameinfo and a ZED, give it a health and modify it via gameinfo again. There probably is more efficient way to do it, but well...


Could you also tell me how to create cash earning multiplier? Would like to make humans earn it a bit quicker.

This one is possible via InitMutator event.

Get gameinfo and replace its DifficultyInfoClass with your own.

Spoiler!

Create (expand) your difficulty class based off KFGameDifficulty_Versus and adjust dosh scale there.
 
  • Like
Reactions: Bardyss
Upvote 0
Did you mean me to replace "KFPawn_Monster" in
Code:
currentZED = KFPawn_Monster(Other);
with explicit KFPawn like "KFPawn_ZedClot_Alpha_Versus" or maybe put
Code:
currentZED = KFPawn_Monster(Other);
CurrentZED.Health = 200;

KFGI.SetMonsterDefaults(CurrentZED);

inside
Code:
if(KFPawn_ZedClot_Alpha_Versus(Other) != None)
condition?

I mean: How to distinguish between zeds?

If not well explained: https://drive.google.com/open?id=1deGXIlFBHFP1WKDI1Pfug3MV0JHf0cJX

Lines:426, 429, 442,1116,1117,1119
 
  • Like
Reactions: simplecat
Upvote 0
Both of your methods will work too. Its only a matter of how many lines of code you'd need to write and presumably some optimization. Now i don't know how resource intensive the type casting is but i think the less it happens - the better. Plus you'd want to exclude all non versus ZEDs in the beginning.


I'd cast me whatever this monster is and then would start checking what is it, zed by zed.

Check the example in my 1st message

https://pastebin.com/YxqzXkJ4
Code:
currentZED = KFPawn_Monster(Other);

if (currentZED !=none && currentZED.bVersusZed)
    {
        if(currentZED.IsA('KFPawn_ZedBloat_Versus'))
            {
            // do Bloat specific stuff
            }
    // next if
 
  • Like
Reactions: Bardyss
Upvote 0
I still can't get it to work. Should I maybe use the keyword "default" or try using HealthMax(then I would set both Health and HealthMax i guess)?

From Pawn.uc

Code:
var() int Health;        /** amount of health this Pawn has */
var() int HealthMax;        /** normal maximum health of Pawn - defaults to default.Health unless explicitly set otherwise */

Could the health be somewhere "explicitly set"(all this talk about having to set values multiple time in mutators makes me believe so)


Edit: Maybe Game Conductor's fault?

From KFGameInfo.uc

Code:
    // Scale health and damage by game conductor values for versus zeds
    if( P.bVersusZed )
    {
        DifficultyInfo.GetVersusHealthModifier(P, LivingPlayerCount, HealthMod, HeadHealthMod);

        HealthMod *= GameConductor.CurrentVersusZedHealthMod;
        HeadHealthMod *= GameConductor.CurrentVersusZedHealthMod;
....
}

Use HealthMod maybe or somehow make sure that my changes are after any Game Conductor's changes?

Edit2: Just realized, that if I were to just change health it won't scale with LivingPlayerCount. So even more I would like to change this Health mod or other Health multiplier, not the health itself.

Edit3: Seems I did it with GameConductorClass and "CurrentVersusZedHealthMod=1.5"

Code:
function InitMutator(string Options, out string ErrorMessage)
{
local KFGameInfo KFGI;
KFGI = KFGameInfo (WorldInfo.Game);
    if (KFGI.IsA('KFGameInfo_VersusSurvival'))
        {
        KFGI.DifficultyInfoClass=class'VersusBalanceMod.VersusBalanceDifficulty';
        KFGI.GameConductorClass=class'VersusBalanceMod.VersusBalanceGameConductor';
        }
super.InitMutator(Options, ErrorMessage);
}



Now I will have to check the classes in KFGameInfo for some way to alter head health(unless it has been affected as well?)
 
Last edited:
Upvote 0
I think i saw your most recent post and now its gone (i guess it got marked by anti spam system).

You were asking about health and how you can't change it that way. You were right, there is indeed no way to change ZED's health via CheckReplacement, my bad. At least from what i know. CheckReplacement happens in actor's PreBeginPlay (at the very beginning, i was wrong about saying that it kicks in after ZED's loaded), by this moment ZEDs are not fully set yet - their health calculation happens later. In SetMonsterDefaults, where they refer to their default health variables.

From what i understand there is no real way to alternate such static (default) variables other than expanding the class. You won't be able to declare statements like these successfully:
Code:
CurrentZED.class.Health = 100;
CurrentZED.default.Health = 100;

I googled a bit and it appears that in older versions of engine, there was a function ReplaceWith within CheckReplacement that would allow you to replace one class with another, unfortunately i can't see such function in kf2.

I can suggest this workaround

Spoiler!

It's pretty stupid and i'm not sure whether it's gonna work online well (it works ok offline, at least). The goal there is to catch player pawn's controller class, destroy its pawn and then replace it with your own. Kinda like brain surgery, but instead of replacing a brain you actually replace the body itself.

You should then create your own ZED classes in your package folder and put all the stuff you'd like to replace there (health, damage modifiers etc).

Yea, i know it's like completely different from what you've done already but so far i can't think of the other ways to change ZED's health.

Also i should warn you in advance. This method only works with ZEDs spawned normally (via RestartPlayer). If you willing to test in offline it won't work with SpawnZEDV - you'll need to start versus game, SpawnHumanPawn 0 1, and then switch your team to ZEDs.
 
Last edited:
  • Like
Reactions: Bardyss
Upvote 0
Thanks :D For now I will see if my way fully works with GameConductorClass and "CurrentVersusZedHealthMod=1.5". I want the health to still be altered by GameConductor (0.8x-2x depending on average humans lvl). If yes I will only need to think about nerfing Patriarch whose damage multipliers are same in Survival and VS(maybe remove his heals?- healing is pointless mechanic in VS anyway if he almost can't be killed during them)

I assume your way would break GameConductor scaling?
 
Last edited:
Upvote 0
You mean these custom ZEDs will not be affected by GC? Don't think so. Worked well when i tested, had a Crawler with 20 default hp and Alpha Clot with 2000. At some point, during the game their HP was altered to 17 and 1700 which is the GC's minimum of x0.85. GC in SetMonterDefaults should work regarldess of ZED class as long as this ZED is bVersusZed.
 
  • Like
Reactions: Bardyss
Upvote 0
There's no real way to get access to constant variables during the run time that way (both RegenerationInterval and RegenerationAmount are const).

However, you can modify the variable TimeUntilNextRegen which stores the current time until next regen tick.

Health regeneration in Perk happens in Tick event, i don't think you can really stop it from happening. The method below is to delay (roll back) the current TimeUntilNextRegen of Perk and to set this var for your own via 2 timers from within the mutator class.

This (much like everything i suggest) does not look healthy at all. It's only gonna work with the Berserker which is the only perk that allows health regeneration. If you want to add health regen to other classes you'd need to (pretty much) copy the entire body of the function TickRegen (from Perk.uc) to your mutator class and then assign its exection to any looping timer while maintaining proper references.

Spoiler!
 
  • Like
Reactions: Bardyss
Upvote 0