Compare commits

...

30 Commits

Author SHA1 Message Date
21353a8a3d
Update README.md 2024-03-09 19:55:43 +03:00
4c6beaaed9
Update README.md 2024-02-27 22:27:43 +03:00
e9f5b8b2b8
Create LEGAL.md 2024-02-27 22:18:28 +03:00
6efe21b7a6
update ci/cd 2023-12-31 20:49:57 +03:00
98ddf593eb
Update README.md 2023-11-22 03:59:29 +03:00
8d8e65777b
Update README.md 2023-11-22 03:54:06 +03:00
b44514dfbb
Merge pull request #10 from GenZmeY/add-fhud
add FriendlyHUD to the list of compatible mutators
2023-11-21 01:08:47 +03:00
d76d279447
add FriendlyHUD to the list of compatible mutators 2023-11-16 21:14:54 +03:00
bbf86d6dcc
update steam description 2023-10-09 18:23:59 +03:00
e05982622c
add AmmoMulti and WorkshopTool to compatible list 2023-10-09 18:21:37 +03:00
ed66f2bbc8
add True Random Boss 2023-10-07 23:50:19 +03:00
90beee7933
update megalinter ci/cd 2023-10-07 23:45:38 +03:00
ef16481633
Update README.md 2023-09-19 23:15:30 +03:00
affffe4690
Update README.md 2023-09-14 23:12:58 +03:00
416eebbdfc
Update README.md 2023-09-11 04:53:22 +03:00
d3827c9e5a
add a delay before checking ranked status 2023-09-11 03:32:30 +03:00
330322dfa0
add UKFP compatibility (again) 2023-09-10 04:24:12 +03:00
256f3df4e8
Merge pull request #6 from GenZmeY/group-names
add mutator group name
2023-06-29 00:56:20 +03:00
2a3a56043e
Merge pull request #7 from GenZmeY/readme-startwave
add StartWave to compatible list
2023-06-29 00:56:02 +03:00
d976e94696 add GC before server travel 2023-06-28 01:06:21 +03:00
2f035c60bd add StartWave to compatible list 2023-05-21 01:55:24 +03:00
cb8140f705 add mutator group name 2023-05-14 05:12:21 +03:00
2293ce47e8 add badges 2023-05-14 05:09:44 +03:00
83ef34bf34
Merge pull request #4 from GenZmeY/MegaLinter
Mega linter
2023-05-14 04:52:20 +03:00
cfa8257071 update .editorconfig 2023-05-14 04:46:11 +03:00
42c844fffd update style 2023-05-14 04:41:54 +03:00
64893b7484 add MegaLinter 2023-05-14 04:32:45 +03:00
0a5e62e3e7 add .editorconfig 2023-05-14 04:32:25 +03:00
98194b3917 update build tools 2023-05-14 04:20:07 +03:00
7a6fca9462 fix SafeMutLoader::PostBeginPlay() initialization
- this fixes the players list in steam server browser -> server details.
2023-03-30 01:38:00 +03:00
16 changed files with 828 additions and 612 deletions

33
.editorconfig Normal file
View File

@ -0,0 +1,33 @@
root = true
# Global
[*]
indent_style = unset
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = unset
# Unreal Engine 3 / Source
[*.uc]
indent_style = tab
[*.{uci,upkg}]
# Unreal Engine 3 / i18n
[*.{chn,cht,cze,dan,deu,dut,esl,esn,fra,frc,hun,int,ita,jpn,kor,pol,por,ptb,rus,tur,ukr}]
charset = utf-16le
# Other
[*.md]
indent_style = space
trim_trailing_whitespace = false
[*.yml]
indent_style = space
indent_size = 2
[*.{txt,cfg,conf}]
indent_style = tab

114
.github/workflows/mega-linter.yml vendored Normal file
View File

@ -0,0 +1,114 @@
---
name: MegaLinter
permissions: read-all
on:
push:
pull_request:
branches:
- master
env:
APPLY_FIXES: none
APPLY_FIXES_EVENT: pull_request
APPLY_FIXES_MODE: commit
FILTER_REGEX_EXCLUDE: (mega-linter.yml)
DISABLE: SPELL
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
megalinter:
name: MegaLinter
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: MegaLinter
uses: oxsecurity/megalinter@7e042c726c68415475b05a65a686c612120a1232
id: ml
env:
VALIDATE_ALL_CODEBASE: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Archive production artifacts
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392
if: success() || failure()
with:
name: MegaLinter reports
path: |
megalinter-reports
mega-linter.log
- name: Set APPLY_FIXES_IF var
run: |
printf 'APPLY_FIXES_IF=%s\n' "${{
steps.ml.outputs.has_updated_sources == 1 &&
(
env.APPLY_FIXES_EVENT == 'all' ||
env.APPLY_FIXES_EVENT == github.event_name
) &&
(
github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.repository
)
}}" >> "${GITHUB_ENV}"
- name: Set APPLY_FIXES_IF_* vars
run: |
printf 'APPLY_FIXES_IF_PR=%s\n' "${{
env.APPLY_FIXES_IF == 'true' &&
env.APPLY_FIXES_MODE == 'pull_request'
}}" >> "${GITHUB_ENV}"
printf 'APPLY_FIXES_IF_COMMIT=%s\n' "${{
env.APPLY_FIXES_IF == 'true' &&
env.APPLY_FIXES_MODE == 'commit' &&
(!contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref))
}}" >> "${GITHUB_ENV}"
- name: Create Pull Request with applied fixes
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
id: cpr
if: env.APPLY_FIXES_IF_PR == 'true'
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
commit-message: "[MegaLinter] Apply linters automatic fixes"
title: "[MegaLinter] Apply linters automatic fixes"
labels: bot
- name: Create PR output
if: env.APPLY_FIXES_IF_PR == 'true'
run: |
echo "PR Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "PR URL - ${{ steps.cpr.outputs.pull-request-url }}"
- name: Prepare commit
if: env.APPLY_FIXES_IF_COMMIT == 'true'
run: sudo chown -Rc $UID .git/
- name: Commit and push applied linter fixes
uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d
if: env.APPLY_FIXES_IF_COMMIT == 'true'
with:
branch: >-
${{
github.event.pull_request.head.ref ||
github.head_ref ||
github.ref
}}
commit_message: "[MegaLinter] Apply linters fixes"
commit_user_name: "github-actions"
commit_user_email: "github-actions[bot]@users.noreply.github.com"

