19 Commits

Author SHA1 Message Date
e2ea9d5f2a Merge branch 'serveractors' 2023-03-02 05:18:04 +03:00
6e048463f6 dont add server actor twice 2023-03-02 05:07:19 +03:00
62f3421cb1 fix webadmin 2023-03-02 03:33:50 +03:00
68de02c9c8 Update README.md
Update information about Unofficial Killing Floor 2 Patch (now fully compatible)
2023-03-02 03:18:47 +03:00
4367cd2b15 add server actors support 2023-03-02 03:01:14 +03:00
f6e1a27d95 Update description.txt 2022-11-27 20:23:02 +03:00
9236bd8387 Update README.md 2022-11-27 20:22:18 +03:00
a9be5a77d6 Update DEV.md 2022-11-27 20:20:48 +03:00
3f6c3552d1 Update DEV.md 2022-11-26 21:22:51 +03:00
e92a146660 Update README.md 2022-11-26 19:26:16 +03:00
22d4c40c53 update unnoficial patch info 2022-11-26 18:34:41 +03:00
6305c8f162 UnofficialKFPatch compat 2022-11-26 03:02:02 +03:00
e8d229e5b7 Update README.md 2022-11-22 17:50:33 +03:00
970d0f13b2 Update README.md 2022-11-22 17:48:11 +03:00
0c649b444e Update DEV.md 2022-11-02 13:27:19 +03:00
b8bc4a58eb Update README.md 2022-10-31 19:41:28 +03:00
f6eeda01eb Update DEV.md 2022-10-30 05:24:28 +03:00
970335943a prepare for publication 2022-10-30 04:56:22 +03:00
6713766a64 update description 2022-09-15 16:39:32 +03:00
8 changed files with 464 additions and 230 deletions

161
DEV.md Normal file
View File

