diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a3108ab --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml new file mode 100644 index 0000000..24897f3 --- /dev/null +++ b/.github/workflows/mega-linter.yml @@ -0,0 +1,72 @@ +--- +name: MegaLinter + +permissions: read-all + +on: + push: + pull_request: + branches: [master] + +env: + APPLY_FIXES: none + APPLY_FIXES_EVENT: pull_request + APPLY_FIXES_MODE: commit + DISABLE: SPELL + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + name: MegaLinter + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} + + - name: MegaLinter + id: ml + uses: oxsecurity/megalinter@v6 + env: + VALIDATE_ALL_CODEBASE: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Archive production artifacts + if: ${{ success() }} || ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: MegaLinter reports + path: | + megalinter-reports + mega-linter.log + + - name: Create Pull Request with applied fixes + id: cpr + if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) + uses: peter-evans/create-pull-request@v5 + 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: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'pull_request' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}" + + - name: Prepare commit + if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) + run: sudo chown -Rc $UID .git/ + - name: Commit and push applied linter fixes + if: steps.ml.outputs.has_updated_sources == 1 && (env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) && env.APPLY_FIXES_MODE == 'commit' && github.ref != 'refs/heads/main' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }} + commit_message: "[MegaLinter] Apply linters fixes" + commit_user_name: megalinter-bot + commit_user_email: nicolas.vuillamy@ox.security diff --git a/PublicationContent/description.txt b/PublicationContent/description.txt index e146f06..6db6d63 100644 --- a/PublicationContent/description.txt +++ b/PublicationContent/description.txt @@ -16,7 +16,7 @@ No. This mod is not whitelisted and will de-rank your server. Any XP gained will [h1]Usage (single player)[/h1] [olist] [*]Subscribe to this mutator; -[*]Start KF2; +[*]Start KF2; [*]Open console (~) and input: [b]open KF-BioticsLab?Mutator=ZedSpawner.ZedSpawnerMut[/b] (replace the map and add the parameters you need) diff --git a/README.md b/README.md index d216159..eb5d742 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,29 @@ # ZedSpawner [![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=2811290931) +[![Steam Downloads](https://img.shields.io/steam/downloads/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931) [![Steam Favorites](https://img.shields.io/steam/favorites/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931) -[![Steam Update Date](https://img.shields.io/steam/update-date/2811290931)](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931) +[![MegaLinter](https://github.com/GenZmeY/KF2-ZedSpawner/actions/workflows/mega-linter.yml/badge.svg?branch=master)](https://github.com/GenZmeY/KF2-ZedSpawner/actions/workflows/mega-linter.yml) [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/GenZmeY/KF2-ZedSpawner)](https://github.com/GenZmeY/KF2-ZedSpawner/tags) [![GitHub](https://img.shields.io/github/license/GenZmeY/KF2-ZedSpawner)](LICENSE) -# Description +## Description Spawner for zeds. Started as a modification of the [this version](https://steamcommunity.com/sharedfiles/filedetails/?id=2488241348), but now there is almost nothing left of the previous mutator, lol -# Features +## Features - spawn without increasing zed counter; - spawn depends on the number of players; - cyclic spawn (useful for endless mode); - separate spawn for special waves and boss waves; - spawn after a certain percentage of killed zeds. -# Usage & Setup +## Usage & Setup [See steam workshop page](https://steamcommunity.com/sharedfiles/filedetails/?id=2811290931) -# Spawn calculator +## Spawn calculator [Spawn Calculator](https://docs.google.com/spreadsheets/d/1q67WJ36jhj6Y0lPNO5tS2bU79Wphu4Xmi62me6DAwtM/edit?usp=sharing) -# Build +## Build **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); 2. open git-bash and go to any folder where you want to store sources: @@ -36,17 +37,10 @@ Spawner for zeds. Started as a modification of the [this version](https://steamc 5. The compiled files will be here: `C:\Users\\Documents\My Games\KillingFloor2\KFGame\Unpublished\BrewedPC\Script\` -# Testing -Open git-bash in the source folder and run command: -`./tools/builder -t` -(or `./tools/builder -ct` if you haven't compiled the mutator yet) - -A local single-user test will be launched with parameters from `builder.cfg` (edit this file if you want to test mutator with different parameters). - -# Bug reports +## Bug reports If you find a bug, go to the [issue page](https://github.com/GenZmeY/KF2-ZedSpawner/issues) and check if there is a description of your bug. If not, create a new issue. Describe what the bug looks like and how reproduce it. Attaching your KFZedSpawner.ini and Launch.log can also be helpful. -# License -[GNU GPLv3](LICENSE) +## License +[![license](https://www.gnu.org/graphics/gplv3-with-text-136x68.png)](LICENSE) diff --git a/ZedSpawner/Classes/KFGI_Access.uc b/ZedSpawner/Classes/KFGI_Access.uc index ce88cfc..df1baae 100644 --- a/ZedSpawner/Classes/KFGI_Access.uc +++ b/ZedSpawner/Classes/KFGI_Access.uc @@ -5,12 +5,12 @@ public function Array > GetAIClassList(E_LogLevel LogLevel { local Array > RV; local class KFPMC; - + `Log_Trace(); - + foreach AIClassList(KFPMC) RV.AddItem(KFPMC); - + return RV; } @@ -18,12 +18,12 @@ public function Array > GetNonSpawnAIClassList(E_LogLevel { local Array > RV; local class KFPMC; - + `Log_Trace(); - + foreach NonSpawnAIClassList(KFPMC) RV.AddItem(KFPMC); - + return RV; } @@ -31,12 +31,12 @@ public function Array > GetAIBossClassList(E_LogLevel LogL { local Array > RV; local class KFPMC; - + `Log_Trace(); - + foreach AIBossClassList(KFPMC) RV.AddItem(KFPMC); - + return RV; } @@ -51,39 +51,39 @@ public function bool IsCustomZed(class KFPM, E_LogLevel LogLevel public function bool IsOriginalAI(class KFPM, optional out EAIType AIType, optional E_LogLevel LogLevel = LL_None) { local int Type; - + `Log_Trace(); - + Type = AIClassList.Find(KFPM); if (Type != INDEX_NONE) { AIType = EAIType(Type); return true; } - + return false; } public function bool IsOriginalAIBoss(class KFPM, optional out EBossAIType AIType, optional E_LogLevel LogLevel = LL_None) { local int Type; - + `Log_Trace(); - + Type = AIBossClassList.Find(KFPM); if (Type != INDEX_NONE) { AIType = EBossAIType(Type); return true; } - + return false; } public function class AITypePawn(EAIType AIType, E_LogLevel LogLevel) { `Log_Trace(); - + if (AIType < AIClassList.Length) return AIClassList[AIType]; else @@ -93,7 +93,7 @@ public function class AITypePawn(EAIType AIType, E_LogLevel LogL public function class BossAITypePawn(EBossAIType AIType, E_LogLevel LogLevel) { `Log_Trace(); - + if (AIType < AIBossClassList.Length) return AIBossClassList[AIType]; else @@ -102,5 +102,5 @@ public function class BossAITypePawn(EBossAIType AIType, E_LogLe defaultproperties { - + } diff --git a/ZedSpawner/Classes/Spawn.uc b/ZedSpawner/Classes/Spawn.uc index fa30a7f..0dcffef 100644 --- a/ZedSpawner/Classes/Spawn.uc +++ b/ZedSpawner/Classes/Spawn.uc @@ -1,107 +1,107 @@ -class Spawn extends Object - dependson(ZedSpawner) - config(ZedSpawner); - -var public config bool bCyclicalSpawn; -var public config bool bShadowSpawn; -var public config float ZedTotalMultiplier; -var public config float SpawnTotalPlayerMultiplier; -var public config float SpawnTotalCycleMultiplier; -var public config float SingleSpawnLimitMultiplier; -var public config float SingleSpawnLimitPlayerMultiplier; -var public config float SingleSpawnLimitCycleMultiplier; -var public config int AliveSpawnLimit; -var public config bool bSmoothSpawn; - -public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - switch (Version) - { - case `NO_CONFIG: - ApplyDefault(LogLevel); - - case 3: - default.bSmoothSpawn = false; - - default: break; - } - - if (LatestVersion != Version) - { - StaticSaveConfig(); - } -} - -private static function ApplyDefault(E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - default.bCyclicalSpawn = true; - default.bShadowSpawn = true; - default.bSmoothSpawn = true; - default.ZedTotalMultiplier = 1.0; - default.SpawnTotalPlayerMultiplier = 0.75; - default.SpawnTotalCycleMultiplier = 0.75; - default.SingleSpawnLimitMultiplier = 1.0; - default.SingleSpawnLimitPlayerMultiplier = 0.75; - default.SingleSpawnLimitCycleMultiplier = 0.75; - default.AliveSpawnLimit = 0; -} - -public static function bool Load(E_LogLevel LogLevel) -{ - local bool Errors; - - `Log_TraceStatic(); - - if (default.ZedTotalMultiplier <= 0.f) - { - `Log_Error("ZedTotalMultiplier" @ "(" $ default.ZedTotalMultiplier $ ")" @ "must be greater than 0.0"); - Errors = true; - } - - if (default.SpawnTotalPlayerMultiplier < 0.f) - { - `Log_Error("SpawnTotalPlayerMultiplier" @ "(" $ default.SpawnTotalPlayerMultiplier $ ")" @ "must be greater than or equal 0.0"); - Errors = true; - } - - if (default.SpawnTotalCycleMultiplier < 0.f) - { - `Log_Error("SpawnTotalCycleMultiplier" @ "(" $ default.SpawnTotalCycleMultiplier $ ")" @ "must be greater than or equal 0.0"); - Errors = true; - } - - if (default.SingleSpawnLimitMultiplier <= 0.f) - { - `Log_Error("SingleSpawnLimitMultiplier" @ "(" $ default.SingleSpawnLimitMultiplier $ ")" @ "must be greater than 0.0"); - Errors = true; - } - - if (default.SingleSpawnLimitPlayerMultiplier < 0.f) - { - `Log_Error("SingleSpawnLimitPlayerMultiplier" @ "(" $ default.SingleSpawnLimitPlayerMultiplier $ ")" @ "must be greater than or equal 0.0"); - Errors = true; - } - - if (default.SingleSpawnLimitCycleMultiplier < 0.f) - { - `Log_Error("SingleSpawnLimitCycleMultiplier" @ "(" $ default.SingleSpawnLimitCycleMultiplier $ ")" @ "must be greater than or equal 0.0"); - Errors = true; - } - - if (default.AliveSpawnLimit < 0) - { - `Log_Error("AliveSpawnLimit" @ "(" $ default.AliveSpawnLimit $ ")" @ "must be greater than or equal 0"); - Errors = true; - } - - return !Errors; -} - -defaultproperties -{ - -} +class Spawn extends Object + dependson(ZedSpawner) + config(ZedSpawner); + +var public config bool bCyclicalSpawn; +var public config bool bShadowSpawn; +var public config float ZedTotalMultiplier; +var public config float SpawnTotalPlayerMultiplier; +var public config float SpawnTotalCycleMultiplier; +var public config float SingleSpawnLimitMultiplier; +var public config float SingleSpawnLimitPlayerMultiplier; +var public config float SingleSpawnLimitCycleMultiplier; +var public config int AliveSpawnLimit; +var public config bool bSmoothSpawn; + +public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(LogLevel); + + case 3: + default.bSmoothSpawn = false; + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault(E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + default.bCyclicalSpawn = true; + default.bShadowSpawn = true; + default.bSmoothSpawn = true; + default.ZedTotalMultiplier = 1.0; + default.SpawnTotalPlayerMultiplier = 0.75; + default.SpawnTotalCycleMultiplier = 0.75; + default.SingleSpawnLimitMultiplier = 1.0; + default.SingleSpawnLimitPlayerMultiplier = 0.75; + default.SingleSpawnLimitCycleMultiplier = 0.75; + default.AliveSpawnLimit = 0; +} + +public static function bool Load(E_LogLevel LogLevel) +{ + local bool Errors; + + `Log_TraceStatic(); + + if (default.ZedTotalMultiplier <= 0.f) + { + `Log_Error("ZedTotalMultiplier" @ "(" $ default.ZedTotalMultiplier $ ")" @ "must be greater than 0.0"); + Errors = true; + } + + if (default.SpawnTotalPlayerMultiplier < 0.f) + { + `Log_Error("SpawnTotalPlayerMultiplier" @ "(" $ default.SpawnTotalPlayerMultiplier $ ")" @ "must be greater than or equal 0.0"); + Errors = true; + } + + if (default.SpawnTotalCycleMultiplier < 0.f) + { + `Log_Error("SpawnTotalCycleMultiplier" @ "(" $ default.SpawnTotalCycleMultiplier $ ")" @ "must be greater than or equal 0.0"); + Errors = true; + } + + if (default.SingleSpawnLimitMultiplier <= 0.f) + { + `Log_Error("SingleSpawnLimitMultiplier" @ "(" $ default.SingleSpawnLimitMultiplier $ ")" @ "must be greater than 0.0"); + Errors = true; + } + + if (default.SingleSpawnLimitPlayerMultiplier < 0.f) + { + `Log_Error("SingleSpawnLimitPlayerMultiplier" @ "(" $ default.SingleSpawnLimitPlayerMultiplier $ ")" @ "must be greater than or equal 0.0"); + Errors = true; + } + + if (default.SingleSpawnLimitCycleMultiplier < 0.f) + { + `Log_Error("SingleSpawnLimitCycleMultiplier" @ "(" $ default.SingleSpawnLimitCycleMultiplier $ ")" @ "must be greater than or equal 0.0"); + Errors = true; + } + + if (default.AliveSpawnLimit < 0) + { + `Log_Error("AliveSpawnLimit" @ "(" $ default.AliveSpawnLimit $ ")" @ "must be greater than or equal 0"); + Errors = true; + } + + return !Errors; +} + +defaultproperties +{ + +} diff --git a/ZedSpawner/Classes/SpawnAtPlayerStart.uc b/ZedSpawner/Classes/SpawnAtPlayerStart.uc index 351bfaf..6979a2a 100644 --- a/ZedSpawner/Classes/SpawnAtPlayerStart.uc +++ b/ZedSpawner/Classes/SpawnAtPlayerStart.uc @@ -1,80 +1,80 @@ -class SpawnAtPlayerStart extends Object - dependson(ZedSpawner) - config(ZedSpawner); - -var private config Array ZedClass; -var public config Array Map; - -public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - switch (Version) - { - case `NO_CONFIG: - case 2: - ApplyDefault(LogLevel); - - default: break; - } - - if (LatestVersion != Version) - { - StaticSaveConfig(); - } -} - -private static function ApplyDefault(E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - default.ZedClass.Length = 0; - default.ZedClass.AddItem("HL2Monsters.Combine_Strider"); - default.ZedClass.AddItem("HL2Monsters.Combine_Gunship"); - default.ZedClass.AddItem("HL2Monsters.Hunter_Chopper"); - default.ZedClass.AddItem("SomePackage.SomeZedClassYouWantToSpawnAtPlayerStart"); - - default.Map.Length = 0; - default.Map.AddItem("KF-SomeMapNameWhereYouWantSpawnZedsAtPlayerStart"); -} - -public static function Array > Load(E_LogLevel LogLevel) -{ - local Array > ZedList; - local class KFPMC; - local String ZedClassTmp; - local int Line; - - `Log_TraceStatic(); - - `Log_Info("Load zeds to spawn at player start:"); - foreach default.ZedClass(ZedClassTmp, Line) - { - KFPMC = class(DynamicLoadObject(ZedClassTmp, class'Class')); - if (KFPMC == None) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ ZedClassTmp); - } - else - { - ZedList.AddItem(KFPMC); - `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ZedClassTmp); - } - } - - if (ZedList.Length == default.ZedClass.Length) - { - `Log_Info("Spawn at player start list (Zeds) loaded successfully (" $ default.ZedClass.Length @ "entries)"); - } - else - { - `Log_Info("Spawn at player start list (Zeds): loaded" @ ZedList.Length @ "of" @ default.ZedClass.Length @ "entries"); - } - - return ZedList; -} - -defaultproperties -{ - -} +class SpawnAtPlayerStart extends Object + dependson(ZedSpawner) + config(ZedSpawner); + +var private config Array ZedClass; +var public config Array Map; + +public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + switch (Version) + { + case `NO_CONFIG: + case 2: + ApplyDefault(LogLevel); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault(E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + default.ZedClass.Length = 0; + default.ZedClass.AddItem("HL2Monsters.Combine_Strider"); + default.ZedClass.AddItem("HL2Monsters.Combine_Gunship"); + default.ZedClass.AddItem("HL2Monsters.Hunter_Chopper"); + default.ZedClass.AddItem("SomePackage.SomeZedClassYouWantToSpawnAtPlayerStart"); + + default.Map.Length = 0; + default.Map.AddItem("KF-SomeMapNameWhereYouWantSpawnZedsAtPlayerStart"); +} + +public static function Array > Load(E_LogLevel LogLevel) +{ + local Array > ZedList; + local class KFPMC; + local String ZedClassTmp; + local int Line; + + `Log_TraceStatic(); + + `Log_Info("Load zeds to spawn at player start:"); + foreach default.ZedClass(ZedClassTmp, Line) + { + KFPMC = class(DynamicLoadObject(ZedClassTmp, class'Class')); + if (KFPMC == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ ZedClassTmp); + } + else + { + ZedList.AddItem(KFPMC); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ZedClassTmp); + } + } + + if (ZedList.Length == default.ZedClass.Length) + { + `Log_Info("Spawn at player start list (Zeds) loaded successfully (" $ default.ZedClass.Length @ "entries)"); + } + else + { + `Log_Info("Spawn at player start list (Zeds): loaded" @ ZedList.Length @ "of" @ default.ZedClass.Length @ "entries"); + } + + return ZedList; +} + +defaultproperties +{ + +} diff --git a/ZedSpawner/Classes/SpawnListBossWaves.uc b/ZedSpawner/Classes/SpawnListBossWaves.uc index 20469e4..d079871 100644 --- a/ZedSpawner/Classes/SpawnListBossWaves.uc +++ b/ZedSpawner/Classes/SpawnListBossWaves.uc @@ -1,142 +1,142 @@ -class SpawnListBossWaves extends Object - dependson(ZedSpawner) - config(ZedSpawner); - -struct S_SpawnEntryCfg -{ - var String BossClass; - var String ZedClass; - var int Delay; - var int Probability; - var int SpawnCountBase; - var int SingleSpawnLimit; -}; - -var public config bool bStopRegularSpawn; -var private config Array Spawn; - -public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA, E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - switch (Version) - { - case `NO_CONFIG: - ApplyDefault(KFGIA, LogLevel); - - default: break; - } - - if (LatestVersion != Version) - { - StaticSaveConfig(); - } -} - -private static function ApplyDefault(KFGI_Access KFGIA, E_LogLevel LogLevel) -{ - local S_SpawnEntryCfg SpawnEntry; - local Array > KFPM_Bosses; - local class KFPMC; - - `Log_TraceStatic(); - - default.Spawn.Length = 0; - - default.bStopRegularSpawn = true; - default.Spawn.Length = 0; - SpawnEntry.ZedClass = "SomePackage.SomeClass"; - SpawnEntry.SpawnCountBase = 2; - SpawnEntry.SingleSpawnLimit = 1; - SpawnEntry.Delay = 30; - SpawnEntry.Probability = 100; - KFPM_Bosses = KFGIA.GetAIBossClassList(LogLevel); - foreach KFPM_Bosses(KFPMC) - { - SpawnEntry.BossClass = "KFGameContent." $ String(KFPMC); - default.Spawn.AddItem(SpawnEntry); - } -} - -public static function Array Load(E_LogLevel LogLevel) -{ - local Array SpawnList; - local S_SpawnEntryCfg SpawnEntryCfg; - local S_SpawnEntry SpawnEntry; - local int Line; - local bool Errors; - - `Log_TraceStatic(); - - `Log_Info("Load boss waves spawn list:"); - foreach default.Spawn(SpawnEntryCfg, Line) - { - Errors = false; - - SpawnEntry.BossClass = class(DynamicLoadObject(SpawnEntryCfg.BossClass, class'Class')); - if (SpawnEntry.BossClass == None) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load boss class:" @ SpawnEntryCfg.BossClass); - Errors = true; - } - - SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); - if (SpawnEntry.ZedClass == None) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); - Errors = true; - } - - SpawnEntry.RelativeStartDefault = 0.f; - - SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; - if (SpawnEntryCfg.Delay <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; - if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); - Errors = true; - } - - SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; - if (SpawnEntry.SpawnCountBase <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; - if (SpawnEntry.SingleSpawnLimit < 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); - Errors = true; - } - - if (!Errors) - { - SpawnList.AddItem(SpawnEntry); - `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.BossClass $ ")" @ SpawnEntryCfg.ZedClass); - } - } - - if (SpawnList.Length == default.Spawn.Length) - { - `Log_Info("Boss spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); - } - else - { - `Log_Info("Boss spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); - } - - return SpawnList; -} - -defaultproperties -{ - -} +class SpawnListBossWaves extends Object + dependson(ZedSpawner) + config(ZedSpawner); + +struct S_SpawnEntryCfg +{ + var String BossClass; + var String ZedClass; + var int Delay; + var int Probability; + var int SpawnCountBase; + var int SingleSpawnLimit; +}; + +var public config bool bStopRegularSpawn; +var private config Array Spawn; + +public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA, E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(KFGIA, LogLevel); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault(KFGI_Access KFGIA, E_LogLevel LogLevel) +{ + local S_SpawnEntryCfg SpawnEntry; + local Array > KFPM_Bosses; + local class KFPMC; + + `Log_TraceStatic(); + + default.Spawn.Length = 0; + + default.bStopRegularSpawn = true; + default.Spawn.Length = 0; + SpawnEntry.ZedClass = "SomePackage.SomeClass"; + SpawnEntry.SpawnCountBase = 2; + SpawnEntry.SingleSpawnLimit = 1; + SpawnEntry.Delay = 30; + SpawnEntry.Probability = 100; + KFPM_Bosses = KFGIA.GetAIBossClassList(LogLevel); + foreach KFPM_Bosses(KFPMC) + { + SpawnEntry.BossClass = "KFGameContent." $ String(KFPMC); + default.Spawn.AddItem(SpawnEntry); + } +} + +public static function Array Load(E_LogLevel LogLevel) +{ + local Array SpawnList; + local S_SpawnEntryCfg SpawnEntryCfg; + local S_SpawnEntry SpawnEntry; + local int Line; + local bool Errors; + + `Log_TraceStatic(); + + `Log_Info("Load boss waves spawn list:"); + foreach default.Spawn(SpawnEntryCfg, Line) + { + Errors = false; + + SpawnEntry.BossClass = class(DynamicLoadObject(SpawnEntryCfg.BossClass, class'Class')); + if (SpawnEntry.BossClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load boss class:" @ SpawnEntryCfg.BossClass); + Errors = true; + } + + SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); + if (SpawnEntry.ZedClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); + Errors = true; + } + + SpawnEntry.RelativeStartDefault = 0.f; + + SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; + if (SpawnEntryCfg.Delay <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; + if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); + Errors = true; + } + + SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; + if (SpawnEntry.SpawnCountBase <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; + if (SpawnEntry.SingleSpawnLimit < 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); + Errors = true; + } + + if (!Errors) + { + SpawnList.AddItem(SpawnEntry); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.BossClass $ ")" @ SpawnEntryCfg.ZedClass); + } + } + + if (SpawnList.Length == default.Spawn.Length) + { + `Log_Info("Boss spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); + } + else + { + `Log_Info("Boss spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); + } + + return SpawnList; +} + +defaultproperties +{ + +} diff --git a/ZedSpawner/Classes/SpawnListRegular.uc b/ZedSpawner/Classes/SpawnListRegular.uc index 2b8c3a3..b6053a6 100644 --- a/ZedSpawner/Classes/SpawnListRegular.uc +++ b/ZedSpawner/Classes/SpawnListRegular.uc @@ -1,155 +1,155 @@ -class SpawnListRegular extends Object - dependson(ZedSpawner) - config(ZedSpawner); - -struct S_SpawnEntryCfg -{ - var int Wave; - var String ZedClass; - var int RelativeStart; - var int Delay; - var int Probability; - var int SpawnCountBase; - var int SingleSpawnLimit; -}; - -var public config Array Spawn; - -delegate int SpawnListSort(S_SpawnEntryCfg A, S_SpawnEntryCfg B) -{ - return A.Wave > B.Wave ? -1 : 0; -} - -public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA, E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - switch (Version) - { - case `NO_CONFIG: - ApplyDefault(KFGIA, LogLevel); - - default: break; - } - - if (LatestVersion != Version) - { - StaticSaveConfig(); - } -} - -private static function ApplyDefault(KFGI_Access KFGIA, E_LogLevel LogLevel) -{ - local S_SpawnEntryCfg SpawnEntry; - local Array > KFPM_Zeds; - local class KFPMC; - - `Log_TraceStatic(); - - default.Spawn.Length = 0; - - SpawnEntry.Wave = 0; - SpawnEntry.SpawnCountBase = 2; - SpawnEntry.SingleSpawnLimit = 1; - SpawnEntry.RelativeStart = 25; - SpawnEntry.Delay = 60; - SpawnEntry.Probability = 100; - - KFPM_Zeds = KFGIA.GetAIClassList(LogLevel); - foreach KFPM_Zeds(KFPMC) - { - ++SpawnEntry.Wave; - SpawnEntry.ZedClass = "KFGameContent." $ String(KFPMC); - default.Spawn.AddItem(SpawnEntry); - } -} - -public static function Array Load(E_LogLevel LogLevel) -{ - local Array SpawnList; - local S_SpawnEntryCfg SpawnEntryCfg; - local S_SpawnEntry SpawnEntry; - local int Line; - local bool Errors; - - `Log_TraceStatic(); - - `Log_Info("Load spawn list:"); - foreach default.Spawn(SpawnEntryCfg, Line) - { - Errors = false; - - SpawnEntry.Wave = SpawnEntryCfg.Wave; - if (SpawnEntryCfg.Wave <= 0 || SpawnEntryCfg.Wave > 255) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Wave" @ "(" $ SpawnEntryCfg.ZedClass $ ")" @ "must be greater than 0 and less than 256"); - Errors = true; - } - - SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); - if (SpawnEntry.ZedClass == None) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); - Errors = true; - } - - SpawnEntry.RelativeStartDefault = SpawnEntryCfg.RelativeStart / 100.f; - if (SpawnEntryCfg.RelativeStart < 0 || SpawnEntryCfg.RelativeStart > 100) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "RelativeStart" @ "(" $ SpawnEntryCfg.RelativeStart $ ")" @ "must be greater than or equal 0 and less than or equal 100"); - Errors = true; - } - - SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; - if (SpawnEntryCfg.Delay <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; - if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); - Errors = true; - } - - SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; - if (SpawnEntry.SpawnCountBase <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; - if (SpawnEntry.SingleSpawnLimit < 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); - Errors = true; - } - - if (!Errors) - { - SpawnList.AddItem(SpawnEntry); - `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (w" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass); - } - } - - default.Spawn.Sort(SpawnListSort); - - if (SpawnList.Length == default.Spawn.Length) - { - `Log_Info("Regular spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); - } - else - { - `Log_Info("Regular spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); - } - - return SpawnList; -} - -defaultproperties -{ - -} +class SpawnListRegular extends Object + dependson(ZedSpawner) + config(ZedSpawner); + +struct S_SpawnEntryCfg +{ + var int Wave; + var String ZedClass; + var int RelativeStart; + var int Delay; + var int Probability; + var int SpawnCountBase; + var int SingleSpawnLimit; +}; + +var public config Array Spawn; + +delegate int SpawnListSort(S_SpawnEntryCfg A, S_SpawnEntryCfg B) +{ + return A.Wave > B.Wave ? -1 : 0; +} + +public static function InitConfig(int Version, int LatestVersion, KFGI_Access KFGIA, E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(KFGIA, LogLevel); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault(KFGI_Access KFGIA, E_LogLevel LogLevel) +{ + local S_SpawnEntryCfg SpawnEntry; + local Array > KFPM_Zeds; + local class KFPMC; + + `Log_TraceStatic(); + + default.Spawn.Length = 0; + + SpawnEntry.Wave = 0; + SpawnEntry.SpawnCountBase = 2; + SpawnEntry.SingleSpawnLimit = 1; + SpawnEntry.RelativeStart = 25; + SpawnEntry.Delay = 60; + SpawnEntry.Probability = 100; + + KFPM_Zeds = KFGIA.GetAIClassList(LogLevel); + foreach KFPM_Zeds(KFPMC) + { + ++SpawnEntry.Wave; + SpawnEntry.ZedClass = "KFGameContent." $ String(KFPMC); + default.Spawn.AddItem(SpawnEntry); + } +} + +public static function Array Load(E_LogLevel LogLevel) +{ + local Array SpawnList; + local S_SpawnEntryCfg SpawnEntryCfg; + local S_SpawnEntry SpawnEntry; + local int Line; + local bool Errors; + + `Log_TraceStatic(); + + `Log_Info("Load spawn list:"); + foreach default.Spawn(SpawnEntryCfg, Line) + { + Errors = false; + + SpawnEntry.Wave = SpawnEntryCfg.Wave; + if (SpawnEntryCfg.Wave <= 0 || SpawnEntryCfg.Wave > 255) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Wave" @ "(" $ SpawnEntryCfg.ZedClass $ ")" @ "must be greater than 0 and less than 256"); + Errors = true; + } + + SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); + if (SpawnEntry.ZedClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); + Errors = true; + } + + SpawnEntry.RelativeStartDefault = SpawnEntryCfg.RelativeStart / 100.f; + if (SpawnEntryCfg.RelativeStart < 0 || SpawnEntryCfg.RelativeStart > 100) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "RelativeStart" @ "(" $ SpawnEntryCfg.RelativeStart $ ")" @ "must be greater than or equal 0 and less than or equal 100"); + Errors = true; + } + + SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; + if (SpawnEntryCfg.Delay <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; + if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); + Errors = true; + } + + SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; + if (SpawnEntry.SpawnCountBase <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; + if (SpawnEntry.SingleSpawnLimit < 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); + Errors = true; + } + + if (!Errors) + { + SpawnList.AddItem(SpawnEntry); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (w" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass); + } + } + + default.Spawn.Sort(SpawnListSort); + + if (SpawnList.Length == default.Spawn.Length) + { + `Log_Info("Regular spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); + } + else + { + `Log_Info("Regular spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); + } + + return SpawnList; +} + +defaultproperties +{ + +} diff --git a/ZedSpawner/Classes/SpawnListSpecialWaves.uc b/ZedSpawner/Classes/SpawnListSpecialWaves.uc index 53baa4e..c13307d 100644 --- a/ZedSpawner/Classes/SpawnListSpecialWaves.uc +++ b/ZedSpawner/Classes/SpawnListSpecialWaves.uc @@ -1,151 +1,151 @@ -class SpawnListSpecialWaves extends Object - dependson(ZedSpawner) - config(ZedSpawner); - -struct S_SpawnEntryCfg -{ - var EAIType Wave; - var String ZedClass; - var int RelativeStart; - var int Delay; - var int Probability; - var int SpawnCountBase; - var int SingleSpawnLimit; -}; - -var public config bool bStopRegularSpawn; -var private config Array Spawn; - -public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) -{ - `Log_TraceStatic(); - - switch (Version) - { - case `NO_CONFIG: - ApplyDefault(LogLevel); - - default: break; - } - - if (LatestVersion != Version) - { - StaticSaveConfig(); - } -} - -private static function ApplyDefault(E_LogLevel LogLevel) -{ - local S_SpawnEntryCfg SpawnEntry; - local EAIType AIType; - - `Log_TraceStatic(); - - default.bStopRegularSpawn = true; - default.Spawn.Length = 0; - SpawnEntry.ZedClass = "SomePackage.SomeClass"; - SpawnEntry.SpawnCountBase = 2; - SpawnEntry.SingleSpawnLimit = 1; - SpawnEntry.RelativeStart = 0; - SpawnEntry.Delay = 60; - SpawnEntry.Probability = 100; - foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType) - { - SpawnEntry.Wave = AIType; - default.Spawn.AddItem(SpawnEntry); - } -} - -public static function Array Load(KFGameInfo_Endless KFGIE, E_LogLevel LogLevel) -{ - local Array SpawnList; - local S_SpawnEntryCfg SpawnEntryCfg; - local S_SpawnEntry SpawnEntry; - local int Line; - local bool Errors; - - `Log_TraceStatic(); - - if (KFGIE == None) - { - `Log_Info("Not Endless mode, skip loading special waves"); - return SpawnList; - } - - `Log_Info("Load special waves spawn list:"); - foreach default.Spawn(SpawnEntryCfg, Line) - { - Errors = false; - - SpawnEntry.Wave = SpawnEntryCfg.Wave; - if (KFGIE.SpecialWaveTypes.Find(EAIType(SpawnEntryCfg.Wave)) == INDEX_NONE) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Unknown special wave:" @ SpawnEntryCfg.Wave); - Errors = true; - } - - SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); - if (SpawnEntry.ZedClass == None) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); - Errors = true; - } - - SpawnEntry.RelativeStartDefault = SpawnEntryCfg.RelativeStart / 100.f; - if (SpawnEntryCfg.RelativeStart < 0 || SpawnEntryCfg.RelativeStart > 100) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "RelativeStart" @ "(" $ SpawnEntryCfg.RelativeStart $ ")" @ "must be greater than or equal 0 and less than or equal 100"); - Errors = true; - } - - SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; - if (SpawnEntryCfg.Delay <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; - if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); - Errors = true; - } - - SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; - if (SpawnEntry.SpawnCountBase <= 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); - Errors = true; - } - - SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; - if (SpawnEntry.SingleSpawnLimit < 0) - { - `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); - Errors = true; - } - - if (!Errors) - { - SpawnList.AddItem(SpawnEntry); - `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass); - } - } - - if (SpawnList.Length == default.Spawn.Length) - { - `Log_Info("Special spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); - } - else - { - `Log_Info("Special spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); - } - - return SpawnList; -} - -defaultproperties -{ - -} +class SpawnListSpecialWaves extends Object + dependson(ZedSpawner) + config(ZedSpawner); + +struct S_SpawnEntryCfg +{ + var EAIType Wave; + var String ZedClass; + var int RelativeStart; + var int Delay; + var int Probability; + var int SpawnCountBase; + var int SingleSpawnLimit; +}; + +var public config bool bStopRegularSpawn; +var private config Array Spawn; + +public static function InitConfig(int Version, int LatestVersion, E_LogLevel LogLevel) +{ + `Log_TraceStatic(); + + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(LogLevel); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault(E_LogLevel LogLevel) +{ + local S_SpawnEntryCfg SpawnEntry; + local EAIType AIType; + + `Log_TraceStatic(); + + default.bStopRegularSpawn = true; + default.Spawn.Length = 0; + SpawnEntry.ZedClass = "SomePackage.SomeClass"; + SpawnEntry.SpawnCountBase = 2; + SpawnEntry.SingleSpawnLimit = 1; + SpawnEntry.RelativeStart = 0; + SpawnEntry.Delay = 60; + SpawnEntry.Probability = 100; + foreach class'KFGameInfo_Endless'.default.SpecialWaveTypes(AIType) + { + SpawnEntry.Wave = AIType; + default.Spawn.AddItem(SpawnEntry); + } +} + +public static function Array Load(KFGameInfo_Endless KFGIE, E_LogLevel LogLevel) +{ + local Array SpawnList; + local S_SpawnEntryCfg SpawnEntryCfg; + local S_SpawnEntry SpawnEntry; + local int Line; + local bool Errors; + + `Log_TraceStatic(); + + if (KFGIE == None) + { + `Log_Info("Not Endless mode, skip loading special waves"); + return SpawnList; + } + + `Log_Info("Load special waves spawn list:"); + foreach default.Spawn(SpawnEntryCfg, Line) + { + Errors = false; + + SpawnEntry.Wave = SpawnEntryCfg.Wave; + if (KFGIE.SpecialWaveTypes.Find(EAIType(SpawnEntryCfg.Wave)) == INDEX_NONE) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Unknown special wave:" @ SpawnEntryCfg.Wave); + Errors = true; + } + + SpawnEntry.ZedClass = class(DynamicLoadObject(SpawnEntryCfg.ZedClass, class'Class')); + if (SpawnEntry.ZedClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load zed class:" @ SpawnEntryCfg.ZedClass); + Errors = true; + } + + SpawnEntry.RelativeStartDefault = SpawnEntryCfg.RelativeStart / 100.f; + if (SpawnEntryCfg.RelativeStart < 0 || SpawnEntryCfg.RelativeStart > 100) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "RelativeStart" @ "(" $ SpawnEntryCfg.RelativeStart $ ")" @ "must be greater than or equal 0 and less than or equal 100"); + Errors = true; + } + + SpawnEntry.DelayDefault = SpawnEntryCfg.Delay; + if (SpawnEntryCfg.Delay <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Delay" @ "(" $ SpawnEntryCfg.Delay $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.Probability = SpawnEntryCfg.Probability / 100.f; + if (SpawnEntryCfg.Probability <= 0 || SpawnEntryCfg.Probability > 100) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Probability" @ "(" $ SpawnEntryCfg.Probability $ ")" @ "must be greater than 0 and less than or equal 100"); + Errors = true; + } + + SpawnEntry.SpawnCountBase = SpawnEntryCfg.SpawnCountBase; + if (SpawnEntry.SpawnCountBase <= 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SpawnCountBase" @ "(" $ SpawnEntryCfg.SpawnCountBase $ ")" @ "must be greater than 0"); + Errors = true; + } + + SpawnEntry.SingleSpawnLimitDefault = SpawnEntryCfg.SingleSpawnLimit; + if (SpawnEntry.SingleSpawnLimit < 0) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "SingleSpawnLimit" @ "(" $ SpawnEntryCfg.SingleSpawnLimit $ ")" @ "must be equal or greater than 0"); + Errors = true; + } + + if (!Errors) + { + SpawnList.AddItem(SpawnEntry); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully: (" $ SpawnEntryCfg.Wave $ ")" @ SpawnEntryCfg.ZedClass); + } + } + + if (SpawnList.Length == default.Spawn.Length) + { + `Log_Info("Special spawn list loaded successfully (" $ default.Spawn.Length @ "entries)"); + } + else + { + `Log_Info("Special spawn list: loaded" @ SpawnList.Length @ "of" @ default.Spawn.Length @ "entries"); + } + + return SpawnList; +} + +defaultproperties +{ + +} diff --git a/ZedSpawner/Classes/ZedSpawner.uc b/ZedSpawner/Classes/ZedSpawner.uc index fbe32ed..62c7694 100644 --- a/ZedSpawner/Classes/ZedSpawner.uc +++ b/ZedSpawner/Classes/ZedSpawner.uc @@ -1,845 +1,845 @@ -class ZedSpawner extends Info - config(ZedSpawner); - -const LatestVersion = 5; - -const CfgSpawn = class'Spawn'; -const CfgSpawnAtPlayerStart = class'SpawnAtPlayerStart'; -const CfgSpawnListRW = class'SpawnListRegular'; -const CfgSpawnListBW = class'SpawnListBossWaves'; -const CfgSpawnListSW = class'SpawnListSpecialWaves'; - -struct S_SpawnEntry -{ - var class BossClass; - var class ZedClass; - var byte Wave; - var int SpawnCountBase; - var int SingleSpawnLimitDefault; - var int SingleSpawnLimit; - var float Probability; - var float RelativeStartDefault; - var float RelativeStart; - var int DelayDefault; - var float Delay; - var int PawnsLeft; - var int PawnsTotal; - var bool ForceSpawn; - var String ZedNameFiller; - var int SmoothPawnPool; -}; - -var private config int Version; -var private config E_LogLevel LogLevel; -var private config float Tickrate; -var private config bool bPreloadContentServer; -var private config bool bPreloadContentClient; - -var private float dt; - -var private Array SpawnListRW; -var private Array SpawnListBW; -var private Array SpawnListSW; -var private Array SpawnListCurrent; - -var private bool NoFreeSpawnSlots; -var private bool UseRegularSpawnList; -var private bool UseBossSpawnList; -var private bool UseSpecialSpawnList; -var private bool GlobalSpawnAtPlayerStart; - -var private KFGameInfo_Survival KFGIS; -var private KFGameInfo_Endless KFGIE; -var private KFGI_Access KFGIA; - -var private int CurrentWave; -var private int SpecialWave; -var private int CurrentCycle; -var private int CycleWaveShift; -var private int CycleWaveSize; -var private int WaveTotalAI; - -var private class CurrentBossClass; -var private Array > CustomZeds; -var private Array > SpawnAtPlayerStartZeds; - -var private bool SpawnActive; -var private String SpawnListsComment; - -var private Array RepInfos; - -public simulated function bool SafeDestroy() -{ - return (bPendingDelete || bDeleteMe || Destroy()); -} - -public event PreBeginPlay() -{ - `Log_Trace(); - - if (WorldInfo.NetMode == NM_Client) - { - `Log_Fatal("NetMode == NM_Client, Destroy..."); - SafeDestroy(); - return; - } - - Super.PreBeginPlay(); - - PreInit(); -} - -private function PreInit() -{ - if (Version == `NO_CONFIG) - { - LogLevel = LL_Info; - SaveConfig(); - } - - CfgSpawn.static.InitConfig(Version, LatestVersion, LogLevel); - CfgSpawnAtPlayerStart.static.InitConfig(Version, LatestVersion, LogLevel); - CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel); - CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel); - CfgSpawnListSW.static.InitConfig(Version, LatestVersion, LogLevel); - - switch (Version) - { - case `NO_CONFIG: - `Log_Info("Config created"); - - case 1: - Tickrate = 1.0f; - - case 2: - case 3: - case 4: - bPreloadContentServer = true; - bPreloadContentClient = true; - - case MaxInt: - `Log_Info("Config updated to version"@LatestVersion); - break; - - case LatestVersion: - `Log_Info("Config is up-to-date"); - break; - - default: - `Log_Warn("The config version is higher than the current version (are you using an old mutator?)"); - `Log_Warn("Config version is" @ Version @ "but current version is" @ LatestVersion); - `Log_Warn("The config version will be changed to" @ LatestVersion); - break; - } - - if (LatestVersion != Version) - { - Version = LatestVersion; - SaveConfig(); - } - - if (LogLevel == LL_WrongLevel) - { - LogLevel = LL_Info; - `Log_Warn("Wrong 'LogLevel', return to default value"); - SaveConfig(); - } - `Log_Base("LogLevel:" @ LogLevel); - - if (Tickrate <= 0) - { - `Log_Error("Spawner tickrate must be positive (current value:" @ Tickrate $ ")"); - `Log_Fatal("Wrong settings, Destroy..."); - SafeDestroy(); - return; - } - - dt = 1 / Tickrate; - `Log_Info("Spawner tickrate:" @ Tickrate @ "(update every" @ dt $ "s)"); - - if (!CfgSpawn.static.Load(LogLevel)) - { - `Log_Fatal("Wrong settings, Destroy..."); - SafeDestroy(); - return; - } - - SpawnListRW = CfgSpawnListRW.static.Load(LogLevel); - SpawnListBW = CfgSpawnListBW.static.Load(LogLevel); - SpawnAtPlayerStartZeds = CfgSpawnAtPlayerStart.static.Load(LogLevel); -} - -public event PostBeginPlay() -{ - `Log_Trace(); - - if (bPendingDelete || bDeleteMe) return; - - Super.PostBeginPlay(); - - PostInit(); -} - -private function PostInit() -{ - local S_SpawnEntry SE; - local String CurrentMap; - - `Log_Trace(); - - KFGIS = KFGameInfo_Survival(WorldInfo.Game); - if (KFGIS == None) - { - `Log_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy..."); - SafeDestroy(); - return; - } - - KFGIA = new(KFGIS) class'KFGI_Access'; - if (KFGIA == None) - { - `Log_Fatal("Can't create KFGI_Access object"); - SafeDestroy(); - return; - } - - KFGIE = KFGameInfo_Endless(KFGIS); - - SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel); - - CurrentMap = String(WorldInfo.GetPackageName()); - GlobalSpawnAtPlayerStart = (CfgSpawnAtPlayerStart.default.Map.Find(CurrentMap) != INDEX_NONE); - `Log_Info("GlobalSpawnAtPlayerStart:" @ GlobalSpawnAtPlayerStart $ GlobalSpawnAtPlayerStart ? "(" $ CurrentMap $ ")" : ""); - - CurrentWave = INDEX_NONE; - SpecialWave = INDEX_NONE; - CurrentCycle = 1; - - if (CfgSpawn.default.bCyclicalSpawn) - { - CycleWaveSize = 0; - CycleWaveShift = MaxInt; - foreach SpawnListRW(SE) - { - CycleWaveShift = Min(CycleWaveShift, SE.Wave); - CycleWaveSize = Max(CycleWaveSize, SE.Wave); - } - CycleWaveSize = CycleWaveSize - CycleWaveShift + 1; - } - - if (bPreloadContentServer || bPreloadContentClient) - { - ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds); - ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds); - ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds); - } - - if (bPreloadContentServer) - { - PreloadContent(); - } - - SetTimer(dt, true, nameof(SpawnTimer)); -} - -private function PreloadContent() -{ - local class PawnClass; - - `Log_Trace(); - - foreach CustomZeds(PawnClass) - { - `Log_Info("Preload content:" @ PawnClass); - PawnClass.static.PreloadContent(); - } -} - -private function ExtractCustomZedsFromSpawnList(Array SpawnList, out Array > Out) -{ - local S_SpawnEntry SE; - - `Log_Trace(); - - foreach SpawnList(SE) - { - if (Out.Find(SE.ZedClass) == INDEX_NONE - && KFGIA.IsCustomZed(SE.ZedClass, LogLevel)) - { - Out.AddItem(SE.ZedClass); - } - } -} - -private function SpawnTimer() -{ - local S_SpawnEntry SE; - local int Index; - local float Threshold; - - `Log_Trace(); - - if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum) - { - SetupWave(); - } - - if (!KFGIS.IsWaveActive()) - { - SpawnTimerLogger(true, "wave is not active"); - return; - } - - if (SpawnListCurrent.Length == 0) - { - SpawnTimerLogger(true, "No spawn list for this wave"); - return; - } - - if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit) - { - SpawnTimerLogger(true, "alive spawn limit reached"); - return; - } - - if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn) - { - if (NoFreeSpawnSlots || KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount) - { - NoFreeSpawnSlots = true; - SpawnTimerLogger(true, "no free spawn slots"); - return; - } - } - - SpawnTimerLogger(false, SpawnListsComment); - - Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI)); - foreach SpawnListCurrent(SE, Index) - { - if (NoFreeSpawnSlots) - { - break; - } - - if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold) - { - continue; - } - - if (SE.Delay > 0.0f) - { - SpawnListCurrent[Index].Delay -= dt; - continue; - } - - if (SE.PawnsLeft > 0) - { - SpawnEntry(SpawnListCurrent, Index); - } - } -} - -private function SetupWave() -{ - local Array SpawnListNames; - local int WaveTotalAIDef; - local byte BaseWave; - local String WaveTypeInfo; - local S_SpawnEntry SE; - local EAIType SWType; - - `Log_Trace(); - - if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle) - { - CurrentCycle++; - `Log_Info("Spawn cycle started:" @ CurrentCycle); - } - - CurrentWave = KFGIS.WaveNum; - - if (KFGIE != None) - { - if (KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode != INDEX_NONE) - { - WaveTypeInfo = "Weekly:" @ KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode; - } - - SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode; - if (SpecialWave != INDEX_NONE) - { - SWType = EAIType(SpecialWave); - WaveTypeInfo = "Special:" @ SWType; - } - } - - if (KFGIS.MyKFGRI.IsBossWave()) - { - CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex), LogLevel); - if (CurrentBossClass == None) - { - `Log_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex); - } - else - { - WaveTypeInfo = "Boss:" @ CurrentBossClass; - } - } - else - { - WaveTotalAIDef = KFGIS.SpawnManager.WaveTotalAI; - KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier; - WaveTotalAI = KFGIS.SpawnManager.WaveTotalAI; - KFGIS.MyKFGRI.WaveTotalAICount = KFGIS.SpawnManager.WaveTotalAI; - KFGIS.MyKFGRI.AIRemaining = KFGIS.SpawnManager.WaveTotalAI; - - if (WaveTotalAIDef != KFGIS.SpawnManager.WaveTotalAI) - { - `Log_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")"); - } - - CurrentBossClass = None; - } - - NoFreeSpawnSlots = false; - UseBossSpawnList = KFGIS.MyKFGRI.IsBossWave(); - UseSpecialSpawnList = (SpecialWave != INDEX_NONE); - UseRegularSpawnList = ((!UseSpecialSpawnList && !UseBossSpawnList) - || (UseSpecialSpawnList && !CfgSpawnListSW.default.bStopRegularSpawn) - || (UseBossSpawnList && !CfgSpawnListBW.default.bStopRegularSpawn)); - - SpawnListCurrent.Length = 0; - if (UseRegularSpawnList) - { - SpawnListNames.AddItem("regular"); - BaseWave = KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1); - foreach SpawnListRW(SE) - if (SE.Wave == BaseWave) - SpawnListCurrent.AddItem(SE); - else if (SE.Wave > BaseWave) - break; - } - - if (UseSpecialSpawnList) - { - SpawnListNames.AddItem("special"); - foreach SpawnListSW(SE) - if (SE.Wave == SpecialWave) - SpawnListCurrent.AddItem(SE); - } - - if (UseBossSpawnList) - { - SpawnListNames.AddItem("boss"); - foreach SpawnListBW(SE) - if (SE.BossClass == CurrentBossClass) - SpawnListCurrent.AddItem(SE); - } - - JoinArray(SpawnListNames, SpawnListsComment, ", "); - AdjustSpawnList(SpawnListCurrent); - - if (WaveTypeInfo != "") - { - WaveTypeInfo = "(" $ WaveTypeInfo $ ")"; - } - - `Log_Info("Wave" @ CurrentWave @ WaveTypeInfo); -} - -private function AdjustSpawnList(out Array List) -{ - local S_SpawnEntry SE; - local int Index; - local float Cycle, Players; - local float B, TM, TCM, TPM; - local float L, LM, LCM, LPM; - local float PawnTotalF, PawnLimitF; - local int ZedNameMaxLength; - - `Log_Trace(); - - Cycle = float(CurrentCycle); - Players = float(PlayerCount()); - - TM = CfgSpawn.default.ZedTotalMultiplier; - TCM = CfgSpawn.default.SpawnTotalCycleMultiplier; - TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier; - - LM = CfgSpawn.default.SingleSpawnLimitMultiplier; - LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier; - LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier; - - ZedNameMaxLength = 0; - foreach List(SE, Index) - { - ZedNameMaxLength = Max(ZedNameMaxLength, Len(String(SE.ZedClass))); - if (KFGIS.MyKFGRI.IsBossWave()) - { - List[Index].RelativeStart = 0.f; - List[Index].Delay = float(SE.DelayDefault); - } - else - { - List[Index].RelativeStart = SE.RelativeStartDefault; - if (List[Index].RelativeStart == 0.f) - List[Index].Delay = float(SE.DelayDefault); - else - List[Index].Delay = 0.0f; - } - - B = float(SE.SpawnCountBase); - L = float(SE.SingleSpawnLimitDefault); - - PawnTotalF = B * (TM + TCM * (Cycle - 1.0f) + TPM * (Players - 1.0f)); - PawnLimitF = L * (LM + LCM * (Cycle - 1.0f) + LPM * (Players - 1.0f)); - - List[Index].ForceSpawn = false; - List[Index].PawnsTotal = Max(Round(PawnTotalF), 1); - List[Index].SingleSpawnLimit = Max(Round(PawnLimitF), 1); - List[Index].PawnsLeft = List[Index].PawnsTotal; - } - - foreach List(SE, Index) - { - List[Index].ZedNameFiller = ""; - while (Len(String(SE.ZedClass)) + Len(List[Index].ZedNameFiller) < ZedNameMaxLength) - List[Index].ZedNameFiller @= ""; - } -} - -private function SpawnTimerLogger(bool Stop, optional String Comment) -{ - local String Message; - - `Log_Trace(); - - if (Stop) - Message = "Stop spawn"; - else - Message = "Start spawn"; - - if (Comment != "") - Message @= "(" $ Comment $ ")"; - - if (SpawnActive == Stop) - { - `Log_Info(Message); - SpawnActive = !Stop; - } -} - -private function SpawnEntry(out Array SpawnList, int Index) -{ - local S_SpawnEntry SE; - local int FreeSpawnSlots, PawnCount, Spawned; - local String Action, Comment, NextSpawn; - local bool SpawnAtPlayerStart; - - `Log_Trace(); - - SE = SpawnList[Index]; - - SpawnList[Index].Delay = float(SE.DelayDefault); - - if (FRand() <= SE.Probability || SE.ForceSpawn) - { - if (SE.SingleSpawnLimit == 0 || SE.PawnsLeft < SE.SingleSpawnLimit) - { - PawnCount = SE.PawnsLeft; - } - else - { - PawnCount = SE.SingleSpawnLimit; - } - - if (CfgSpawn.default.bSmoothSpawn) - { - if (SE.SmoothPawnPool <= 0) - { - SpawnList[Index].SmoothPawnPool = PawnCount; - } - PawnCount = 1; - } - - if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) - { - FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount; - if (FreeSpawnSlots == 0) - { - NoFreeSpawnSlots = true; - SpawnList[Index].PawnsLeft = 0; - return; - } - else if (PawnCount > FreeSpawnSlots) - { - PawnCount = FreeSpawnSlots; - } - } - - SpawnAtPlayerStart = (GlobalSpawnAtPlayerStart || (SpawnAtPlayerStartZeds.Find(SE.ZedClass) != INDEX_NONE)); - - Spawned = SpawnZed(SE.ZedClass, PawnCount, SpawnAtPlayerStart); - if (Spawned == INDEX_NONE) - { - SpawnList[Index].Delay = 5.0f; - SpawnList[Index].ForceSpawn = true; - Action = "Skip spawn"; - Comment = "no free spawn volume, try to spawn it again in" @ Round(SpawnList[Index].Delay) @ "seconds..."; - SpawnLog(SE, Action, Comment); - return; - } - else if (Spawned == 0) - { - Action = "Spawn failed"; - } - else - { - Action = "Spawned"; - Comment = "x" $ Spawned; - if (CfgSpawn.default.bSmoothSpawn) - { - SpawnList[Index].SmoothPawnPool -= Spawned; - if (SpawnList[Index].SmoothPawnPool > 0) - { - SpawnList[Index].Delay = 1.0f; - SpawnList[Index].ForceSpawn = true; - } - else - { - SpawnList[Index].Delay = float(SE.DelayDefault); - SpawnList[Index].ForceSpawn = false; - } - } - else - { - SpawnList[Index].ForceSpawn = false; - } - } - } - else - { - Action = "Skip spawn"; - Comment = "due to" @ Round(SE.Probability * 100) $ "%" @ "probability"; - Spawned = SE.SingleSpawnLimit; - } - - SpawnList[Index].PawnsLeft -= Spawned; - if (SpawnList[Index].PawnsLeft > 0) - { - if (CfgSpawn.default.bSmoothSpawn && SpawnList[Index].SmoothPawnPool > 0) - { - NextSpawn = "next after" @ Round(SpawnList[Index].Delay) $ "sec," @ "pawns left:" @ SpawnList[Index].SmoothPawnPool @ "(" $ SpawnList[Index].PawnsLeft $ ")"; - } - else - { - NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].PawnsLeft; - } - } - SpawnLog(SE, Action, Comment, NextSpawn); -} - -private function SpawnLog(S_SpawnEntry SE, String Action, optional String Comment, optional String NextSpawn) -{ - if (Comment != "") Comment = ":" @ Comment; - if (NextSpawn != "") NextSpawn = "(" $ NextSpawn $ ")"; - - `Log_Info(String(SE.ZedClass) $ SE.ZedNameFiller @ ">" @ Action $ Comment @ NextSpawn); -} - -private function int PlayerCount() -{ - local PlayerController PC; - local int HumanPlayers; - local KFOnlineGameSettings KFGameSettings; - - `Log_Trace(); - - if (KFGIS.PlayfabInter != None && KFGIS.PlayfabInter.GetGameSettings() != None) - { - KFGameSettings = KFOnlineGameSettings(KFGIS.PlayfabInter.GetGameSettings()); - HumanPlayers = KFGameSettings.NumPublicConnections - KFGameSettings.NumOpenPublicConnections; - } - else - { - HumanPlayers = 0; - foreach WorldInfo.AllControllers(class'PlayerController', PC) - if (PC.bIsPlayer - && PC.PlayerReplicationInfo != None - && !PC.PlayerReplicationInfo.bOnlySpectator - && !PC.PlayerReplicationInfo.bBot) - HumanPlayers++; - } - - return HumanPlayers; -} - -private function Vector PlayerStartLocation() -{ - local PlayerController PC; - - `Log_Trace(); - - foreach WorldInfo.AllControllers(class'PlayerController', PC) - return KFGIS.FindPlayerStart(PC, 0).Location; - - return KFGIS.FindPlayerStart(None, 0).Location; -} - -private function int SpawnZed(class ZedClass, int PawnCount, optional bool SpawnAtPlayerStart = false) -{ - local Array > CustomSquad; - local ESquadType PrevDesiredSquadType; - local Vector SpawnLocation, PlayerStart; - local KFSpawnVolume SpawnVolume; - local KFPawn_Monster KFPM; - local Controller C; - local int Failed, Spawned; - local int Index; - - `Log_Trace(); - - PlayerStart = PlayerStartLocation(); - if (SpawnAtPlayerStart) - { - SpawnLocation = PlayerStart; - SpawnLocation.Y += 64; - SpawnLocation.Z += 64; - } - else - { - for (Index = 0; Index < PawnCount; Index++) - { - CustomSquad.AddItem(ZedClass); - } - - PrevDesiredSquadType = KFGIS.SpawnManager.DesiredSquadType; - KFGIS.SpawnManager.SetDesiredSquadTypeForZedList(CustomSquad); - SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad); - KFGIS.SpawnManager.DesiredSquadType = PrevDesiredSquadType; - - if (SpawnVolume == None) - { - return INDEX_NONE; - } - - SpawnVolume.VolumeChosenCount++; - - SpawnLocation = SpawnVolume.Location; - if (SpawnLocation == PlayerStart) - { - return INDEX_NONE; - } - - SpawnLocation.Z += 10; - } - - Spawned = 0; Failed = 0; - while (Failed + Spawned < PawnCount) - { - KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true); - if (KFPM == None) - { - `Log_Error("Can't spawn" @ ZedClass); - Failed++; - continue; - } - - C = KFPM.Spawn(KFPM.ControllerClass); - if (C == None) - { - `Log_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this" @ KFPM $ "..."); - KFPM.Destroy(); - Failed++; - continue; - } - C.Possess(KFPM, false); - Spawned++; - } - - if (Spawned > 0) - { - KFGIS.SpawnManager.LastAISpawnVolume = SpawnVolume; - } - - if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) - { - KFGIS.NumAIFinishedSpawning += Spawned; - KFGIS.NumAISpawnsQueued += Spawned; - } - - KFGIS.UpdateAIRemaining(); - - return Spawned; -} - -public function NotifyLogin(Controller C) -{ - `Log_Trace(); - - if (!bPreloadContentClient) return; - - if (!CreateRepInfo(C)) - { - `Log_Error("Can't create RepInfo for:" @ C); - } -} - -public function NotifyLogout(Controller C) -{ - `Log_Trace(); - - if (!bPreloadContentClient) return; - - DestroyRepInfo(C); -} - -public function bool CreateRepInfo(Controller C) -{ - local ZedSpawnerRepInfo RepInfo; - - `Log_Trace(); - - if (C == None) return false; - - RepInfo = Spawn(class'ZedSpawnerRepInfo', C); - - if (RepInfo == None) return false; - - RepInfo.LogLevel = LogLevel; - RepInfo.CustomZeds = CustomZeds; - RepInfo.ZS = Self; - - RepInfos.AddItem(RepInfo); - - RepInfo.ServerSync(); - - return true; -} - -public function bool DestroyRepInfo(Controller C) -{ - local ZedSpawnerRepInfo RepInfo; - - `Log_Trace(); - - if (C == None) return false; - - foreach RepInfos(RepInfo) - { - if (RepInfo.Owner == C) - { - RepInfos.RemoveItem(RepInfo); - RepInfo.SafeDestroy(); - return true; - } - } - - return false; -} - -defaultproperties -{ - +class ZedSpawner extends Info + config(ZedSpawner); + +const LatestVersion = 5; + +const CfgSpawn = class'Spawn'; +const CfgSpawnAtPlayerStart = class'SpawnAtPlayerStart'; +const CfgSpawnListRW = class'SpawnListRegular'; +const CfgSpawnListBW = class'SpawnListBossWaves'; +const CfgSpawnListSW = class'SpawnListSpecialWaves'; + +struct S_SpawnEntry +{ + var class BossClass; + var class ZedClass; + var byte Wave; + var int SpawnCountBase; + var int SingleSpawnLimitDefault; + var int SingleSpawnLimit; + var float Probability; + var float RelativeStartDefault; + var float RelativeStart; + var int DelayDefault; + var float Delay; + var int PawnsLeft; + var int PawnsTotal; + var bool ForceSpawn; + var String ZedNameFiller; + var int SmoothPawnPool; +}; + +var private config int Version; +var private config E_LogLevel LogLevel; +var private config float Tickrate; +var private config bool bPreloadContentServer; +var private config bool bPreloadContentClient; + +var private float dt; + +var private Array SpawnListRW; +var private Array SpawnListBW; +var private Array SpawnListSW; +var private Array SpawnListCurrent; + +var private bool NoFreeSpawnSlots; +var private bool UseRegularSpawnList; +var private bool UseBossSpawnList; +var private bool UseSpecialSpawnList; +var private bool GlobalSpawnAtPlayerStart; + +var private KFGameInfo_Survival KFGIS; +var private KFGameInfo_Endless KFGIE; +var private KFGI_Access KFGIA; + +var private int CurrentWave; +var private int SpecialWave; +var private int CurrentCycle; +var private int CycleWaveShift; +var private int CycleWaveSize; +var private int WaveTotalAI; + +var private class CurrentBossClass; +var private Array > CustomZeds; +var private Array > SpawnAtPlayerStartZeds; + +var private bool SpawnActive; +var private String SpawnListsComment; + +var private Array RepInfos; + +public simulated function bool SafeDestroy() +{ + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public event PreBeginPlay() +{ + `Log_Trace(); + + if (WorldInfo.NetMode == NM_Client) + { + `Log_Fatal("NetMode == NM_Client, Destroy..."); + SafeDestroy(); + return; + } + + Super.PreBeginPlay(); + + PreInit(); +} + +private function PreInit() +{ + if (Version == `NO_CONFIG) + { + LogLevel = LL_Info; + SaveConfig(); + } + + CfgSpawn.static.InitConfig(Version, LatestVersion, LogLevel); + CfgSpawnAtPlayerStart.static.InitConfig(Version, LatestVersion, LogLevel); + CfgSpawnListRW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel); + CfgSpawnListBW.static.InitConfig(Version, LatestVersion, KFGIA, LogLevel); + CfgSpawnListSW.static.InitConfig(Version, LatestVersion, LogLevel); + + switch (Version) + { + case `NO_CONFIG: + `Log_Info("Config created"); + + case 1: + Tickrate = 1.0f; + + case 2: + case 3: + case 4: + bPreloadContentServer = true; + bPreloadContentClient = true; + + case MaxInt: + `Log_Info("Config updated to version"@LatestVersion); + break; + + case LatestVersion: + `Log_Info("Config is up-to-date"); + break; + + default: + `Log_Warn("The config version is higher than the current version (are you using an old mutator?)"); + `Log_Warn("Config version is" @ Version @ "but current version is" @ LatestVersion); + `Log_Warn("The config version will be changed to" @ LatestVersion); + break; + } + + if (LatestVersion != Version) + { + Version = LatestVersion; + SaveConfig(); + } + + if (LogLevel == LL_WrongLevel) + { + LogLevel = LL_Info; + `Log_Warn("Wrong 'LogLevel', return to default value"); + SaveConfig(); + } + `Log_Base("LogLevel:" @ LogLevel); + + if (Tickrate <= 0) + { + `Log_Error("Spawner tickrate must be positive (current value:" @ Tickrate $ ")"); + `Log_Fatal("Wrong settings, Destroy..."); + SafeDestroy(); + return; + } + + dt = 1 / Tickrate; + `Log_Info("Spawner tickrate:" @ Tickrate @ "(update every" @ dt $ "s)"); + + if (!CfgSpawn.static.Load(LogLevel)) + { + `Log_Fatal("Wrong settings, Destroy..."); + SafeDestroy(); + return; + } + + SpawnListRW = CfgSpawnListRW.static.Load(LogLevel); + SpawnListBW = CfgSpawnListBW.static.Load(LogLevel); + SpawnAtPlayerStartZeds = CfgSpawnAtPlayerStart.static.Load(LogLevel); +} + +public event PostBeginPlay() +{ + `Log_Trace(); + + if (bPendingDelete || bDeleteMe) return; + + Super.PostBeginPlay(); + + PostInit(); +} + +private function PostInit() +{ + local S_SpawnEntry SE; + local String CurrentMap; + + `Log_Trace(); + + KFGIS = KFGameInfo_Survival(WorldInfo.Game); + if (KFGIS == None) + { + `Log_Fatal("Incompatible gamemode:" @ WorldInfo.Game $ ". Destroy..."); + SafeDestroy(); + return; + } + + KFGIA = new(KFGIS) class'KFGI_Access'; + if (KFGIA == None) + { + `Log_Fatal("Can't create KFGI_Access object"); + SafeDestroy(); + return; + } + + KFGIE = KFGameInfo_Endless(KFGIS); + + SpawnListSW = CfgSpawnListSW.static.Load(KFGIE, LogLevel); + + CurrentMap = String(WorldInfo.GetPackageName()); + GlobalSpawnAtPlayerStart = (CfgSpawnAtPlayerStart.default.Map.Find(CurrentMap) != INDEX_NONE); + `Log_Info("GlobalSpawnAtPlayerStart:" @ GlobalSpawnAtPlayerStart $ GlobalSpawnAtPlayerStart ? "(" $ CurrentMap $ ")" : ""); + + CurrentWave = INDEX_NONE; + SpecialWave = INDEX_NONE; + CurrentCycle = 1; + + if (CfgSpawn.default.bCyclicalSpawn) + { + CycleWaveSize = 0; + CycleWaveShift = MaxInt; + foreach SpawnListRW(SE) + { + CycleWaveShift = Min(CycleWaveShift, SE.Wave); + CycleWaveSize = Max(CycleWaveSize, SE.Wave); + } + CycleWaveSize = CycleWaveSize - CycleWaveShift + 1; + } + + if (bPreloadContentServer || bPreloadContentClient) + { + ExtractCustomZedsFromSpawnList(SpawnListRW, CustomZeds); + ExtractCustomZedsFromSpawnList(SpawnListBW, CustomZeds); + ExtractCustomZedsFromSpawnList(SpawnListSW, CustomZeds); + } + + if (bPreloadContentServer) + { + PreloadContent(); + } + + SetTimer(dt, true, nameof(SpawnTimer)); +} + +private function PreloadContent() +{ + local class PawnClass; + + `Log_Trace(); + + foreach CustomZeds(PawnClass) + { + `Log_Info("Preload content:" @ PawnClass); + PawnClass.static.PreloadContent(); + } +} + +private function ExtractCustomZedsFromSpawnList(Array SpawnList, out Array > Out) +{ + local S_SpawnEntry SE; + + `Log_Trace(); + + foreach SpawnList(SE) + { + if (Out.Find(SE.ZedClass) == INDEX_NONE + && KFGIA.IsCustomZed(SE.ZedClass, LogLevel)) + { + Out.AddItem(SE.ZedClass); + } + } +} + +private function SpawnTimer() +{ + local S_SpawnEntry SE; + local int Index; + local float Threshold; + + `Log_Trace(); + + if (KFGIS.WaveNum != 0 && CurrentWave < KFGIS.WaveNum) + { + SetupWave(); + } + + if (!KFGIS.IsWaveActive()) + { + SpawnTimerLogger(true, "wave is not active"); + return; + } + + if (SpawnListCurrent.Length == 0) + { + SpawnTimerLogger(true, "No spawn list for this wave"); + return; + } + + if (CfgSpawn.default.AliveSpawnLimit > 0 && KFGIS.AIAliveCount >= CfgSpawn.default.AliveSpawnLimit) + { + SpawnTimerLogger(true, "alive spawn limit reached"); + return; + } + + if (!KFGIS.MyKFGRI.IsBossWave() && CfgSpawn.default.bShadowSpawn) + { + if (NoFreeSpawnSlots || KFGIS.MyKFGRI.AIRemaining <= KFGIS.AIAliveCount) + { + NoFreeSpawnSlots = true; + SpawnTimerLogger(true, "no free spawn slots"); + return; + } + } + + SpawnTimerLogger(false, SpawnListsComment); + + Threshold = 1.0f - (float(KFGIS.MyKFGRI.AIRemaining) / float(WaveTotalAI)); + foreach SpawnListCurrent(SE, Index) + { + if (NoFreeSpawnSlots) + { + break; + } + + if (SE.RelativeStart != 0.0f && SE.RelativeStart > Threshold) + { + continue; + } + + if (SE.Delay > 0.0f) + { + SpawnListCurrent[Index].Delay -= dt; + continue; + } + + if (SE.PawnsLeft > 0) + { + SpawnEntry(SpawnListCurrent, Index); + } + } +} + +private function SetupWave() +{ + local Array SpawnListNames; + local int WaveTotalAIDef; + local byte BaseWave; + local String WaveTypeInfo; + local S_SpawnEntry SE; + local EAIType SWType; + + `Log_Trace(); + + if (CfgSpawn.default.bCyclicalSpawn && KFGIS.WaveNum > 1 && KFGIS.WaveNum == CycleWaveShift + CycleWaveSize * CurrentCycle) + { + CurrentCycle++; + `Log_Info("Spawn cycle started:" @ CurrentCycle); + } + + CurrentWave = KFGIS.WaveNum; + + if (KFGIE != None) + { + if (KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode != INDEX_NONE) + { + WaveTypeInfo = "Weekly:" @ KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentWeeklyMode; + } + + SpecialWave = KFGameReplicationInfo_Endless(KFGIE.GameReplicationInfo).CurrentSpecialMode; + if (SpecialWave != INDEX_NONE) + { + SWType = EAIType(SpecialWave); + WaveTypeInfo = "Special:" @ SWType; + } + } + + if (KFGIS.MyKFGRI.IsBossWave()) + { + CurrentBossClass = KFGIA.BossAITypePawn(EBossAIType(KFGIS.MyKFGRI.BossIndex), LogLevel); + if (CurrentBossClass == None) + { + `Log_Error("Can't determine boss class. Boss index:" @ KFGIS.MyKFGRI.BossIndex); + } + else + { + WaveTypeInfo = "Boss:" @ CurrentBossClass; + } + } + else + { + WaveTotalAIDef = KFGIS.SpawnManager.WaveTotalAI; + KFGIS.SpawnManager.WaveTotalAI *= CfgSpawn.default.ZedTotalMultiplier; + WaveTotalAI = KFGIS.SpawnManager.WaveTotalAI; + KFGIS.MyKFGRI.WaveTotalAICount = KFGIS.SpawnManager.WaveTotalAI; + KFGIS.MyKFGRI.AIRemaining = KFGIS.SpawnManager.WaveTotalAI; + + if (WaveTotalAIDef != KFGIS.SpawnManager.WaveTotalAI) + { + `Log_Info("increase WaveTotalAI from" @ WaveTotalAIDef @ "to" @ WaveTotalAI @ "due to ZedTotalMultiplier" @ "(" $ CfgSpawn.default.ZedTotalMultiplier $ ")"); + } + + CurrentBossClass = None; + } + + NoFreeSpawnSlots = false; + UseBossSpawnList = KFGIS.MyKFGRI.IsBossWave(); + UseSpecialSpawnList = (SpecialWave != INDEX_NONE); + UseRegularSpawnList = ((!UseSpecialSpawnList && !UseBossSpawnList) + || (UseSpecialSpawnList && !CfgSpawnListSW.default.bStopRegularSpawn) + || (UseBossSpawnList && !CfgSpawnListBW.default.bStopRegularSpawn)); + + SpawnListCurrent.Length = 0; + if (UseRegularSpawnList) + { + SpawnListNames.AddItem("regular"); + BaseWave = KFGIS.WaveNum - CycleWaveSize * (CurrentCycle - 1); + foreach SpawnListRW(SE) + if (SE.Wave == BaseWave) + SpawnListCurrent.AddItem(SE); + else if (SE.Wave > BaseWave) + break; + } + + if (UseSpecialSpawnList) + { + SpawnListNames.AddItem("special"); + foreach SpawnListSW(SE) + if (SE.Wave == SpecialWave) + SpawnListCurrent.AddItem(SE); + } + + if (UseBossSpawnList) + { + SpawnListNames.AddItem("boss"); + foreach SpawnListBW(SE) + if (SE.BossClass == CurrentBossClass) + SpawnListCurrent.AddItem(SE); + } + + JoinArray(SpawnListNames, SpawnListsComment, ", "); + AdjustSpawnList(SpawnListCurrent); + + if (WaveTypeInfo != "") + { + WaveTypeInfo = "(" $ WaveTypeInfo $ ")"; + } + + `Log_Info("Wave" @ CurrentWave @ WaveTypeInfo); +} + +private function AdjustSpawnList(out Array List) +{ + local S_SpawnEntry SE; + local int Index; + local float Cycle, Players; + local float B, TM, TCM, TPM; + local float L, LM, LCM, LPM; + local float PawnTotalF, PawnLimitF; + local int ZedNameMaxLength; + + `Log_Trace(); + + Cycle = float(CurrentCycle); + Players = float(PlayerCount()); + + TM = CfgSpawn.default.ZedTotalMultiplier; + TCM = CfgSpawn.default.SpawnTotalCycleMultiplier; + TPM = CfgSpawn.default.SpawnTotalPlayerMultiplier; + + LM = CfgSpawn.default.SingleSpawnLimitMultiplier; + LCM = CfgSpawn.default.SingleSpawnLimitCycleMultiplier; + LPM = CfgSpawn.default.SingleSpawnLimitPlayerMultiplier; + + ZedNameMaxLength = 0; + foreach List(SE, Index) + { + ZedNameMaxLength = Max(ZedNameMaxLength, Len(String(SE.ZedClass))); + if (KFGIS.MyKFGRI.IsBossWave()) + { + List[Index].RelativeStart = 0.f; + List[Index].Delay = float(SE.DelayDefault); + } + else + { + List[Index].RelativeStart = SE.RelativeStartDefault; + if (List[Index].RelativeStart == 0.f) + List[Index].Delay = float(SE.DelayDefault); + else + List[Index].Delay = 0.0f; + } + + B = float(SE.SpawnCountBase); + L = float(SE.SingleSpawnLimitDefault); + + PawnTotalF = B * (TM + TCM * (Cycle - 1.0f) + TPM * (Players - 1.0f)); + PawnLimitF = L * (LM + LCM * (Cycle - 1.0f) + LPM * (Players - 1.0f)); + + List[Index].ForceSpawn = false; + List[Index].PawnsTotal = Max(Round(PawnTotalF), 1); + List[Index].SingleSpawnLimit = Max(Round(PawnLimitF), 1); + List[Index].PawnsLeft = List[Index].PawnsTotal; + } + + foreach List(SE, Index) + { + List[Index].ZedNameFiller = ""; + while (Len(String(SE.ZedClass)) + Len(List[Index].ZedNameFiller) < ZedNameMaxLength) + List[Index].ZedNameFiller @= ""; + } +} + +private function SpawnTimerLogger(bool Stop, optional String Comment) +{ + local String Message; + + `Log_Trace(); + + if (Stop) + Message = "Stop spawn"; + else + Message = "Start spawn"; + + if (Comment != "") + Message @= "(" $ Comment $ ")"; + + if (SpawnActive == Stop) + { + `Log_Info(Message); + SpawnActive = !Stop; + } +} + +private function SpawnEntry(out Array SpawnList, int Index) +{ + local S_SpawnEntry SE; + local int FreeSpawnSlots, PawnCount, Spawned; + local String Action, Comment, NextSpawn; + local bool SpawnAtPlayerStart; + + `Log_Trace(); + + SE = SpawnList[Index]; + + SpawnList[Index].Delay = float(SE.DelayDefault); + + if (FRand() <= SE.Probability || SE.ForceSpawn) + { + if (SE.SingleSpawnLimit == 0 || SE.PawnsLeft < SE.SingleSpawnLimit) + { + PawnCount = SE.PawnsLeft; + } + else + { + PawnCount = SE.SingleSpawnLimit; + } + + if (CfgSpawn.default.bSmoothSpawn) + { + if (SE.SmoothPawnPool <= 0) + { + SpawnList[Index].SmoothPawnPool = PawnCount; + } + PawnCount = 1; + } + + if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) + { + FreeSpawnSlots = KFGIS.MyKFGRI.AIRemaining - KFGIS.AIAliveCount; + if (FreeSpawnSlots == 0) + { + NoFreeSpawnSlots = true; + SpawnList[Index].PawnsLeft = 0; + return; + } + else if (PawnCount > FreeSpawnSlots) + { + PawnCount = FreeSpawnSlots; + } + } + + SpawnAtPlayerStart = (GlobalSpawnAtPlayerStart || (SpawnAtPlayerStartZeds.Find(SE.ZedClass) != INDEX_NONE)); + + Spawned = SpawnZed(SE.ZedClass, PawnCount, SpawnAtPlayerStart); + if (Spawned == INDEX_NONE) + { + SpawnList[Index].Delay = 5.0f; + SpawnList[Index].ForceSpawn = true; + Action = "Skip spawn"; + Comment = "no free spawn volume, try to spawn it again in" @ Round(SpawnList[Index].Delay) @ "seconds..."; + SpawnLog(SE, Action, Comment); + return; + } + else if (Spawned == 0) + { + Action = "Spawn failed"; + } + else + { + Action = "Spawned"; + Comment = "x" $ Spawned; + if (CfgSpawn.default.bSmoothSpawn) + { + SpawnList[Index].SmoothPawnPool -= Spawned; + if (SpawnList[Index].SmoothPawnPool > 0) + { + SpawnList[Index].Delay = 1.0f; + SpawnList[Index].ForceSpawn = true; + } + else + { + SpawnList[Index].Delay = float(SE.DelayDefault); + SpawnList[Index].ForceSpawn = false; + } + } + else + { + SpawnList[Index].ForceSpawn = false; + } + } + } + else + { + Action = "Skip spawn"; + Comment = "due to" @ Round(SE.Probability * 100) $ "%" @ "probability"; + Spawned = SE.SingleSpawnLimit; + } + + SpawnList[Index].PawnsLeft -= Spawned; + if (SpawnList[Index].PawnsLeft > 0) + { + if (CfgSpawn.default.bSmoothSpawn && SpawnList[Index].SmoothPawnPool > 0) + { + NextSpawn = "next after" @ Round(SpawnList[Index].Delay) $ "sec," @ "pawns left:" @ SpawnList[Index].SmoothPawnPool @ "(" $ SpawnList[Index].PawnsLeft $ ")"; + } + else + { + NextSpawn = "next after" @ SE.DelayDefault $ "sec," @ "pawns left:" @ SpawnList[Index].PawnsLeft; + } + } + SpawnLog(SE, Action, Comment, NextSpawn); +} + +private function SpawnLog(S_SpawnEntry SE, String Action, optional String Comment, optional String NextSpawn) +{ + if (Comment != "") Comment = ":" @ Comment; + if (NextSpawn != "") NextSpawn = "(" $ NextSpawn $ ")"; + + `Log_Info(String(SE.ZedClass) $ SE.ZedNameFiller @ ">" @ Action $ Comment @ NextSpawn); +} + +private function int PlayerCount() +{ + local PlayerController PC; + local int HumanPlayers; + local KFOnlineGameSettings KFGameSettings; + + `Log_Trace(); + + if (KFGIS.PlayfabInter != None && KFGIS.PlayfabInter.GetGameSettings() != None) + { + KFGameSettings = KFOnlineGameSettings(KFGIS.PlayfabInter.GetGameSettings()); + HumanPlayers = KFGameSettings.NumPublicConnections - KFGameSettings.NumOpenPublicConnections; + } + else + { + HumanPlayers = 0; + foreach WorldInfo.AllControllers(class'PlayerController', PC) + if (PC.bIsPlayer + && PC.PlayerReplicationInfo != None + && !PC.PlayerReplicationInfo.bOnlySpectator + && !PC.PlayerReplicationInfo.bBot) + HumanPlayers++; + } + + return HumanPlayers; +} + +private function Vector PlayerStartLocation() +{ + local PlayerController PC; + + `Log_Trace(); + + foreach WorldInfo.AllControllers(class'PlayerController', PC) + return KFGIS.FindPlayerStart(PC, 0).Location; + + return KFGIS.FindPlayerStart(None, 0).Location; +} + +private function int SpawnZed(class ZedClass, int PawnCount, optional bool SpawnAtPlayerStart = false) +{ + local Array > CustomSquad; + local ESquadType PrevDesiredSquadType; + local Vector SpawnLocation, PlayerStart; + local KFSpawnVolume SpawnVolume; + local KFPawn_Monster KFPM; + local Controller C; + local int Failed, Spawned; + local int Index; + + `Log_Trace(); + + PlayerStart = PlayerStartLocation(); + if (SpawnAtPlayerStart) + { + SpawnLocation = PlayerStart; + SpawnLocation.Y += 64; + SpawnLocation.Z += 64; + } + else + { + for (Index = 0; Index < PawnCount; Index++) + { + CustomSquad.AddItem(ZedClass); + } + + PrevDesiredSquadType = KFGIS.SpawnManager.DesiredSquadType; + KFGIS.SpawnManager.SetDesiredSquadTypeForZedList(CustomSquad); + SpawnVolume = KFGIS.SpawnManager.GetBestSpawnVolume(CustomSquad); + KFGIS.SpawnManager.DesiredSquadType = PrevDesiredSquadType; + + if (SpawnVolume == None) + { + return INDEX_NONE; + } + + SpawnVolume.VolumeChosenCount++; + + SpawnLocation = SpawnVolume.Location; + if (SpawnLocation == PlayerStart) + { + return INDEX_NONE; + } + + SpawnLocation.Z += 10; + } + + Spawned = 0; Failed = 0; + while (Failed + Spawned < PawnCount) + { + KFPM = Spawn(ZedClass,,, SpawnLocation, rot(0,0,1),, true); + if (KFPM == None) + { + `Log_Error("Can't spawn" @ ZedClass); + Failed++; + continue; + } + + C = KFPM.Spawn(KFPM.ControllerClass); + if (C == None) + { + `Log_Error("Can't spawn controller for" @ ZedClass $ ". Destroy this" @ KFPM $ "..."); + KFPM.Destroy(); + Failed++; + continue; + } + C.Possess(KFPM, false); + Spawned++; + } + + if (Spawned > 0) + { + KFGIS.SpawnManager.LastAISpawnVolume = SpawnVolume; + } + + if (CfgSpawn.default.bShadowSpawn && !KFGIS.MyKFGRI.IsBossWave()) + { + KFGIS.NumAIFinishedSpawning += Spawned; + KFGIS.NumAISpawnsQueued += Spawned; + } + + KFGIS.UpdateAIRemaining(); + + return Spawned; +} + +public function NotifyLogin(Controller C) +{ + `Log_Trace(); + + if (!bPreloadContentClient) return; + + if (!CreateRepInfo(C)) + { + `Log_Error("Can't create RepInfo for:" @ C); + } +} + +public function NotifyLogout(Controller C) +{ + `Log_Trace(); + + if (!bPreloadContentClient) return; + + DestroyRepInfo(C); +} + +public function bool CreateRepInfo(Controller C) +{ + local ZedSpawnerRepInfo RepInfo; + + `Log_Trace(); + + if (C == None) return false; + + RepInfo = Spawn(class'ZedSpawnerRepInfo', C); + + if (RepInfo == None) return false; + + RepInfo.LogLevel = LogLevel; + RepInfo.CustomZeds = CustomZeds; + RepInfo.ZS = Self; + + RepInfos.AddItem(RepInfo); + + RepInfo.ServerSync(); + + return true; +} + +public function bool DestroyRepInfo(Controller C) +{ + local ZedSpawnerRepInfo RepInfo; + + `Log_Trace(); + + if (C == None) return false; + + foreach RepInfos(RepInfo) + { + if (RepInfo.Owner == C) + { + RepInfos.RemoveItem(RepInfo); + RepInfo.SafeDestroy(); + return true; + } + } + + return false; +} + +defaultproperties +{ + } \ No newline at end of file diff --git a/ZedSpawner/Classes/ZedSpawner.upkg b/ZedSpawner/Classes/ZedSpawner.upkg index 29cb156..09febdf 100644 --- a/ZedSpawner/Classes/ZedSpawner.upkg +++ b/ZedSpawner/Classes/ZedSpawner.upkg @@ -1,4 +1,4 @@ -[Flags] -AllowDownload=True -ClientOptional=False -ServerSideOnly=False +[Flags] +AllowDownload=True +ClientOptional=False +ServerSideOnly=False diff --git a/ZedSpawner/Classes/ZedSpawnerMut.uc b/ZedSpawner/Classes/ZedSpawnerMut.uc index 6b38955..3089fbd 100644 --- a/ZedSpawner/Classes/ZedSpawnerMut.uc +++ b/ZedSpawner/Classes/ZedSpawnerMut.uc @@ -1,56 +1,56 @@ -class ZedSpawnerMut extends KFMutator - dependson(ZedSpawner); - -var private ZedSpawner ZS; - -public event PreBeginPlay() -{ - Super.PreBeginPlay(); - - if (WorldInfo.NetMode == NM_Client) return; - - foreach WorldInfo.DynamicActors(class'ZedSpawner', ZS) - { - break; - } - - if (ZS == None) - { - ZS = WorldInfo.Spawn(class'ZedSpawner'); - } - - if (ZS == None) - { - `Log_Base("FATAL: Can't Spawn 'ZedSpawner'"); - Destroy(); - } -} - -public function AddMutator(Mutator Mut) -{ - if (Mut == Self) return; - - if (Mut.Class == Class) - Mut.Destroy(); - else - Super.AddMutator(Mut); -} - -public function NotifyLogin(Controller C) -{ - ZS.NotifyLogin(C); - - Super.NotifyLogin(C); -} - -public function NotifyLogout(Controller C) -{ - ZS.NotifyLogout(C); - - Super.NotifyLogout(C); -} - -defaultproperties -{ - +class ZedSpawnerMut extends KFMutator + dependson(ZedSpawner); + +var private ZedSpawner ZS; + +public event PreBeginPlay() +{ + Super.PreBeginPlay(); + + if (WorldInfo.NetMode == NM_Client) return; + + foreach WorldInfo.DynamicActors(class'ZedSpawner', ZS) + { + break; + } + + if (ZS == None) + { + ZS = WorldInfo.Spawn(class'ZedSpawner'); + } + + if (ZS == None) + { + `Log_Base("FATAL: Can't Spawn 'ZedSpawner'"); + Destroy(); + } +} + +public function AddMutator(Mutator Mut) +{ + if (Mut == Self) return; + + if (Mut.Class == Class) + Mut.Destroy(); + else + Super.AddMutator(Mut); +} + +public function NotifyLogin(Controller C) +{ + ZS.NotifyLogin(C); + + Super.NotifyLogin(C); +} + +public function NotifyLogout(Controller C) +{ + ZS.NotifyLogout(C); + + Super.NotifyLogout(C); +} + +defaultproperties +{ + } \ No newline at end of file diff --git a/ZedSpawner/Classes/ZedSpawnerRepInfo.uc b/ZedSpawner/Classes/ZedSpawnerRepInfo.uc index 411dddf..7711218 100644 --- a/ZedSpawner/Classes/ZedSpawnerRepInfo.uc +++ b/ZedSpawner/Classes/ZedSpawnerRepInfo.uc @@ -3,7 +3,7 @@ class ZedSpawnerRepInfo extends ReplicationInfo; var public ZedSpawner ZS; var public E_LogLevel LogLevel; var public Array > CustomZeds; -var private int Recieved; +var private int Recieved; replication { @@ -19,7 +19,7 @@ public simulated function bool SafeDestroy() public reliable client function ClientSync(class CustomZed) { `Log_Trace(); - + `Log_Debug("Received:" @ CustomZed); CustomZeds.AddItem(CustomZed); ServerSync(); @@ -28,24 +28,24 @@ public reliable client function ClientSync(class CustomZed) public reliable client function SyncFinished() { local class CustomZed; - + `Log_Trace(); - + foreach CustomZeds(CustomZed) { `Log_Debug("Preload Content for" @ CustomZed); CustomZed.static.PreloadContent(); } - + SafeDestroy(); } public reliable server function ServerSync() { `Log_Trace(); - + if (bPendingDelete || bDeleteMe) return; - + if (CustomZeds.Length == Recieved || WorldInfo.NetMode == NM_StandAlone) { `Log_Debug("Sync finished"); @@ -67,6 +67,6 @@ defaultproperties bAlwaysRelevant = false; bOnlyRelevantToOwner = true; bSkipActorPropertyReplication = false; - + Recieved = 0 } diff --git a/ZedSpawner/Classes/_Logger.uc b/ZedSpawner/Classes/_Logger.uc index 93fc28a..d9cfb52 100644 --- a/ZedSpawner/Classes/_Logger.uc +++ b/ZedSpawner/Classes/_Logger.uc @@ -1,20 +1,20 @@ -class _Logger extends Object - abstract; - -enum E_LogLevel -{ - LL_WrongLevel, - LL_None, - LL_Fatal, - LL_Error, - LL_Warning, - LL_Info, - LL_Debug, - LL_Trace, - LL_All -}; - -defaultproperties -{ - -} +class _Logger extends Object + abstract; + +enum E_LogLevel +{ + LL_WrongLevel, + LL_None, + LL_Fatal, + LL_Error, + LL_Warning, + LL_Info, + LL_Debug, + LL_Trace, + LL_All +}; + +defaultproperties +{ + +} diff --git a/ZedSpawner/Constants.uci b/ZedSpawner/Constants.uci index 1003f19..432fa68 100644 --- a/ZedSpawner/Constants.uci +++ b/ZedSpawner/Constants.uci @@ -1,2 +1,2 @@ -// Constants -`define NO_CONFIG 0 +// Constants +`define NO_CONFIG 0 diff --git a/ZedSpawner/Logger.uci b/ZedSpawner/Logger.uci index 3756d74..7ffd0ef 100644 --- a/ZedSpawner/Logger.uci +++ b/ZedSpawner/Logger.uci @@ -1,15 +1,15 @@ -// Logger -`define Log_Tag 'ZedSpawner' - -`define LocationStatic "`{ClassName}::" $ GetFuncName() - -`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_Error(msg) `log("ERROR:" @ `msg, (LogLevel >= LL_Error), `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_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_TraceStatic(msg) `log("TRACE:" @ `LocationStatic `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) +// Logger +`define Log_Tag 'ZedSpawner' + +`define LocationStatic "`{ClassName}::" $ GetFuncName() + +`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_Error(msg) `log("ERROR:" @ `msg, (LogLevel >= LL_Error), `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_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_TraceStatic(msg) `log("TRACE:" @ `LocationStatic `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) diff --git a/builder.cfg b/builder.cfg index cec3353..eb196f0 100644 --- a/builder.cfg +++ b/builder.cfg @@ -7,7 +7,7 @@ StripSource="True" # Mutators to be compiled # 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="ZedSpawner" @@ -16,7 +16,7 @@ PackageBuildOrder="ZedSpawner" # Packages you want to brew using @peelz's patched KFEditor. # Useful for cases where regular brew doesn't put *.upk inside the package. # Specify them with a space as a separator, -# The order doesn't matter +# The order doesn't matter PackagePeelzBrew="" @@ -24,7 +24,7 @@ PackagePeelzBrew="" # Mutators that will be uploaded to the workshop # Specify them with a space as a separator, -# The order doesn't matter +# The order doesn't matter PackageUpload="ZedSpawner"