112
DEV.md
View File

@ -2,7 +2,7 @@
**SML compatible mutator development guide** **SML compatible mutator development guide**
# Mutator template ## Mutator template
You can use this template to make the mutator compatible with SML. You can use this template to make the mutator compatible with SML.
Here I will use `Example` as mutator name. **Replace it with yours.** Here I will use `Example` as mutator name. **Replace it with yours.**
@ -10,64 +10,64 @@ Here I will use `Example` as mutator name. **Replace it with yours.**
**ExampleMut.uc** **ExampleMut.uc**
```unrealscript ```unrealscript
class ExampleMut extends KFMutator; class ExampleMut extends KFMutator;
var private Example Example; var private Example Example;
public simulated function bool SafeDestroy() public simulated function bool SafeDestroy()
{ {
return (bPendingDelete || bDeleteMe || Destroy()); return (bPendingDelete || bDeleteMe || Destroy());
} }
public event PreBeginPlay() public event PreBeginPlay()
{ {
Super.PreBeginPlay(); Super.PreBeginPlay();
if (WorldInfo.NetMode == NM_Client) return; if (WorldInfo.NetMode == NM_Client) return;
foreach WorldInfo.DynamicActors(class'Example', Example) foreach WorldInfo.DynamicActors(class'Example', Example)
{ {
break; break;
} }
if (Example == None) if (Example == None)
{ {
Example = WorldInfo.Spawn(class'Example'); Example = WorldInfo.Spawn(class'Example');
} }
if (Example == None) if (Example == None)
{ {
`Log("Example: FATAL: Can't Spawn 'Example'"); `Log("Example: FATAL: Can't Spawn 'Example'");
SafeDestroy(); SafeDestroy();
} }
} }
public function AddMutator(Mutator Mut) public function AddMutator(Mutator Mut)
{ {
if (Mut == Self) return; if (Mut == Self) return;
if (Mut.Class == Class) if (Mut.Class == Class)
ExampleMut(Mut).SafeDestroy(); ExampleMut(Mut).SafeDestroy();
else else
Super.AddMutator(Mut); Super.AddMutator(Mut);
} }
public function NotifyLogin(Controller C) public function NotifyLogin(Controller C)
{ {
Example.NotifyLogin(C); Example.NotifyLogin(C);
Super.NotifyLogin(C); Super.NotifyLogin(C);
} }
public function NotifyLogout(Controller C) public function NotifyLogout(Controller C)
{ {
Example.NotifyLogout(C); Example.NotifyLogout(C);
Super.NotifyLogout(C); Super.NotifyLogout(C);
} }
static function String GetLocalString(optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2) static function String GetLocalString(optional int Switch, optional PlayerReplicationInfo RelatedPRI_1, optional PlayerReplicationInfo RelatedPRI_2)
{ {
return String(class'Example'); return String(class'Example');
} }
defaultproperties defaultproperties
@ -82,34 +82,34 @@ class Example extends Info;
public event PreBeginPlay() public event PreBeginPlay()
{ {
Super.PreBeginPlay(); Super.PreBeginPlay();
// do some initialization here // do some initialization here
} }
public event PostBeginPlay() public event PostBeginPlay()
{ {
Super.PostBeginPlay(); Super.PostBeginPlay();
// do some initialization here // do some initialization here
} }
public function NotifyLogin(Controller C) public function NotifyLogin(Controller C)
{ {
// Do what you need here when the player log in // Do what you need here when the player log in
} }
public function NotifyLogout(Controller C) public function NotifyLogout(Controller C)
{ {
// Do what you need here when the player log out // Do what you need here when the player log out
} }
public simulated function vector GetTargetLocation(optional actor RequestedBy, optional bool bRequestAlternateLoc) public simulated function vector GetTargetLocation(optional actor RequestedBy, optional bool bRequestAlternateLoc)
{ {
local Controller C; local Controller C;
C = Controller(RequestedBy); C = Controller(RequestedBy);
if (C != None) { bRequestAlternateLoc ? NotifyLogout(C) : NotifyLogin(C); } if (C != None) { bRequestAlternateLoc ? NotifyLogout(C) : NotifyLogin(C); }
return Super.GetTargetLocation(RequestedBy, bRequestAlternateLoc); return Super.GetTargetLocation(RequestedBy, bRequestAlternateLoc);
} }
defaultproperties defaultproperties
@ -120,21 +120,21 @@ 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. 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 ## Limitations
❌ Can't make ranked game mode this way; ❌ 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. ❌ 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 ## Tips
## Alternative to the InitMutator(...) function ## 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: 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). 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 ### 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. 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. 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). 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 ### 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: 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 ```unrealscript
WorldInfo.Game.DefaultPawnClass = class'ExamplePawn_Human'; // Put this to `PostBeginPlay()` of your Example.uc WorldInfo.Game.DefaultPawnClass = class'ExamplePawn_Human'; // Put this to `PostBeginPlay()` of your Example.uc
@ -146,13 +146,13 @@ class ExamplePawn_Human extends KFPawn_Human;
public function ThrowWeaponOnDeath() public function ThrowWeaponOnDeath()
{ {
local KFWeapon KFW; local KFWeapon KFW;
if (InvManager == None) return; if (InvManager == None) return;
foreach InvManager.InventoryActors(class'KFWeapon', KFW) foreach InvManager.InventoryActors(class'KFWeapon', KFW)
if (KFW.bDropOnDeath && KFW.CanThrow()) if (KFW.bDropOnDeath && KFW.CanThrow())
KFP.TossInventory(KFW); KFP.TossInventory(KFW);
} }
``` ```

