KF2-SafeMutLoader/DEV.md
2023-05-14 04:41:54 +03:00

5.5 KiB

[SML] Developer Guide

SML compatible mutator development guide

Mutator template

You can use this template to make the mutator compatible with SML. Here I will use Example as mutator name. Replace it with yours.

ExampleMut.uc

class ExampleMut extends KFMutator;

var private Example Example;

public simulated function bool SafeDestroy()
{
    return (bPendingDelete || bDeleteMe || Destroy());
}

public event PreBeginPlay()
{
    Super.PreBeginPlay();

    if (WorldInfo.NetMode == NM_Client) return;

    foreach WorldInfo.DynamicActors(class'Example', Example)
    {
        break;
    }

    if (Example == None)
    {
        Example = WorldInfo.Spawn(class'Example');
    }

    if (Example == None)
    {
        `Log("Example: FATAL: Can't Spawn 'Example'");
        SafeDestroy();
    }
}

public function AddMutator(Mutator Mut)
{
    if (Mut == Self) return;

    if (Mut.Class == Class)
        ExampleMut(Mut).SafeDestroy();
    else
        Super.AddMutator(Mut);
}

public function NotifyLogin(Controller C)
{
    Example.NotifyLogin(C);

    Super.NotifyLogin(C);
}

public function NotifyLogout(Controller C)
{
    Example.NotifyLogout(C);

    Super.NotifyLogout(C);
}

static function String GetLocalString(optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2)
{
    return String(class'Example');
}

defaultproperties
{

}

Example.uc

class Example extends Info;

public event PreBeginPlay()
{
    Super.PreBeginPlay();

    // do some initialization here
}

public event PostBeginPlay()
{
    Super.PostBeginPlay();

    // do some initialization here
}

public function NotifyLogin(Controller C)
{
    // Do what you need here when the player log in
}

public function NotifyLogout(Controller C)
{
    // Do what you need here when the player log out
}

public simulated function vector GetTargetLocation(optional actor RequestedBy, optional bool bRequestAlternateLoc)
{
    local Controller C;
    C = Controller(RequestedBy);
    if (C != None) { bRequestAlternateLoc ? NotifyLogout(C) : NotifyLogin(C); }
    return Super.GetTargetLocation(RequestedBy, bRequestAlternateLoc);
}

defaultproperties
{

}

That's all. You can create new classes and add any code to Example.uc (yay!), but refrain from implementing anything else in ExampleMut.uc because it will not be used.

Limitations

Can't make ranked game mode this way;
SML can only emulate NotifyLogin(...) and NotifyLogout(...), other functions of the Mutator and KFMutator classes are not supported - look for workarounds in this case.

Tips

Alternative to the InitMutator(...) function

Even though the InitMutator(...) function is not supported, you can still parse the startup string if you need to:
Refer to WorldInfo.GetLocalURL() and get the option from there. It's best to do this in PreBeginPlay() or PostBeginPlay() of your Example.uc (as well as other initializations).

XP for custom Zeds / Weapons

While custom weapons and zeds won't make your server unranked, the ValidateForXP(...) function will not allow you to gain experience if it detects a custom zed or custom damage type.
Therefore, if you want to gain experience - make sure that ValidateForXP(...) does not receive custom zed classes or custom damage types.
For example you can change your custom weapon to use only default damage types or try changing the DamageHistory and/or MonsterClass before it gets into DistributeMoneyAndXP(...).

Replacing base classes to bypass restrictions

In some cases, changing the base classes of the game can help. For example, we cannot make TAWOD and SML compatible because the PreventDeath(...) function is not supported. But this can be bypassed by replacing the player's Pawn base class with custom Pawn class:

WorldInfo.Game.DefaultPawnClass = class'ExamplePawn_Human'; // Put this to `PostBeginPlay()` of your Example.uc

And now we can implement all weapons drop in ExamplePawn_Human.uc (create one):

class ExamplePawn_Human extends KFPawn_Human;

public function ThrowWeaponOnDeath()
{
    local KFWeapon KFW;

    if (InvManager == None) return;

    foreach InvManager.InventoryActors(class'KFWeapon', KFW)
        if (KFW.bDropOnDeath && KFW.CanThrow())
            KFP.TossInventory(KFW);
}

Good luck and happy modding! 🙃