@ -0,0 +1,161 @@
# [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**
```unrealscript
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**
```unrealscript
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(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/Engine/Classes/Mutator.uc#L147) and [`NotifyLogout(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/Engine/Classes/Mutator.uc#L141), other functions of the [`Mutator`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/master/Engine/Classes/Mutator.uc) and [`KFMutator`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/master/KFGame/Classes/KFMutator.uc) classes are not supported - look for workarounds in this case.
# Tips
## Alternative to the InitMutator(...) function
Even though the [`InitMutator(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/KFGame/Classes/KFMutator.uc#L22) function is not supported, you can still parse the startup string if you need to:
Refer to [`WorldInfo.GetLocalURL()`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/Engine/Classes/WorldInfo.uc#L1315) 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(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/KFGame/Classes/KFGameInfo.uc#L2564) 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(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/KFGame/Classes/KFGameInfo.uc#L2564) 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(...)`](https://github.com/GenZmeY/KF2-Dev-Scripts/blob/23d1ca3a9a2f62692741e77039f03fe0a913be1d/KFGame/Classes/KFGameInfo.uc#L2489).
## Replacing base classes to bypass restrictions
In some cases, changing the base classes of the game can help. For example, we cannot make [TAWOD](https://steamcommunity.com/sharedfiles/filedetails/?id=2379769040) and SML compatible because the [PreventDeath(...)](https://github.com/GenZmeY/KF2-TAWOD/blob/master/TAWOD/Classes/TAWODMut.uc#L19) function is not supported. But this can be bypassed by replacing the player's Pawn base class with custom Pawn class:
```unrealscript
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):
```unrealscript
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! 🙃**

View File

@ -1,8 +1,20 @@
[h1][b]Use non-whitelisted mutators and stay ranked[/b][/h1] [h1][b]Use non-whitelisted mutators and stay ranked[/b][/h1]
[h1]Limitations[/h1] [h1]Usage (server only)[/h1]
⚠️ SML only has an effect when compatible mutators are used. If you use incompatible mutators you will lose ranked status. [olist]
⚠️ SML incompatible with [b][url=https://github.com/th3-z/kf2-acpp]AccessPlus[/url][/b]. If you need something from there, implement it as an SML compatible mutator using [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2863210737]developer guide[/url]. [*]Subscribe your server to this.
[*]Add [b]SML.Mut[/b] [u]first[/u] to your list of mutators, example:
[code]
?Mutator=SML.Mut,UnofficialKFPatch.UKFPMutator,AAL.AALMut,DiscordMessage.DMMutator,YAS.YASMut,CTI.CTIMut,CVC.CVCMut,ZedSpawner.ZedSpawnerMut
[/code]
(add/remove [b]compatible[/b] mutators you need)
[/olist]
⚠️ Doesn't work in single player
⚠️ SML must be first in the mutators list or it won't work.
⚠️ SML only has an effect when [b]compatible[/b] mutators are used (the list below). If you use incompatible mutators you will lose ranked status.
⚠️ SML is a server-side mutator, clients never download it. Therefore, no one will know about you using SML if you dont tell yourself (or if you share with the whole world the BrewedPC folder where you put the SML, lol).
⚠️ SML is incompatible with [url=https://github.com/th3-z/kf2-acpp]AccessPlus[/url] and other mods based on it. If you need something from there, implement it as an SML compatible mutator using [url=https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/DEV.md]developer guide[/url].
[h1]Compatible mutators[/h1] [h1]Compatible mutators[/h1]
🟢 Any whitelisted mutators 🟢 Any whitelisted mutators
@ -13,29 +25,18 @@
[*]Purchasing a DLC weapon will unrank the server if currently there is no player with the purchased DLC. This can be bypassed by replacing all DLC weapons with their clones. [*]Purchasing a DLC weapon will unrank the server if currently there is no player with the purchased DLC. This can be bypassed by replacing all DLC weapons with their clones.
[*]Since [url=https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)]KF2 v1133[/url] the content preload causes the server to unrank for some reason. Disable it in CTI settings ([b]bPreloadContent=False[/b]) to stay ranked. [*]Since [url=https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)]KF2 v1133[/url] the content preload causes the server to unrank for some reason. Disable it in CTI settings ([b]bPreloadContent=False[/b]) to stay ranked.
[/list] [/list]
🟡 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931]Zed Spawner[/url] 🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2891475864]Discord Link [Edited][/url]
🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909]Looted Trader Inventory[/url]
🟡 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2875147606]Unofficial Killing Floor 2 Patch[/url]
[list] [list]
[*]Since [url=https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)]KF2 v1133[/url] zed preload causes the server to unrank for some reason (current version of ZedSpawner does not allow you to disable preload, but I will add it later). [*]Settings are not available (config and start parameters are ignored), patch will work with default settings
[/list] [/list]
🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2521826524]Yet Another Scoreboard[/url] 🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2521826524]Yet Another Scoreboard[/url]
🟡 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931]Zed Spawner[/url]
[list]
[*]Since [url=https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)]KF2 v1133[/url] zed preload causes the server to unrank for some reason. Disable it in ZedSpawner settings ([b]bPreloadContentServer=False[/b]) to stay ranked.
[/list]
[h1]Developer guide[/h1]
If you want to make your mutator compatible with SML use this:
[url=https://steamcommunity.com/sharedfiles/filedetails/?id=2863210737]Developer Guide[/url]
If you make a public mutator with SML support, I'd be happy if you tell me about it so I can add it to the list above.
[h1]Usage[/h1]
[code]
?Mutator=SML.Mut,AAL.AALMut,YAS.YASMut,CTI.CTIMut,CVC.CVCMut,ZedSpawner.ZedSpawnerMut
[/code]
(replace the map and add/remove [b]compatible[/b] mutators you need)
❗️ SML must be first in the mutators list or it won't work.
❗️ SML is a server-side mutator, clients never download it. Therefore, no one will know about SML if you dont tell yourself. You can also accidentally give it to the whole world if you use a redirect and share the folder where it's located. Be careful about this.
❗️ I also really hope you don't report this to TWI. If I find this blocked, I will probably be upset in the world and in the people with whom I shared this :(
[h1]Sources[/h1] [h1]Sources[/h1]
Right now, I don't want to post SML sources because I'm planning to submit several whitelist requests for my mutators. TWI will probably look at my github (At least I hope so) and I don't want them to see SML. [url=https://github.com/GenZmeY/KF2-SafeMutLoader]https://github.com/GenZmeY/KF2-SafeMutLoader[/url] [b](GNU GPLv3)[/b]
But later I will post it under the GNU GPLv3.

View File

@ -1,176 +0,0 @@
[SML] Developer Guide
[b]SML compatible mutator development guide[/b]
Mutator template
You can use this template to make the mutator compatible with [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847]SML[/url].
Here I will use "[b]Example[/b]" as mutator name. Replace it with yours.
[b]ExampleMut.uc[/b]
[code]
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)
Mut.Destroy();
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
{
}
[/code]
[b]Example.uc[/b]
[code]
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
{
}
[/code]
That's all. You can create new classes and add any code to [b]Example.uc[/b], but refrain from implementing anything else in [b]ExampleMut.uc[/b]
Limitations
❌ Can't make ranked game mode this way;
❌ [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847]SML[/url] can only emulate [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/Mutator.uc#L147]NotifyLogin(...)[/url] and [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/Mutator.uc#L141]NotifyLogout(...)[/url], other functions of the [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/Mutator.uc]Mutator[/url] and [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/KFMutator.uc]KFMutator[/url] classes are not supported - look for workarounds in this case.
[Tips] Alternative to the InitMutator(...) function
Even though the [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/KFGame/Classes/KFMutator.uc#L22]InitMutator(...)[/url] function is not supported, you can still parse the startup string if you need to:
Refer to [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/GameInfo.uc#L209]WorldInfo.Game.ServerOptions[/url] or [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/9553ac0b31c729c27e9cd0a460ab5a7bae2fd799/Engine/Classes/WorldInfo.uc#L1315]WorldInfo.GetLocalURL()[/url] and get the option from there. It's best to do this in [b]PreBeginPlay()[/b] or [b]PostBeginPlay()[/b] of your [b]Example.uc[/b] (as well as other initializations).
[Tips] XP for custom Zeds / Weapons
While custom weapons and zeds won't make your server unranked, the [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/513c60070e2b700b5c311843754ed0e39b55a14e/KFGame/Classes/KFGameInfo.uc#L2594]ValidateForXP(...)[/url] function will not allow you to gain experience if it detects a custom zed or custom damage type.
To work around this, don't use custom damage types on custom weapons, or replace information in [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/513c60070e2b700b5c311843754ed0e39b55a14e/KFGame/Classes/KFPawn.uc#L182]DamageHistory[/url] before it gets into the [url=https://github.com/GenZmeY/KF2-Dev-Scripts/blob/513c60070e2b700b5c311843754ed0e39b55a14e/KFGame/Classes/KFGameInfo.uc#L2489]DistributeMoneyAndXP(...)[/url] function.
[Tips] Replacing base classes to bypass restrictions
In some cases, changing the base classes of the game can help. For example, we cannot make [b][url=https://steamcommunity.com/sharedfiles/filedetails/?id=2379769040]TAWOD[/url][/b] and [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847]SML[/url] compatible because the [url=https://github.com/GenZmeY/KF2-TAWOD/blob/master/TAWOD/Classes/TAWODMut.uc#L19]PreventDeath(...)[/url] function is not supported. But this can be bypassed by replacing the player's Pawn base class with custom Pawn class:
[code]
WorldInfo.Game.DefaultPawnClass = class'ExamplePawn_Human';
[/code]
(This can be done in [b]PostBeginPlay()[/b])
And now we can implement all weapons drop in [b]ExamplePawn_Human.uc[/b]:
[code]
class TAWODPawn_Human extends KFPawn_Human;
public function ThrowActiveWeapon(optional bool bDestroyWeap)
{
local KFWeapon KFW;
if (Role < ROLE_Authority)
{
return;
}
if (Health <= 0)
{
if (InvManager != None)
foreach KFP.InvManager.InventoryActors(class'KFWeapon', KFW)
if (KFW != None && KFW.bDropOnDeath && KFW.CanThrow())
KFP.TossInventory(KFW);
}
else
{
super.ThrowActiveWeapon(bDestroyWeap);
}
}
[/code]
[Tips] Cloning DLC weapons
🛠[b] <UNDER_CONSTRUCTION> [/b]🛠

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

View File

@ -1,25 +1,47 @@
# KF2-SafeMutLoader # KF2-SafeMutLoader
Use non-whitelisted mutators and stay ranked.
[![Steam Workshop](https://img.shields.io/static/v1?message=workshop&logo=steam&labelColor=gray&color=blue&logoColor=white&label=steam%20)](https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847) # Disclaimer
[![Steam Downloads](https://img.shields.io/steam/downloads/2863226847)](https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847) **SML only uses KF2 and UnrealScript features, it doesn't change game executables or RAM or anything like that, so it's not a hack and it doesn't violate [Killing Floor 2 EULA](https://store.steampowered.com/eula/232090_eula_0).**
[![Steam Favorites](https://img.shields.io/steam/favorites/2863226847)](https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847)
[![Steam Update Date](https://img.shields.io/steam/update-date/2863226847)](https://steamcommunity.com/sharedfiles/filedetails/?id=2863226847)
[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/GenZmeY/KF2-SafeMutLoader)](https://github.com/GenZmeY/KF2-SafeMutLoader/tags)
[![GitHub](https://img.shields.io/github/license/GenZmeY/KF2-SafeMutLoader)](LICENSE)
# Description However, [AccessPlus](https://forums.tripwireinteractive.com/index.php?threads/utility-admin-access-plus-manager.118740) is also not a hack for the same reason, but it is constantly banned in the steam workshop. Why? I dont know.
Use non-whitelisted mutators and stay ranked
# Usage **So use this at your own risk!**
# Usage (server only)
1. Add SML to your server. There are two ways:
* **without workshop:** download `SML.u` from [releases](https://github.com/GenZmeY/KF2-SafeMutLoader/releases) and put it to `KFGame/BrewedPC`
* **with workshop:** Use the [instructions below](https://github.com/GenZmeY/KF2-SafeMutLoader#build--upload) to build the SML and upload it to your workshop, then subscribe your server to SML
2. Add `SML.Mut` **first** to your list of mutators, example:
``` ```
?Mutator=SML.Mut,AAL.AALMut,YAS.YASMut,CTI.CTIMut,CVC.CVCMut,ZedSpawner.ZedSpawnerMut ?Mutator=SML.Mut,UnofficialKFPatch.UKFPMutator,AAL.AALMut,DiscordMessage.DMMutator,YAS.YASMut,CTI.CTIMut,CVC.CVCMut,ZedSpawner.ZedSpawnerMut
``` ```
(replace the map and add/remove compatible mutators you need) (add/remove **compatible** mutators you need)
SML must be first in the mutators list or it won't work. Doesn't work in single player
SML is a server-side mutator, clients never download it. Therefore, no one will know about SML if you dont tell yourself. You can also accidentally give it to the whole world if you use a redirect and share the folder where it's located. Be careful about this. SML must be first in the mutators list or it won't work.
⚠️ SML only has an effect when **compatible** mutators are used (the list below). If you use incompatible mutators you will lose ranked status.
⚠️ SML is a server-side mutator, clients never download it. Therefore, no one will know about you using SML if you dont tell yourself (or if you share with the whole world the `BrewedPC` folder where you put the SML, lol).
⚠️ SML is incompatible with [AccessPlus](https://github.com/th3-z/kf2-acpp) and other mods based on it. If you need something from there, implement it as an SML compatible mutator using [developer guide](https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/DEV.md).
# Build # Compatible mutators
🟢 Any whitelisted mutators
🟢 [Admin Auto Login](https://steamcommunity.com/sharedfiles/filedetails/?id=2848836389)
🟢 [Controlled Vote Collector](https://steamcommunity.com/sharedfiles/filedetails/?id=2847465899)
🟡 [Custom Trader Inventory](https://steamcommunity.com/sharedfiles/filedetails/?id=2830826239)
Purchasing a DLC weapon will unrank the server if currently there is no player with the purchased DLC. This can be bypassed by replacing all DLC weapons with their clones.
Since KF2 [v1133](https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)) the content preload causes the server to unrank for some reason. Disable it in CTI settings (`bPreloadContent=False`) to stay ranked.
🟢 [Discord Link [Edited]](https://steamcommunity.com/sharedfiles/filedetails/?id=2891475864)
🟢 [Looted Trader Inventory](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909)
🟢 [Unofficial Killing Floor 2 Patch](https://steamcommunity.com/sharedfiles/filedetails/?id=2875147606)
🟢 [Yet Another Scoreboard](https://steamcommunity.com/sharedfiles/filedetails/?id=2521826524)
🟡 [Zed Spawner](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931)
Since KF2 [v1133](https://wiki.killingfloor2.com/index.php?title=Update_1133_(Killing_Floor_2)) zed preload causes the server to unrank for some reason. Disable it in ZedSpawner settings (`bPreloadContentServer=False`) to stay ranked.
# Making SML-compatible mutators
See [developer guide](https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/DEV.md)
# Build & Upload
**Note:** If you want to build/test/brew/publish a mutator without git-bash and/or scripts, follow [these instructions](https://tripwireinteractive.atlassian.net/wiki/spaces/KF2SW/pages/26247172/KF2+Code+Modding+How-to) instead of what is described here. **Note:** If you want to build/test/brew/publish a mutator without git-bash and/or scripts, follow [these instructions](https://tripwireinteractive.atlassian.net/wiki/spaces/KF2SW/pages/26247172/KF2+Code+Modding+How-to) instead of what is described here.
1. Install [Killing Floor 2](https://store.steampowered.com/app/232090/Killing_Floor_2/), Killing Floor 2 - SDK and [git for windows](https://git-scm.com/download/win); 1. Install [Killing Floor 2](https://store.steampowered.com/app/232090/Killing_Floor_2/), Killing Floor 2 - SDK and [git for windows](https://git-scm.com/download/win);
2. open git-bash and go to any folder where you want to store sources: 2. open git-bash and go to any folder where you want to store sources:
@ -28,10 +50,13 @@ Use non-whitelisted mutators and stay ranked
`git clone https://github.com/GenZmeY/KF2-SafeMutLoader && cd KF2-SafeMutLoader` `git clone https://github.com/GenZmeY/KF2-SafeMutLoader && cd KF2-SafeMutLoader`
4. Download dependencies: 4. Download dependencies:
`git submodule init && git submodule update` `git submodule init && git submodule update`
5. Compile: 5. Build and upload to steam workshop:
`./tools/builder -c` `./tools/builder -cbu`
5. The compiled files will be here: 6. Find `SafeMutLoader` in your workshop and change `Visibility` to `Unlisted` so your server can download it (don't use `Public` visibility)
`C:\Users\<USERNAME>\Documents\My Games\KillingFloor2\KFGame\Unpublished\BrewedPC\Script\`
# Contributing
If you make a mod compatible with SML I'll be happy to add it to the list of compatible mutators.
Contact me in any convenient way (for example, create an [issue](https://github.com/GenZmeY/KF2-SafeMutLoader/issues))
# License # License
[GNU GPLv3](LICENSE) [GNU GPLv3](LICENSE)

View File

@ -0,0 +1,11 @@
class KFGI_Access extends Object within KFGameInfo;
public function bool IsRankedGame()
{
return !IsUnrankedGame();
}
defaultproperties
{
}

View File

@ -32,10 +32,14 @@ public function AddMutator(Mutator Mut)
if (CorrectLoadOrder() || Mut == Self) return; if (CorrectLoadOrder() || Mut == Self) return;
if (Mut.Class == Class) if (Mut.Class == Class)
{
Mut.Destroy(); Mut.Destroy();
}
else else
{
Super.AddMutator(Mut); Super.AddMutator(Mut);
} }
}
private function bool CorrectLoadOrder() private function bool CorrectLoadOrder()
{ {
@ -53,7 +57,9 @@ private function ModifyLoad()
local String MutatorsRaw; local String MutatorsRaw;
local String AccessControlRaw; local String AccessControlRaw;
local Array<String> Mutators; local Array<String> Mutators;
local int PrevServerActorsCount;
local int Index; local int Index;
local GameEngine GameEngine;
`Log_Trace(); `Log_Trace();
@ -66,6 +72,7 @@ private function ModifyLoad()
LoadURL = Repl(LoadURL, Subst(OptAC) $ AccessControlRaw, ""); LoadURL = Repl(LoadURL, Subst(OptAC) $ AccessControlRaw, "");
SML.static.ClearMutators(); SML.static.ClearMutators();
SML.static.ClearServerActors();
ParseStringIntoArray(MutatorsRaw, Mutators, ",", true); ParseStringIntoArray(MutatorsRaw, Mutators, ",", true);
Index = 0; Index = 0;
@ -81,6 +88,35 @@ private function ModifyLoad()
++Index; ++Index;
} }
} }
GameEngine = GameEngine(Class'Engine'.static.GetEngine());
if (GameEngine == None)
{
`Log_Error("GameEngine is None, skip loading server actors");
}
else
{
PrevServerActorsCount = GameEngine.ServerActors.Length;
Index = 0;
while (Index < GameEngine.ServerActors.Length)
{
if (SML.static.AddServerActor(GameEngine.ServerActors[Index]))
{
GameEngine.ServerActors.Remove(Index, 1);
}
else
{
++Index;
}
}
if (GameEngine.ServerActors.Length != PrevServerActorsCount)
{
GameEngine.SaveConfig();
}
}
SML.static.StaticSaveConfig(); SML.static.StaticSaveConfig();
JoinArray(Mutators, MutatorsRaw); JoinArray(Mutators, MutatorsRaw);

View File

@ -1,22 +1,76 @@
class SafeMutLoader extends KFAccessControl class SafeMutLoader extends KFAccessControl
config(SML); config(SML);
var private Array<Actor> ServerActors; struct CMR
{
var String Mutator;
var String Replacement;
};
var private Array<Actor> ActiveMutators;
var private Array<Actor> ActiveServerActors;
var private Array<CMR> CustomMutReplacements;
var private Array<String> SystemServerActors;
var private config E_LogLevel LogLevel; var private config E_LogLevel LogLevel;
var private config Array<String> Mutators; var private config Array<String> Mutators;
var private config Array<String> ServerActors;
public function PreBeginPlay() public function PreBeginPlay()
{ {
`Log_Trace();
LogLevel = GetLogLevel(); LogLevel = GetLogLevel();
LoadActors(); `Log_Trace();
Super.PreBeginPlay(); Super.PreBeginPlay();
LoadMutators();
LoadServerActors();
} }
private function LoadActors() public function PostBeginPlay()
{
local KFGI_Access KFGIA;
`Log_Trace();
Super.PreBeginPlay();
RestoreServerActors();
KFGIA = GetKFGIA();
if (KFGIA == None)
{
`Log_Error("Can't check ranked status");
}
else if (KFGIA.IsRankedGame())
{
`Log_Info("Mutators and server actors successfully loaded! Your server is RANKED!");
}
else
{
`Log_Warn("Your server is UNRANKED! Check the mutators and server actors you are using. Maybe some of them are incompatible with SML");
}
}
private function KFGI_Access GetKFGIA()
{
local KFGameInfo KFGI;
if (WorldInfo == None || WorldInfo.Game == None)
{
return None;
}
KFGI = KFGameInfo(WorldInfo.Game);
if (KFGI == None)
{
return None;
}
return new(KFGI) class'KFGI_Access';
}
private function LoadMutators()
{ {
local String MutString; local String MutString;
local class<Mutator> MutClass; local class<Mutator> MutClass;
@ -48,11 +102,96 @@ private function LoadActors()
continue; continue;
} }
ServerActors.AddItem(ServerActor); ActiveMutators.AddItem(ServerActor);
`Log_Info("Loaded:" @ MutString); `Log_Info("Loaded:" @ MutString);
} }
} }
private function LoadServerActors()
{
local String ActorString;
local class<Actor> ActorClass;
local Actor ServerActor;
foreach ServerActors(ActorString)
{
ActorClass = class<Actor>(DynamicLoadObject(ActorString, class'Class'));
if (ActorClass == None)
{
`Log_Error("Can't load server actor:" @ ActorString);
continue;
}
ServerActor = WorldInfo.Spawn(ActorClass);
if (ServerActor == None)
{
`Log_Error("Can't spawn:" @ ActorString);
continue;
}
ActiveServerActors.AddItem(ServerActor);
`Log_Info("Loaded:" @ ActorString);
}
}
private function RestoreServerActors()
{
local GameEngine GameEngine;
local String ActorString;
local int PrevServerActorsCount;
GameEngine = GameEngine(Class'Engine'.static.GetEngine());
if (GameEngine == None)
{
`Log_Error("GameEngine is None! Can't restore ServerActors!");
return;
}
PrevServerActorsCount = GameEngine.ServerActors.Length;
foreach ServerActors(ActorString)
{
if (GameEngine.ServerActors.Find(ActorString) != INDEX_NONE)
{
GameEngine.ServerActors.AddItem(ActorString);
}
}
if (GameEngine.ServerActors.Length != PrevServerActorsCount)
{
GameEngine.SaveConfig();
}
}
public static function bool AddServerActor(String ServerActor)
{
local class<Actor> ActorClass;
if (default.SystemServerActors.Find(ServerActor) != INDEX_NONE)
{
return false;
}
ActorClass = class<Actor>(DynamicLoadObject(ServerActor, class'Class'));
if (ActorClass == None)
{
return false;
}
if (ClassIsChildOf(ActorClass, class'Mutator'))
{
return false;
}
if (default.ServerActors.Find(ServerActor) == INDEX_NONE)
{
default.ServerActors.AddItem(ServerActor);
}
return true;
}
public static function bool AddMutator(String MutString) public static function bool AddMutator(String MutString)
{ {
if (GetMutStringReplacement(MutString) != None) if (GetMutStringReplacement(MutString) != None)
@ -72,6 +211,11 @@ public static function ClearMutators()
default.Mutators.Length = 0; default.Mutators.Length = 0;
} }
public static function ClearServerActors()
{
default.ServerActors.Length = 0;
}
public static function bool WantsToSpawn() public static function bool WantsToSpawn()
{ {
return (default.Mutators.Length > 0); return (default.Mutators.Length > 0);
@ -89,6 +233,13 @@ public static function String GetName(optional Object O)
} }
} }
public static function String GetMutName(class<Mutator> CMut)
{
if (CMut == None) return "";
return CMut.GetPackageName() $ "." $ String(CMut);
}
public function PostLogin(PlayerController C) public function PostLogin(PlayerController C)
{ {
local Actor A; local Actor A;
@ -97,7 +248,7 @@ public function PostLogin(PlayerController C)
if (C != None) if (C != None)
{ {
foreach ServerActors(A) foreach ActiveMutators(A)
{ {
A.GetTargetLocation(C, false); A.GetTargetLocation(C, false);
} }
@ -116,7 +267,7 @@ public function OnClientConnectionClose(Player ClientConnection)
C = ClientConnection.Actor; C = ClientConnection.Actor;
if (C != None) if (C != None)
{ {
foreach ServerActors(A) foreach ActiveMutators(A)
{ {
A.GetTargetLocation(C, true); A.GetTargetLocation(C, true);
} }
@ -138,10 +289,26 @@ public static function E_LogLevel GetLogLevel()
private static function class<Actor> GetMutReplacement(class<Mutator> MutClass) private static function class<Actor> GetMutReplacement(class<Mutator> MutClass)
{ {
if (MutClass == None || MutClass.static.GetLocalString() == "") return None; local int Index;
return class<Actor>(DynamicLoadObject( local String Replacement;
MutClass.GetPackageName() $ "." $
MutClass.static.GetLocalString(), class'Class')); if (MutClass == None) return None;
Index = default.CustomMutReplacements.Find('Mutator', GetMutName(MutClass));
if (Index != INDEX_NONE)
{
Replacement = default.CustomMutReplacements[Index].Replacement;
}
else if (MutClass.static.GetLocalString() == "")
{
return None;
}
else
{
Replacement = MutClass.GetPackageName() $ "." $ MutClass.static.GetLocalString();
}
return class<Actor>(DynamicLoadObject(Replacement, class'Class'));
} }
private static function class<Actor> GetMutStringReplacement(String MutString) private static function class<Actor> GetMutStringReplacement(String MutString)
@ -151,5 +318,14 @@ private static function class<Actor> GetMutStringReplacement(String MutString)
defaultproperties defaultproperties
{ {
CustomMutReplacements.Add({(
Mutator="UnofficialKFPatch.UKFPMutator",
Replacement="UnofficialKFPatch.UKFPReplicationInfo"
)})
CustomMutReplacements.Add({(
Mutator="UnofficialKFPatch.UKFPMutatorNW",
Replacement="UnofficialKFPatch.UKFPReplicationInfo"
)})
SystemServerActors.Add("IpDrv.WebServer")
} }