66
LEGAL.md Normal file
View File

@ -0,0 +1,66 @@
# SafeMutLoader (SML) is legal and does not violate the [KF2 EULA](https://store.steampowered.com/eula/232090_eula_0).
## Here's why in detail:
Let's look at the [EULA](https://store.steampowered.com/eula/232090_eula_0) points that relate to modifications:
> ### 2. Permitted User Modifications and New Creations
> Some of our games come with a KILLING FLOOR 2 editor. You can use editors to make mods or create new content to be played in KILLING FLOOR 2. You agree that you will not distribute or share the KILLING FLOOR 2 editor because it is not shareware. You agree that any new creations or materials that you make for KILLING FLOOR 2, with or without the KILLING FLOOR 2 editor, (collectively referred to as “Mods”) are subject to the following restrictions:
> Your Mods must only work with the full, registered copy of KILLING FLOOR 2, not independently or with any other software.
SML only works with a full registered copy of KILLING FLOOR 2 and is not intended to work with any other software.
> Your Mods must not contain modifications to any executable file(s).
SML does not modify KILLING FLOOR 2 executables.
> Your Mods must not contain any sexually explicit, harmful, threatening, abusive, defamatory, obscene, hateful, racially or ethnically offensive imagery or libelous, defamatory, or other illegal material, material that is scandalous or invades the rights of privacy or publicity of any third party.
SML does not contain anything listed here.
> Your Mods must not contain, or be used in conjunction with, any trademarks, copyright protected work, or other recognizable property of third parties without their written authority.
SML does not contain any trademarks or property of third parties.
> Your Mods must not be used by you, or anyone else, for any commercial exploitation including, but not limited to in-game advertising, other advertising or marketing for any company, product or service.
SML does not have any functionality for commercial use or advertising, SML is distributed free of charge.
> While we encourage folks to make Mods, Mods will not be supported by Tripwire Interactive and its licensors, licensees or suppliers, and if distributed pursuant to this license your Mods must include a statement to that effect.
SML is supported by its author. SML is distributed under the GNU GPLv3 license, which states that this program is provided "as is" and without any warranties.
> Your Mods must be distributed for free, period. Neither you, nor any other person or party, may sell them to anyone, commercially exploit them in any way, or charge anyone for receiving or using them without prior written consent from Tripwire Interactive. You may exchange them at no charge among other end users and distribute them to others over the Internet, on magazine cover disks, or otherwise for free.
SML is distributed free of charge, including both compiled mod files and its source code.
> The prohibitions and restrictions in this section apply to anyone in possession of KILLING FLOOR 2 or any Mods.
Of course.
> ### 3. Commercial Exploitation
> You may not use KILLING FLOOR 2, or any Mods created for or from KILLING FLOOR 2 or using the KILLING FLOOR 2 editor or any other tools provided with this KILLING FLOOR 2, for any commercial purposes without the prior written consent of Tripwire Interactive or its authorized licensees including, but not limited to, the following rules: 1. If you are the proprietor of an Internet café or gaming room, you may operate the KILLING FLOOR 2 in a “pay for play” environment provided that all computers used have validly licensed KILLING FLOOR 2 installed, such KILLING FLOOR 2 having been properly purchased through one of our licensees. 2. You may not, without prior written consent from Tripwire Interactive, operate KILLING FLOOR 2 in any gaming contest where (a) the cash value of all winnings and prizes paid throughout the entire competition is equal to or greater than US$10,000.00 or (b) the name of the event, or any individual contest therein, incorporates or approximates the name of a company, product or commercial service or (c) any company has provided, whether donated or as sponsorship any prizes, products or services worth with a fair market value of over US $20,000.00.
SML is distributed free of charge, including both compiled mod files and its source code.
> ### 4. Restrictions on Use
> Just to make sure you understand what you can and cannot do with KILLING FLOOR 2, here is a list of restrictions to your use of KILLING FLOOR 2 under this EULA:
> You may not decompile, modify, reverse engineer, publicly display, prepare derivative works based on KILLING FLOOR 2 (except as permitted in Section 2, above), disassemble or otherwise reproduce KILLING FLOOR 2.
SML is not a derivative work. No decompilation of game files was used during the development of SML. SML works only on the functionality provided by KILLING FLOOR 2 and Unreal Script available through the KILLING FLOOR 2 - SDK.
> Except as set forth herein, you may not rent, sell, lease, barter, sublicense or distribute KILLING FLOOR 2. You may not delete the copyright notices or any other proprietary legends on the original copy of KILLING FLOOR 2.
SML cannot be used for the purposes listed here. SML does not affect copyright notices in KILLING FLOOR 2.
> You may not offer KILLING FLOOR 2 on a pay per play basis or otherwise commercially exploit KILLING FLOOR 2 or use KILLING FLOOR 2 for any commercial purpose except as described in this agreement.
SML cannot be used for the purposes listed here.
> You may not electronically transmit KILLING FLOOR 2 from one computer to another or over a network except as described in this agreement..
SML cannot be used for these purposes listed here.

View File

