diff --git a/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc b/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc
new file mode 100644
index 0000000..5221a45
--- /dev/null
+++ b/sources/Gameplay/BaseClasses/Frontend/Health/AHealthComponent.uc
@@ -0,0 +1,26 @@
+/**
+ * Subset of functionality for dealing with everything related to pawns'
+ * health.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class AHealthComponent extends AcediaObject
+ abstract;
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc b/sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc
index 711efc9..1c5be1b 100644
--- a/sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc
+++ b/sources/Gameplay/BaseClasses/KillingFloor/Frontend/KFFrontend.uc
@@ -23,18 +23,26 @@ class KFFrontend extends BaseFrontend
var private config class tradingClass;
var public ATradingComponent trading;
+var private config class healthClass;
+var public AHealthComponent health;
+
protected function Constructor()
{
super.Constructor();
if (tradingClass != none) {
trading = ATradingComponent(_.memory.Allocate(tradingClass));
}
+ if (healthClass != none) {
+ health = AHealthComponent(_.memory.Allocate(healthClass));
+ }
}
protected function Finalizer()
{
_.memory.Free(trading);
+ _.memory.Free(health);
trading = none;
+ health = none;
}
/**
@@ -53,5 +61,6 @@ public function EItemTemplateInfo GetItemTemplateInfo(BaseText templateName)
defaultproperties
{
- tradingClass = none
+ tradingClass = none
+ healthClass = none
}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_DamTypeVomit.uc b/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_DamTypeVomit.uc
new file mode 100644
index 0000000..a6c42e6
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_DamTypeVomit.uc
@@ -0,0 +1,26 @@
+/**
+ * Dummy replacer for bloat's `DamTypeVomit` damage type to avoid TWI's moronic
+ * "This stuff cuts thru all the B.S" bullshit.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class Dummy_DamTypeVomit extends DamTypeVomit
+ abstract;
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_SirenScreamDamage.uc b/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_SirenScreamDamage.uc
new file mode 100644
index 0000000..dd14265
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/Health/DummyDamageTypes/Dummy_SirenScreamDamage.uc
@@ -0,0 +1,26 @@
+/**
+ * Dummy replacer for bloat's `SirenScreamDamage` damage type to avoid TWI's
+ * moronic "This stuff cuts thru all the B.S" bullshit.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class Dummy_SirenScreamDamage extends SirenScreamDamage
+ abstract;
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc
new file mode 100644
index 0000000..4b3ffa6
--- /dev/null
+++ b/sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc
@@ -0,0 +1,187 @@
+/**
+ * `AHealthComponent`'s implementation for `KF1_Frontend`.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class KF1_HealthComponent extends AHealthComponent
+ dependson(ConnectionService)
+ config(AcediaSystem);
+
+var private const config bool replaceBloatAndSirenDamageTypes;
+
+var private LoggerAPI.Definition infoReplacingDamageTypes, errNoServerLevelCore;
+var private LoggerAPI.Definition infoRestoringReplacingDamageTypes;
+
+public function PresudoConstructor()
+{
+ local LevelCore core;
+ _.unreal.gameRules.OnNetDamage(self).connect = OnNetDamageHandler;
+ if (!replaceBloatAndSirenDamageTypes) {
+ return;
+ }
+ _.logger.Auto(infoReplacingDamageTypes);
+ core = class'ServerLevelCore'.static.GetInstance();
+ if (core != none)
+ {
+ ReplaceDamageTypes(core);
+ _.unreal.gameRules.OnScoreKill(self).connect = UpdateBileAchievement;
+ core.OnShutdown(self).connect = RestoreDamageTypes;
+ }
+ else {
+ _.logger.Auto(errNoServerLevelCore);
+ }
+}
+
+protected function Finalizer()
+{
+ _.unreal.gameRules.OnNetDamage(self).Disconnect();
+ if (replaceBloatAndSirenDamageTypes) {
+ RestoreDamageTypes();
+ }
+}
+
+private final function ReplaceDamageTypes(LevelCore core)
+{
+ local KFBloatVomit nextVomit;
+ local ZombieSirenBase nextSiren;
+
+ class'KFBloatVomit'.default.myDamageType = class'Dummy_DamTypeVomit';
+ class'ZombieSirenBase'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ class'ZombieSirenBase'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ class'ZombieSiren_STANDARD'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ class'ZombieSiren_HALLOWEEN'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ class'ZombieSiren_XMas'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ class'ZombieSiren_CIRCUS'.default.screamDamageType =
+ class'Dummy_SirenScreamDamage';
+ foreach core.AllActors(class'KFBloatVomit', nextVomit) {
+ nextVomit.myDamageType = class'Dummy_DamTypeVomit';
+ }
+ foreach core.AllActors(class'ZombieSirenBase', nextSiren) {
+ nextSiren.screamDamageType = class'Dummy_SirenScreamDamage';
+ }
+}
+
+private final function RestoreDamageTypes()
+{
+ _.logger.Auto(infoRestoringReplacingDamageTypes);
+ class'KFBloatVomit'.default.myDamageType = class'DamTypeVomit';
+ class'ZombieSirenBase'.default.screamDamageType = class'SirenScreamDamage';
+ class'ZombieSirenBase'.default.screamDamageType = class'SirenScreamDamage';
+ class'ZombieSiren_STANDARD'.default.screamDamageType =
+ class'SirenScreamDamage';
+ class'ZombieSiren_HALLOWEEN'.default.screamDamageType =
+ class'SirenScreamDamage';
+ class'ZombieSiren_XMas'.default.screamDamageType = class'SirenScreamDamage';
+ class'ZombieSiren_CIRCUS'.default.screamDamageType =
+ class'SirenScreamDamage';
+}
+
+private function int OnNetDamageHandler(
+ int originalDamage,
+ int damage,
+ Pawn injured,
+ Pawn instigatedBy,
+ Vector hitLocation,
+ out Vector momentum,
+ class damageType)
+{
+ if (damageType == class'Dummy_DamTypeVomit') {
+ damage = ReapplyNativeBileAdjustments(originalDamage, damage, injured);
+ }
+ if (damageType == class'Dummy_SirenScreamDamage') {
+ damage = ReapplyNativeScreamAdjustments(damage, injured, hitLocation);
+ }
+ return damage;
+}
+
+private function int ReapplyNativeBileAdjustments(
+ int originalDamage,
+ int damage,
+ Pawn injured)
+{
+ local KFPlayerReplicationInfo kfPRI;
+
+ if (ZombieBloatBase(injured) != none) {
+ return 0;
+ }
+ if (ZombieFleshpoundBase(injured) != none) {
+ return 0;
+ }
+ if (injured != none && injured.controller != none)
+ {
+ kfPRI = KFPlayerReplicationInfo(
+ injured.controller.playerReplicationInfo);
+ }
+ return damage;
+}
+
+private function int ReapplyNativeScreamAdjustments(
+ int damage,
+ Pawn injured,
+ Vector hitLocation)
+{
+ local KFPawn injuredPawn;
+
+ injuredPawn = KFPawn(injured);
+ if (injuredPawn != none)
+ {
+ // TODO: `PlayHit()` is not informed about siren's damage!!!
+ // TODO: Neither are bloody projectiles!
+ injuredPawn.lastHitDamType = class'SirenScreamDamage';
+ }
+ return damage;
+}
+
+private function UpdateBileAchievement(Controller killer, Controller killed)
+{
+ local int i;
+ local KFMonster killedMonster;
+ local ConnectionService service;
+ local KFSteamStatsAndAchievements kfSteamStats;
+ local array activeConnections;
+
+ killedMonster = KFMonster(killed.pawn);
+ if (killedMonster == none) return;
+ if (killedMonster.lastDamagedByType != class'Dummy_DamTypeVomit') return;
+ service = ConnectionService(class'ConnectionService'.static.Require());
+ if (service == none) return;
+
+ activeConnections = service.GetActiveConnections();
+ for (i = 0; i < activeConnections.length; i += 1)
+ {
+ kfSteamStats = KFSteamStatsAndAchievements(activeConnections[i]
+ .controllerReference
+ .steamStatsAndAchievements);
+ if (kfSteamStats != none) {
+ kfSteamStats.KilledEnemyWithBloatAcid();
+ }
+ }
+}
+
+
+defaultproperties
+{
+ replaceBloatAndSirenDamageTypes = true
+ infoReplacingDamageTypes = (l=LOG_Info,m="Replacing bloat's and siren's damage types to dummy ones.")
+ infoRestoringReplacingDamageTypes = (l=LOG_Info,m="Restoring bloat and siren's damage types to their original values.")
+ errNoServerLevelCore = (l=LOG_Error,m="Server level core is missing. Either this isn't a server or Acedia was wrongly initialized. Bloat and siren damage type will not be replaced.")
+}
\ No newline at end of file
diff --git a/sources/Gameplay/KF1Frontend/KF1_Frontend.uc b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc
index bf395cd..99c8022 100644
--- a/sources/Gameplay/KF1Frontend/KF1_Frontend.uc
+++ b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc
@@ -2,7 +2,7 @@
* Frontend implementation for classic `KFGameType` that changes as little as
* possible and only on request from another mod, otherwise not altering
* gameplay at all.
- * Copyright 2021 Anton Tarasenko
+ * Copyright 2021-2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -35,4 +35,5 @@ defaultproperties
{
templatesClass = class'KF1_TemplatesComponent'
tradingClass = class'KF1_TradingComponent'
+ healthClass = class'KF1_HealthComponent'
}
\ No newline at end of file