@ -17,26 +17,7 @@
⚠️ 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]. ⚠️ 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 https://github.com/GenZmeY/KF2-SafeMutLoader#compatible-mutators
🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2848836389]Admin Auto Login[/url]
🟢 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2847465899]Controlled Vote Collector[/url]
🟡 [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2830826239]Custom Trader Inventory[/url]
[list]
[*]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.
[/list]
🟢 [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]
[*]Settings are not available (config and start parameters are ignored), patch will work with default settings
[/list]
🟢 [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]Sources[/h1] [h1]Sources[/h1]
[url=https://github.com/GenZmeY/KF2-SafeMutLoader]https://github.com/GenZmeY/KF2-SafeMutLoader[/url] [b](GNU GPLv3)[/b] [url=https://github.com/GenZmeY/KF2-SafeMutLoader]https://github.com/GenZmeY/KF2-SafeMutLoader[/url] [b](GNU GPLv3)[/b]

View File

@ -1,20 +1,24 @@
# KF2-SafeMutLoader # KF2-SafeMutLoader
[![Downloads](https://img.shields.io/github/downloads/GenZmeY/KF2-SafeMutLoader/total)](https://github.com/GenZmeY/KF2-SafeMutLoader/releases)
[![MegaLinter](https://github.com/GenZmeY/KF2-SafeMutLoader/actions/workflows/mega-linter.yml/badge.svg?branch=master)](https://github.com/GenZmeY/KF2-SafeMutLoader/actions/workflows/mega-linter.yml)
[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/GenZmeY/KF2-SafeMutLoader)](https://github.com/GenZmeY/KF2-SafeMutLoader/releases)
[![GitHub](https://img.shields.io/github/license/GenZmeY/KF2-SafeMutLoader)](LICENSE)
## Description
Use non-whitelisted mutators and stay ranked. Use non-whitelisted mutators and stay ranked.
# Disclaimer ## Legal
**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).** SafeMutLoader is legal and does not violate the [KF2 EULA](https://store.steampowered.com/eula/232090_eula_0). Here's why in detail: [LEGAL.md](LEGAL.md).
However, for some reason SML is getting banned in the steam workshop, so **use it at your own risk**.
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. ## Usage (server only)
**So use this at your own risk!**
# Usage (server only)
1. Add SML to your server. There are two ways: 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` * **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 * **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: 2. Add `SML.Mut` **first** to your list of mutators, example:
``` ```text
?Mutator=SML.Mut,UnofficialKFPatch.UKFPMutator,AAL.AALMut,DiscordMessage.DMMutator,YAS.YASMut,CTI.CTIMut,CVC.CVCMut,ZedSpawner.ZedSpawnerMut ?Mutator=SML.Mut,FriendlyHUD.FriendlyHUDMutator,YAS.Mut,CTI.Mut,CVC.Mut,AAL.Mut
``` ```
(add/remove **compatible** mutators you need) (add/remove **compatible** mutators you need)
@ -24,24 +28,30 @@ However, [AccessPlus](https://forums.tripwireinteractive.com/index.php?threads/u
⚠️ 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 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). ⚠️ 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).
# Compatible mutators ## Compatible mutators
🟢 Any whitelisted mutators 🟢 Any whitelisted mutators
🟢 [Admin Auto Login](https://steamcommunity.com/sharedfiles/filedetails/?id=2848836389) 🟢 [Admin Auto Login](https://steamcommunity.com/sharedfiles/filedetails/?id=2848836389)
🟢 [AmmoMulti](https://steamcommunity.com/sharedfiles/filedetails/?id=3026449204)
🟢 [Controlled Vote Collector](https://steamcommunity.com/sharedfiles/filedetails/?id=2847465899) 🟢 [Controlled Vote Collector](https://steamcommunity.com/sharedfiles/filedetails/?id=2847465899)
🟡 [Custom Trader Inventory](https://steamcommunity.com/sharedfiles/filedetails/?id=2830826239) 🟡 [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. Using `UnlockDLC=ReplaceFilter` will unrank the server when someone buys DLC weapons. Use `UnlockDLC=ReplaceWeapons` to get around this.
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. 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) 🟢 [Discord Link [Edited]](https://steamcommunity.com/sharedfiles/filedetails/?id=2891475864)
🟢 [FriendlyHUD](https://steamcommunity.com/sharedfiles/filedetails/?id=1819268190)
🟢 [Looted Trader Inventory](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) 🟢 [Looted Trader Inventory](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909)
🟡 [StartWave](https://github.com/GenZmeY/KF2-StartWave)
`mutate startwave X` command not working.
🟢 [True Random Boss](https://steamcommunity.com/sharedfiles/filedetails/?id=3047331564)
🟢 [Unofficial Killing Floor 2 Patch](https://steamcommunity.com/sharedfiles/filedetails/?id=2875147606) 🟢 [Unofficial Killing Floor 2 Patch](https://steamcommunity.com/sharedfiles/filedetails/?id=2875147606)
🟢 [WorkshopTool](https://steamcommunity.com/sharedfiles/filedetails/?id=3047217103)
🟢 [Yet Another Scoreboard](https://steamcommunity.com/sharedfiles/filedetails/?id=2521826524) 🟢 [Yet Another Scoreboard](https://steamcommunity.com/sharedfiles/filedetails/?id=2521826524)
🟡 [Zed Spawner](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931) 🟡 [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. 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 ## Making SML-compatible mutators
See [developer guide](https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/DEV.md) See [developer guide](https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/DEV.md)
# Build & Upload ## 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:
@ -54,9 +64,9 @@ See [developer guide](https://github.com/GenZmeY/KF2-SafeMutLoader/blob/master/D
`./tools/builder -cbu` `./tools/builder -cbu`
6. Find `SafeMutLoader` in your workshop and change `Visibility` to `Unlisted` so your server can download it (don't use `Public` visibility) 6. Find `SafeMutLoader` in your workshop and change `Visibility` to `Unlisted` so your server can download it (don't use `Public` visibility)
# Contributing ## Contributing
If you make a mod compatible with SML I'll be happy to add it to the list of compatible mutators. 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)) Contact me in any convenient way (for example, create an [issue](https://github.com/GenZmeY/KF2-SafeMutLoader/issues))
# License ## License
[GNU GPLv3](LICENSE) [![license](https://www.gnu.org/graphics/gplv3-with-text-136x68.png)](LICENSE)

View File

@ -7,5 +7,5 @@ public function bool IsRankedGame()
defaultproperties defaultproperties
{ {
} }

View File

@ -1,142 +1,143 @@
class Mut extends KFMutator; class Mut extends KFMutator;
const SML = class'SafeMutLoader'; const SML = class'SafeMutLoader';
const OptAC = "AccessControl"; const OptAC = "AccessControl";
const OptMut = "Mutator"; const OptMut = "Mutator";
var private E_LogLevel LogLevel; var private E_LogLevel LogLevel;
public function PreBeginPlay() public function PreBeginPlay()
{ {
Super.PreBeginPlay(); Super.PreBeginPlay();
LogLevel = SML.static.GetLogLevel(); LogLevel = SML.static.GetLogLevel();
`Log_Trace(); `Log_Trace();
if (CorrectLoadOrder()) if (CorrectLoadOrder())
{ {
ModifyLoad(); ModifyLoad();
} }
else else
{ {
`Log_Fatal(SML.static.GetName(Self) @ "must be loaded first."); `Log_Fatal(SML.static.GetName(Self) @ "must be loaded first.");
} }
} }
public function AddMutator(Mutator Mut) public function AddMutator(Mutator Mut)
{ {
`Log_Trace(); `Log_Trace();
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()
{ {
`Log_Trace(); `Log_Trace();
return ( return (
WorldInfo.Game.BaseMutator == None || WorldInfo.Game.BaseMutator == None ||
WorldInfo.Game.BaseMutator == Self); WorldInfo.Game.BaseMutator == Self);
} }
private function ModifyLoad() private function ModifyLoad()
{ {
local String LoadURL; local String LoadURL;
local String LoadParams; local String LoadParams;
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 PrevServerActorsCount;
local int Index; local int Index;
local GameEngine GameEngine; local GameEngine GameEngine;
`Log_Trace(); `Log_Trace();
LoadURL = WorldInfo.GetLocalURL(); LoadURL = WorldInfo.GetLocalURL();
LoadParams = Mid(LoadURL, InStr(LoadURL, "?")); LoadParams = Mid(LoadURL, InStr(LoadURL, "?"));
MutatorsRaw = WorldInfo.Game.ParseOption(LoadParams, OptMut); MutatorsRaw = WorldInfo.Game.ParseOption(LoadParams, OptMut);
AccessControlRaw = WorldInfo.Game.ParseOption(LoadParams, OptAC); AccessControlRaw = WorldInfo.Game.ParseOption(LoadParams, OptAC);
LoadURL = Repl(LoadURL, Subst(OptMut) $ MutatorsRaw, ""); LoadURL = Repl(LoadURL, Subst(OptMut) $ MutatorsRaw, "");
LoadURL = Repl(LoadURL, Subst(OptAC) $ AccessControlRaw, ""); LoadURL = Repl(LoadURL, Subst(OptAC) $ AccessControlRaw, "");
SML.static.ClearMutators(); SML.static.ClearMutators();
SML.static.ClearServerActors(); SML.static.ClearServerActors();
ParseStringIntoArray(MutatorsRaw, Mutators, ",", true); ParseStringIntoArray(MutatorsRaw, Mutators, ",", true);
Index = 0; Index = 0;
while (Index < Mutators.Length) while (Index < Mutators.Length)
{ {
if (SML.static.AddMutator(Mutators[Index]) || if (SML.static.AddMutator(Mutators[Index]) ||
Mutators[Index] ~= SML.static.GetName(Self)) Mutators[Index] ~= SML.static.GetName(Self))
{ {
Mutators.Remove(Index, 1); Mutators.Remove(Index, 1);
} }
else else
{ {
++Index; ++Index;
} }
} }
GameEngine = GameEngine(Class'Engine'.static.GetEngine()); GameEngine = GameEngine(Class'Engine'.static.GetEngine());
if (GameEngine == None) if (GameEngine == None)
{ {
`Log_Error("GameEngine is None, skip loading server actors"); `Log_Error("GameEngine is None, skip loading server actors");
} }
else else
{ {
PrevServerActorsCount = GameEngine.ServerActors.Length; PrevServerActorsCount = GameEngine.ServerActors.Length;
Index = 0; Index = 0;
while (Index < GameEngine.ServerActors.Length) while (Index < GameEngine.ServerActors.Length)
{ {
if (SML.static.AddServerActor(GameEngine.ServerActors[Index])) if (SML.static.AddServerActor(GameEngine.ServerActors[Index]))
{ {
GameEngine.ServerActors.Remove(Index, 1); GameEngine.ServerActors.Remove(Index, 1);
} }
else else
{ {
++Index; ++Index;
} }
} }
if (GameEngine.ServerActors.Length != PrevServerActorsCount) if (GameEngine.ServerActors.Length != PrevServerActorsCount)
{ {
GameEngine.SaveConfig(); GameEngine.SaveConfig();
} }
} }
SML.static.StaticSaveConfig(); SML.static.StaticSaveConfig();
JoinArray(Mutators, MutatorsRaw); JoinArray(Mutators, MutatorsRaw);
LoadURL $= (Subst(OptMut) $ MutatorsRaw); LoadURL $= (Subst(OptMut) $ MutatorsRaw);
if (SML.static.WantsToSpawn()) if (SML.static.WantsToSpawn())
{ {
LoadURL $= (Subst(OptAC) $ SML.static.GetName()); LoadURL $= (Subst(OptAC) $ SML.static.GetName());
} }
`Log_Info("Loader modified, do server travel..."); `Log_Info("Loader modified, do server travel...");
WorldInfo.ServerTravel(LoadURL, true); WorldInfo.ServerTravel(LoadURL, true);
} WorldInfo.ForceGarbageCollection(true);
}
private static function String Subst(String Option)
{ private static function String Subst(String Option)
return ("?" $ Option $ "="); {
} return ("?" $ Option $ "=");
}
defaultproperties
{ defaultproperties
{
GroupNames.Add("AccessControl")
} }

View File

@ -1,4 +1,4 @@
[Flags] [Flags]
AllowDownload=False AllowDownload=False
ClientOptional=False ClientOptional=False
ServerSideOnly=True ServerSideOnly=True

View File

@ -1,331 +1,342 @@
class SafeMutLoader extends KFAccessControl class SafeMutLoader extends KFAccessControl
config(SML); config(SML);
struct CMR struct CMR
{ {
var String Mutator; var String Mutator;
var String Replacement; var String Replacement;
}; };
var private Array<Actor> ActiveMutators; var private Array<Actor> ActiveMutators;
var private Array<Actor> ActiveServerActors; var private Array<Actor> ActiveServerActors;
var private Array<CMR> CustomMutReplacements; var private Array<CMR> CustomMutReplacements;
var private Array<String> SystemServerActors; 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; var private config Array<String> ServerActors;
public function PreBeginPlay() public function PreBeginPlay()
{ {
LogLevel = GetLogLevel(); LogLevel = GetLogLevel();
`Log_Trace(); `Log_Trace();
Super.PreBeginPlay(); Super.PreBeginPlay();
LoadMutators(); LoadMutators();
LoadServerActors(); LoadServerActors();
} }
public function PostBeginPlay() public function PostBeginPlay()
{ {
local KFGI_Access KFGIA; `Log_Trace();
`Log_Trace(); Super.PostBeginPlay();
Super.PreBeginPlay(); RestoreServerActors();
RestoreServerActors(); SetTimer(2.0f, false, nameof(CheckStatus));
}
KFGIA = GetKFGIA();
if (KFGIA == None) private function CheckStatus()
{ {
`Log_Error("Can't check ranked status"); local KFGI_Access KFGIA;
}
else if (KFGIA.IsRankedGame()) `Log_Trace();
{
`Log_Info("Mutators and server actors successfully loaded! Your server is RANKED!"); KFGIA = GetKFGIA();
} if (KFGIA == None)
else {
{ `Log_Error("Can't check ranked status");
`Log_Warn("Your server is UNRANKED! Check the mutators and server actors you are using. Maybe some of them are incompatible with SML"); }
} else if (KFGIA.IsRankedGame())
} {
`Log_Info("Mutators and server actors successfully loaded! Your server is RANKED!");
private function KFGI_Access GetKFGIA() }
{ else
local KFGameInfo KFGI; {
`Log_Warn("Your server is UNRANKED! Check the mutators and server actors you are using. Maybe some of them are incompatible with SML");
if (WorldInfo == None || WorldInfo.Game == None) }
{ }
return None;
} private function KFGI_Access GetKFGIA()
{
KFGI = KFGameInfo(WorldInfo.Game); local KFGameInfo KFGI;
if (KFGI == None)
{ if (WorldInfo == None || WorldInfo.Game == None)
return None; {
} return None;
}
return new(KFGI) class'KFGI_Access';
} KFGI = KFGameInfo(WorldInfo.Game);
if (KFGI == None)
private function LoadMutators() {
{ return None;
local String MutString; }
local class<Mutator> MutClass;
local class<Actor> ActClass; return new(KFGI) class'KFGI_Access';
local Actor ServerActor; }
`Log_Trace(); private function LoadMutators()
{
foreach Mutators(MutString) local String MutString;
{ local class<Mutator> MutClass;
MutClass = class<Mutator>(DynamicLoadObject(MutString, class'Class')); local class<Actor> ActClass;
if (MutClass == None) local Actor ServerActor;
{
`Log_Error("Can't load mutator:" @ MutString); `Log_Trace();
continue;
} foreach Mutators(MutString)
{
ActClass = GetMutReplacement(MutClass); MutClass = class<Mutator>(DynamicLoadObject(MutString, class'Class'));
if (ActClass == None) if (MutClass == None)
{ {
`Log_Warn("Incompatible:" @ MutString @ "(skip)"); `Log_Error("Can't load mutator:" @ MutString);
continue; continue;
} }
ServerActor = WorldInfo.Spawn(ActClass); ActClass = GetMutReplacement(MutClass);
if (ServerActor == None) if (ActClass == None)
{ {
`Log_Error("Can't spawn:" @ MutString); `Log_Warn("Incompatible:" @ MutString @ "(skip)");
continue; continue;
} }
ActiveMutators.AddItem(ServerActor); ServerActor = WorldInfo.Spawn(ActClass);
`Log_Info("Loaded:" @ MutString); if (ServerActor == None)
} {
} `Log_Error("Can't spawn:" @ MutString);
continue;
private function LoadServerActors() }
{
local String ActorString; ActiveMutators.AddItem(ServerActor);
local class<Actor> ActorClass; `Log_Info("Loaded:" @ MutString);
local Actor ServerActor; }
}
foreach ServerActors(ActorString)
{ private function LoadServerActors()
ActorClass = class<Actor>(DynamicLoadObject(ActorString, class'Class')); {
if (ActorClass == None) local String ActorString;
{ local class<Actor> ActorClass;
`Log_Error("Can't load server actor:" @ ActorString); local Actor ServerActor;
continue;
} foreach ServerActors(ActorString)
{
ServerActor = WorldInfo.Spawn(ActorClass); ActorClass = class<Actor>(DynamicLoadObject(ActorString, class'Class'));
if (ServerActor == None) if (ActorClass == None)
{ {
`Log_Error("Can't spawn:" @ ActorString); `Log_Error("Can't load server actor:" @ ActorString);
continue; continue;
} }
ActiveServerActors.AddItem(ServerActor); ServerActor = WorldInfo.Spawn(ActorClass);
`Log_Info("Loaded:" @ ActorString); if (ServerActor == None)
} {
} `Log_Error("Can't spawn:" @ ActorString);
continue;
private function RestoreServerActors() }
{
local GameEngine GameEngine; ActiveServerActors.AddItem(ServerActor);
local String ActorString; `Log_Info("Loaded:" @ ActorString);
local int PrevServerActorsCount; }
}
GameEngine = GameEngine(Class'Engine'.static.GetEngine());
private function RestoreServerActors()
if (GameEngine == None) {
{ local GameEngine GameEngine;
`Log_Error("GameEngine is None! Can't restore ServerActors!"); local String ActorString;
return; local int PrevServerActorsCount;
}
GameEngine = GameEngine(Class'Engine'.static.GetEngine());
PrevServerActorsCount = GameEngine.ServerActors.Length;
foreach ServerActors(ActorString) if (GameEngine == None)
{ {
if (GameEngine.ServerActors.Find(ActorString) != INDEX_NONE) `Log_Error("GameEngine is None! Can't restore ServerActors!");
{ return;
GameEngine.ServerActors.AddItem(ActorString); }
}
} PrevServerActorsCount = GameEngine.ServerActors.Length;
foreach ServerActors(ActorString)
if (GameEngine.ServerActors.Length != PrevServerActorsCount) {
{ if (GameEngine.ServerActors.Find(ActorString) != INDEX_NONE)
GameEngine.SaveConfig(); {
} GameEngine.ServerActors.AddItem(ActorString);
} }
}
public static function bool AddServerActor(String ServerActor)
{ if (GameEngine.ServerActors.Length != PrevServerActorsCount)
local class<Actor> ActorClass; {
GameEngine.SaveConfig();
if (default.SystemServerActors.Find(ServerActor) != INDEX_NONE) }
{ }
return false;
} public static function bool AddServerActor(String ServerActor)
{
ActorClass = class<Actor>(DynamicLoadObject(ServerActor, class'Class')); local class<Actor> ActorClass;
if (ActorClass == None) if (default.SystemServerActors.Find(ServerActor) != INDEX_NONE)
{ {
return false; return false;
} }
if (ClassIsChildOf(ActorClass, class'Mutator')) ActorClass = class<Actor>(DynamicLoadObject(ServerActor, class'Class'));
{
return false; if (ActorClass == None)
} {
return false;
if (default.ServerActors.Find(ServerActor) == INDEX_NONE) }
{
default.ServerActors.AddItem(ServerActor); if (ClassIsChildOf(ActorClass, class'Mutator'))
} {
return false;
return true; }
}
if (default.ServerActors.Find(ServerActor) == INDEX_NONE)
public static function bool AddMutator(String MutString) {
{ default.ServerActors.AddItem(ServerActor);
if (GetMutStringReplacement(MutString) != None) }
{
if (default.Mutators.Find(MutString) == INDEX_NONE) return true;
{ }
default.Mutators.AddItem(MutString);
} public static function bool AddMutator(String MutString)
return true; {
} if (GetMutStringReplacement(MutString) != None)
{
return false; if (default.Mutators.Find(MutString) == INDEX_NONE)
} {
default.Mutators.AddItem(MutString);
public static function ClearMutators() }
{ return true;
default.Mutators.Length = 0; }
}
return false;
public static function ClearServerActors() }
{
default.ServerActors.Length = 0; public static function ClearMutators()
} {
default.Mutators.Length = 0;
public static function bool WantsToSpawn() }
{
return (default.Mutators.Length > 0); public static function ClearServerActors()
} {
default.ServerActors.Length = 0;
public static function String GetName(optional Object O) }
{
if (O == None) public static function bool WantsToSpawn()
{ {
return (default.class.GetPackageName() $ "." $ String(default.class)); return (default.Mutators.Length > 0);
} }
else
{ public static function String GetName(optional Object O)
return (O.class.GetPackageName() $ "." $ String(O.class)); {
} if (O == None)
} {
return (default.class.GetPackageName() $ "." $ String(default.class));
public static function String GetMutName(class<Mutator> CMut) }
{ else
if (CMut == None) return ""; {
return (O.class.GetPackageName() $ "." $ String(O.class));
return CMut.GetPackageName() $ "." $ String(CMut); }
} }
public function PostLogin(PlayerController C) public static function String GetMutName(class<Mutator> CMut)
{ {
local Actor A; if (CMut == None) return "";
`Log_Trace(); return CMut.GetPackageName() $ "." $ String(CMut);
}
if (C != None)
{ public function PostLogin(PlayerController C)
foreach ActiveMutators(A) {
{ local Actor A;
A.GetTargetLocation(C, false);
} `Log_Trace();
}
if (C != None)
Super.PostLogin(C); {
} foreach ActiveMutators(A)
{
public function OnClientConnectionClose(Player ClientConnection) A.GetTargetLocation(C, false);
{ }
local Controller C; }
local Actor A;
Super.PostLogin(C);
`Log_Trace(); }
C = ClientConnection.Actor; public function OnClientConnectionClose(Player ClientConnection)
if (C != None) {
{ local Controller C;
foreach ActiveMutators(A) local Actor A;
{
A.GetTargetLocation(C, true); `Log_Trace();
}
} C = ClientConnection.Actor;
if (C != None)
Super.OnClientConnectionClose(ClientConnection); {
} foreach ActiveMutators(A)
{
public static function E_LogLevel GetLogLevel() A.GetTargetLocation(C, true);
{ }
if (default.LogLevel == LL_WrongLevel) }
{
default.LogLevel = LL_Info; Super.OnClientConnectionClose(ClientConnection);
StaticSaveConfig(); }
}
public static function E_LogLevel GetLogLevel()
return default.LogLevel; {
} if (default.LogLevel == LL_WrongLevel)
{
private static function class<Actor> GetMutReplacement(class<Mutator> MutClass) default.LogLevel = LL_Info;
{ StaticSaveConfig();
local int Index; }
local String Replacement;
return default.LogLevel;
if (MutClass == None) return None; }
Index = default.CustomMutReplacements.Find('Mutator', GetMutName(MutClass)); private static function class<Actor> GetMutReplacement(class<Mutator> MutClass)
if (Index != INDEX_NONE) {
{ local int Index;
Replacement = default.CustomMutReplacements[Index].Replacement; local String Replacement;
}
else if (MutClass.static.GetLocalString() == "") if (MutClass == None) return None;
{
return None; Index = default.CustomMutReplacements.Find('Mutator', GetMutName(MutClass));
} if (Index != INDEX_NONE)
else {
{ Replacement = default.CustomMutReplacements[Index].Replacement;
Replacement = MutClass.GetPackageName() $ "." $ MutClass.static.GetLocalString(); }
} else if (MutClass.static.GetLocalString() == "")
{
return class<Actor>(DynamicLoadObject(Replacement, class'Class')); return None;
} }
else
private static function class<Actor> GetMutStringReplacement(String MutString) {
{ Replacement = MutClass.GetPackageName() $ "." $ MutClass.static.GetLocalString();
return GetMutReplacement(class<Mutator>(DynamicLoadObject(MutString, class'Class'))); }
}
return class<Actor>(DynamicLoadObject(Replacement, class'Class'));
defaultproperties }
{
CustomMutReplacements.Add({( private static function class<Actor> GetMutStringReplacement(String MutString)
Mutator="UnofficialKFPatch.UKFPMutator", {
Replacement="UnofficialKFPatch.UKFPReplicationInfo" return GetMutReplacement(class<Mutator>(DynamicLoadObject(MutString, class'Class')));
)}) }
CustomMutReplacements.Add({(
Mutator="UnofficialKFPatch.UKFPMutatorNW", defaultproperties
Replacement="UnofficialKFPatch.UKFPReplicationInfo" {
)}) // Looks like this method is no longer needed to load UKFP
// But I'll leave this commented just in case
SystemServerActors.Add("IpDrv.WebServer") /*
CustomMutReplacements.Add({(
Mutator="UnofficialKFPatch.UKFPMutator",
Replacement="UnofficialKFPatch.UKFPReplicationInfo"
)})
CustomMutReplacements.Add({(
Mutator="UnofficialKFPatch.UKFPMutatorNW",
Replacement="UnofficialKFPatch.UKFPReplicationInfo"
)})
*/
SystemServerActors.Add("IpDrv.WebServer")
} }

View File

@ -1,20 +1,20 @@
class _Logger extends Object class _Logger extends Object
abstract; abstract;
enum E_LogLevel enum E_LogLevel
{ {
LL_WrongLevel, LL_WrongLevel,
LL_None, LL_None,
LL_Fatal, LL_Fatal,
LL_Error, LL_Error,
LL_Warning, LL_Warning,
LL_Info, LL_Info,
LL_Debug, LL_Debug,
LL_Trace, LL_Trace,
LL_All LL_All
}; };
defaultproperties defaultproperties
{ {
} }

View File

@ -1,2 +1,2 @@
// Constants // Constants
`define NO_CONFIG 0 `define NO_CONFIG 0

View File

@ -1,3 +1,3 @@
// Imports // Imports
`include(Logger.uci) `include(Logger.uci)
`include(Constants.uci) `include(Constants.uci)

View File

@ -1,15 +1,15 @@
// Logger // Logger
`define Log_Tag 'SML' `define Log_Tag 'SML'
`define LocationStatic "`{ClassName}::" $ GetFuncName() `define LocationStatic "`{ClassName}::" $ GetFuncName()
`define Log_Base(msg, cond) `log(`msg `if(`cond), `cond`{endif}, `Log_Tag) `define Log_Base(msg, cond) `log(`msg `if(`cond), `cond`{endif}, `Log_Tag)
`define Log_Fatal(msg) `log("FATAL:" @ `msg, (LogLevel >= LL_Fatal), `Log_Tag) `define Log_Fatal(msg) `log("FATAL:" @ `msg, (LogLevel >= LL_Fatal), `Log_Tag)
`define Log_Error(msg) `log("ERROR:" @ `msg, (LogLevel >= LL_Error), `Log_Tag) `define Log_Error(msg) `log("ERROR:" @ `msg, (LogLevel >= LL_Error), `Log_Tag)
`define Log_Warn(msg) `log("WARN:" @ `msg, (LogLevel >= LL_Warning), `Log_Tag) `define Log_Warn(msg) `log("WARN:" @ `msg, (LogLevel >= LL_Warning), `Log_Tag)
`define Log_Info(msg) `log("INFO:" @ `msg, (LogLevel >= LL_Info), `Log_Tag) `define Log_Info(msg) `log("INFO:" @ `msg, (LogLevel >= LL_Info), `Log_Tag)
`define Log_Debug(msg) `log("DEBUG:" @ `msg, (LogLevel >= LL_Debug), `Log_Tag) `define Log_Debug(msg) `log("DEBUG:" @ `msg, (LogLevel >= LL_Debug), `Log_Tag)
`define Log_Trace(msg) `log("TRACE:" @ `Location `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) `define Log_Trace(msg) `log("TRACE:" @ `Location `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag)
`define Log_TraceStatic(msg) `log("TRACE:" @ `LocationStatic `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) `define Log_TraceStatic(msg) `log("TRACE:" @ `LocationStatic `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag)

View File

@ -7,7 +7,7 @@ StripSource="True"
# Mutators to be compiled # Mutators to be compiled
# Specify them with a space as a separator, # Specify them with a space as a separator,
# Mutators will be compiled in the specified order # Mutators will be compiled in the specified order
PackageBuildOrder="SML" PackageBuildOrder="SML"
@ -16,7 +16,7 @@ PackageBuildOrder="SML"
# Packages you want to brew using @peelz's patched KFEditor. # Packages you want to brew using @peelz's patched KFEditor.
# Useful for cases where regular brew doesn't put *.upk inside the package. # Useful for cases where regular brew doesn't put *.upk inside the package.
# Specify them with a space as a separator, # Specify them with a space as a separator,
# The order doesn't matter # The order doesn't matter
PackagePeelzBrew="" PackagePeelzBrew=""
@ -24,7 +24,7 @@ PackagePeelzBrew=""
# Mutators that will be uploaded to the workshop # Mutators that will be uploaded to the workshop
# Specify them with a space as a separator, # Specify them with a space as a separator,
# The order doesn't matter # The order doesn't matter
PackageUpload="SML" PackageUpload="SML"

2
tools

@ -1 +1 @@
Subproject commit 88b35bd7ebb7e30448579f1564220398f990541c Subproject commit fb458ac61f7e6c6426b8dff366dd5e7499e0d95f