Browse Source

Merge pull request 'Add `UnflectApi`' (#12) from feature_uflect into develop

Reviewed-on: #12
pull/13/head
dkanus 2 years ago
parent
commit
0354396d31
  1. 2
      config/AcediaSystem.ini
  2. 277
      sources/BaseAPI/API/SideEffects/SideEffect.uc
  3. 194
      sources/BaseAPI/API/SideEffects/SideEffectAPI.uc
  4. 76
      sources/BaseAPI/API/Unflect/FunctionReplacement.uc
  5. 30
      sources/BaseAPI/API/Unflect/Tests/MockInitialClass.uc
  6. 33
      sources/BaseAPI/API/Unflect/Tests/MockReplacerClass.uc
  7. 87
      sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
  8. 29
      sources/BaseAPI/API/Unflect/TypeCast.uc
  9. 49
      sources/BaseAPI/API/Unflect/UClass.uc
  10. 30
      sources/BaseAPI/API/Unflect/UClassCast.uc
  11. 28
      sources/BaseAPI/API/Unflect/UField.uc
  12. 30
      sources/BaseAPI/API/Unflect/UFieldCast.uc
  13. 35
      sources/BaseAPI/API/Unflect/UFunction.uc
  14. 30
      sources/BaseAPI/API/Unflect/UFunctionCast.uc
  15. 41
      sources/BaseAPI/API/Unflect/UProperty.uc
  16. 30
      sources/BaseAPI/API/Unflect/UPropertyCast.uc
  17. 30
      sources/BaseAPI/API/Unflect/UState.uc
  18. 30
      sources/BaseAPI/API/Unflect/UStateCast.uc
  19. 48
      sources/BaseAPI/API/Unflect/UStruct.uc
  20. 30
      sources/BaseAPI/API/Unflect/UStructCast.uc
  21. 28
      sources/BaseAPI/API/Unflect/UTextBuffer.uc
  22. 32
      sources/BaseAPI/API/Unflect/Unflect.uc
  23. 409
      sources/BaseAPI/API/Unflect/UnflectApi.uc
  24. 4
      sources/BaseAPI/Global.uc
  25. 1
      sources/ClientAPI/ClientAcediaAdapter.uc
  26. 2
      sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc
  27. 115
      sources/KFImplementation/Core/KF1_SideEffectAPI.uc
  28. 2
      sources/KFImplementation/Server/ServerSideEffects.uc
  29. 2
      sources/KFImplementation/Server/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc
  30. 65
      sources/KFImplementation/Server/Unreal/BroadcastsAPI/BroadcastSideEffect.uc
  31. 47
      sources/KFImplementation/Server/Unreal/BroadcastsAPI/KF1_BroadcastAPI.uc
  32. 43
      sources/KFImplementation/Server/Unreal/GameRulesAPI/GameRulesSideEffect.uc
  33. 30
      sources/KFImplementation/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc
  34. 164
      sources/LevelAPI/API/SideEffects/SideEffect.uc
  35. 76
      sources/LevelAPI/API/SideEffects/SideEffectAPI.uc
  36. 1
      sources/LevelAPI/AcediaAdapter.uc
  37. 2
      sources/LevelAPI/CoreGlobal.uc
  38. 1
      sources/Manifest.uc
  39. 1
      sources/ServerAPI/ServerAcediaAdapter.uc
  40. 2
      sources/ServerAPI/ServerGlobal.uc

2
config/AcediaSystem.ini

@ -3,7 +3,7 @@
[AcediaCore.AcediaEnvironment]
debugMode=false
[AcediaCore.SideEffects]
[AcediaCore.ServerSideEffects]
; Acedia requires adding its own `GameRules` to listen to many different
; game events.
; It's normal for a mod to add its own game rules: game rules are

277
sources/BaseAPI/API/SideEffects/SideEffect.uc

@ -0,0 +1,277 @@
/**
* Author: dkanus
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2022-2023 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 <https://www.gnu.org/licenses/>.
*/
class SideEffect extends AcediaObject;
//! Defines the concept of "side effects" in the context of the Acedia and its derivative mods.
//!
//! Side effects are changes that are not part of the mod's main functionality, but rather something
//! necessary to enable that functionality, while also possibly affecting how other mods work.
//! Documenting these side effects helps developers and server admins understand changes performed
//! by Acedia or mods based on it, and anticipate any potential conflicts or issues that may arise.
//!
//! It should be noted that what constitutes a side effect is loosely defined, and it is simply
//! a tool to inform others that something unexpected has happened, possibly breaking other mods.
//! AcediaCore aims to leave a minimal footprint, but still needs to make some changes
//! (e.g., adding GameRules, patching code of some functions), and [`SideEffects`] can be used to
//! document them.
//! Similarly, [`SideEffect`]s can be used to document changes made by AcediaFixes, a package meant
//! only for fixing bugs that inevitably needs to make many under-the-hood changes to achieve
//! that goal.
//!
//! On the other hand gameplay mods like Futility or Ire can make a lot of changes, but they can all
//! be just expected part of its direct functionality: we expect feature that shares dosh of leavers
//! to alter players' dosh values, so this is not a side effect.
//! Such mods are likely not going to have to specify any side effects whatsoever.
var private Text name;
var private Text description;
var private Text package;
var private Text source;
var private Text status;
var private bool initialized;
protected function Finalizer() {
_.memory.Free(name);
_.memory.Free(description);
_.memory.Free(package);
_.memory.Free(source);
_.memory.Free(status);
name = none;
description = none;
package = none;
source = none;
status = none;
initialized = false;
}
/// Checks whether caller [`SideEffect`] was initialized.
///
/// Initialization must happen directly after creation and only initialized instances should
/// ever be used.
public final function bool IsInitialized() {
return initialized;
}
/// This function is used to set the initial values of the [`SideEffect`] object properties when it
/// is first created.
///
/// All arguments must be not `none`.
///
/// Returns `true` if the initialization was successful, `false` otherwise (including the case where
/// the [`SideEffect`] object has already been initialized).
public final function bool Initialize(
BaseText sideEffectName,
BaseText sideEffectDescription,
BaseText sideEffectPackage,
BaseText sideEffectSource,
BaseText sideEffectStatus
) {
if (initialized) return false;
if (sideEffectName == none) return false;
if (sideEffectDescription == none) return false;
if (sideEffectPackage == none) return false;
if (sideEffectSource == none) return false;
if (sideEffectStatus == none) return false;
name = sideEffectName.Copy();
description = sideEffectDescription.Copy();
package = sideEffectPackage.Copy();
source = sideEffectSource.Copy();
status = sideEffectStatus.Copy();
initialized = true;
return true;
}
/// This function is used to set the initial values of the [`SideEffect`] object properties when it
/// is first created.
///
/// Returns `true` if the initialization was successful, `false` otherwise (including the case where
/// the [`SideEffect`] object has already been initialized).
public final function bool Initialize_S(
string sideEffectName,
string sideEffectDescription,
string sideEffectPackage,
string sideEffectSource,
string sideEffectStatus
) {
name = _.text.FromString(sideEffectName);
description = _.text.FromString(sideEffectDescription);
package = _.text.FromString(sideEffectPackage);
source = _.text.FromString(sideEffectSource);
status = _.text.FromString(sideEffectStatus);
initialized = true;
return true;
}
/// Returns a brief summary that conveys the purpose of the caller [SideEffect] to the user in
/// a clear and concise manner.
///
/// While there is no hard limit on the length of this value, it is recommended to keep it under 80
/// characters for readability.
///
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is
/// a required property of the [`SideEffect`] object.
public final function Text GetName() {
if (initialized) {
return name.Copy();
}
return none;
}
/// Returns a brief summary that conveys the purpose of the caller [SideEffect] to the user in
/// a clear and concise manner.
///
/// While there is no hard limit on the length of this value, it is recommended to keep it under 80
/// characters for readability.
public final function string GetName_S() {
if (initialized && name != none) {
return name.ToString();
}
return "";
}
/// Returns the detailed description of the caller [`SideEffect`], which describes what was done
/// and why the relevant change was necessary.
///
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is
/// a required property of the [`SideEffect`] object.
public final function Text GetDescription() {
if (initialized) {
return description.Copy();
}
return none;
}
/// Returns the detailed description of the caller [`SideEffect`], which describes what was done
/// and why the relevant change was necessary.
public final function string GetDescription_S() {
if (initialized && description != none) {
return description.ToString();
}
return "";
}
/// Returns the name of the package ("*.u" file) that introduced the changes
/// represented by the caller `SideEffect`.
///
/// It should be noted that even if a different package requested the functionality that led to
/// the changes being made, the package responsible for the side effect is the one that performed
/// the changes.
///
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is
/// a required property of the [`SideEffect`] object.
public final function Text GetPackage() {
if (initialized) {
return package.Copy();
}
return none;
}
/// Returns the name of the package ("*.u" file) that introduced the changes
/// represented by the caller `SideEffect`.
///
/// It should be noted that even if a different package requested the functionality that led to
/// the changes being made, the package responsible for the side effect is the one that performed
/// the changes.
public final function string GetPackage_S() {
if (initialized && package != none) {
return package.ToString();
}
return "";
}
/// The origin of this change within the package is specified, and for larger packages, additional
/// details can be provided to clarify the cause of the change.
///
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is
/// a required property of the [`SideEffect`] object.
public final function Text GetSource() {
if (initialized) {
return source.Copy();
}
return none;
}
/// The origin of this change within the package is specified, and for larger packages, additional
/// details can be provided to clarify the cause of the change.
public final function string GetSource_S() {
if (initialized && source != none) {
return source.ToString();
}
return "";
}
/// The status of the caller [`SideEffect`], that is used to differentiate between different ways
/// that a side effect may have been introduced, allowing for better tracking and management of
/// the effect.
///
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is
/// a required property of the [`SideEffect`] object.
public final function Text GetStatus() {
if (initialized) {
return status.Copy();
}
return none;
}
/// The status of the caller [`SideEffect`], that is used to differentiate between different ways
/// that a side effect may have been introduced, allowing for better tracking and management of
/// the effect.
public final function string GetStatus_S() {
if (initialized && status != none) {
return status.ToString();
}
return "";
}
public function bool IsEqual(Object other) {
local SideEffect otherSideEffect;
if (self == other) return true;
otherSideEffect = SideEffect(other);
if (otherSideEffect == none) return false;
if (!otherSideEffect.initialized) return false;
if (GetHashCode() != otherSideEffect.GetHashCode()) return false;
if (!name.Compare(otherSideEffect.name,, SFORM_SENSITIVE)) return false;
if (!package.Compare(otherSideEffect.package,, SFORM_SENSITIVE)) return false;
if (!source.Compare(otherSideEffect.source,, SFORM_SENSITIVE)) return false;
if (!status.Compare(otherSideEffect.status,, SFORM_SENSITIVE)) return false;
if (!description.Compare(otherSideEffect.description,, SFORM_SENSITIVE)) return false;
return true;
}
protected function int CalculateHashCode() {
local int result;
if (initialized) {
result = name.GetHashCode();
result = CombineHash(result, description.GetHashCode());
result = CombineHash(result, package.GetHashCode());
result = CombineHash(result, source.GetHashCode());
result = CombineHash(result, status.GetHashCode());
return result;
}
}
defaultproperties {
}

194
sources/BaseAPI/API/SideEffects/SideEffectAPI.uc

@ -0,0 +1,194 @@
/**
* Author: dkanus
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2022-2023 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 <https://www.gnu.org/licenses/>.
*/
class SideEffectAPI extends AcediaObject;
var private array<SideEffect> activeSideEffects;
/// Returns an array containing all SideEffect objects that have been registered up to this point.
///
/// The order of the elements in the array is not guaranteed.
public function array<SideEffect> GetAll() {
local int i;
for (i = 0; i < activeSideEffects.length; i += 1) {
activeSideEffects[i].NewRef();
}
return activeSideEffects;
}
/// Returns all registered [`SideEffects`] that are associated with the specified package name
/// (case-insensitive).
public function array<SideEffect> GetFromPackage(BaseText packageName) {
local int i;
local Text nextPackage;
local array<SideEffect> result;
if (packageName == none) {
return result;
}
for (i = 0; i < activeSideEffects.length; i += 1) {
nextPackage = activeSideEffects[i].GetPackage();
if (packageName.Compare(nextPackage, SCASE_INSENSITIVE)) {
activeSideEffects[i].NewRef();
result[result.length] = activeSideEffects[i];
}
_.memory.Free(nextPackage);
}
return result;
}
/// Adds a new side effect to the list of active side effects.
///
/// This method will fail if any of its arguments are `none` or a side effect with that exact
/// contents was already added.
public function SideEffect Add(
BaseText sideEffectName,
BaseText sideEffectDescription,
BaseText sideEffectPackage,
BaseText sideEffectSource,
BaseText sideEffectStatus
) {
local bool initialized;
local SideEffect newSideEffect;
newSideEffect = SideEffect(_.memory.Allocate(class'SideEffect'));
initialized = newSideEffect.Initialize(
sideEffectName,
sideEffectDescription,
sideEffectPackage,
sideEffectSource,
sideEffectStatus);
if (initialized) {
if (!AddInstance(newSideEffect)) {
_.memory.Free(newSideEffect);
return none;
}
} else {
_.memory.Free(newSideEffect);
return none;
}
return newSideEffect;
}
/// Adds a new side effect to the list of active side effects.
///
/// This method will fail if a side effect with that exact contents was already added.
public function SideEffect Add_S(
string sideEffectName,
string sideEffectDescription,
string sideEffectPackage,
string sideEffectSource,
string sideEffectStatus
) {
local bool initialized;
local SideEffect newSideEffect;
newSideEffect = SideEffect(_.memory.Allocate(class'SideEffect'));
initialized = newSideEffect.Initialize_S(
sideEffectName,
sideEffectDescription,
sideEffectPackage,
sideEffectSource,
sideEffectStatus);
if (initialized) {
if (!AddInstance(newSideEffect)) {
_.memory.Free(newSideEffect);
return none;
}
} else {
return none;
}
return newSideEffect;
}
/// Adds a new side effect to the list of active side effects.
///
/// This method will fail if its argument is `none`, non-initialized or a side effect with that
/// exact contents was already added.
public function bool AddInstance(SideEffect newSideEffect) {
local int i;
if (newSideEffect == none) return false;
if (!newSideEffect.IsInitialized()) return false;
for (i = 0; i < activeSideEffects.length; i += 1) {
if (activeSideEffects[i].IsEqual(newSideEffect)) {
return false;
}
}
newSideEffect.NewRef();
activeSideEffects[activeSideEffects.length] = newSideEffect;
LogAddingSideEffectChange(newSideEffect, true);
return true;
}
/// Removes a side effect from the list of active side effects.
///
/// This method will fail if its argument is `none`, non-initialized or a side effect with its
/// contents isn't in the records.
public function bool RemoveInstance(SideEffect inactiveSideEffect) {
local int i;
local bool foundInstance;
if (inactiveSideEffect == none) {
return false;
}
for (i = 0; i < activeSideEffects.length; i += 1) {
if (activeSideEffects[i].IsEqual(inactiveSideEffect)) {
LogAddingSideEffectChange(activeSideEffects[i], false);
_.memory.Free(activeSideEffects[i]);
activeSideEffects.Remove(i, 1);
foundInstance = true;
}
}
return foundInstance;
}
private function LogAddingSideEffectChange(SideEffect effect, bool added) {
local MutableText builder;
local Text sideEffectData;
if (effect == none) {
return;
}
builder = _.text.Empty();
if (added) {
builder.Append(P("NEW SIDE EFFECT: "));
} else {
builder.Append(P("REMOVED SIDE EFFECT: "));
}
sideEffectData = effect.GetName();
builder.Append(sideEffectData);
_.memory.Free(sideEffectData);
sideEffectData = effect.GetStatus();
if (sideEffectData != none) {
builder.Append(P(" {"));
builder.Append(sideEffectData);
_.memory.Free(sideEffectData);
builder.Append(P("}"));
}
_.logger.Info(builder);
builder.FreeSelf();
}
defaultproperties {
}

76
sources/BaseAPI/API/Unflect/FunctionReplacement.uc

@ -0,0 +1,76 @@
/**
* Config class for storing map lists.
* Copyright 2023 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 <https://www.gnu.org/licenses/>.
*/
class FunctionReplacement extends AcediaObject;
var private Text replaced;
var private Text replacer;
var private SideEffect effect;
protected function Finalizer() {
_.memory.Free(replaced);
_.memory.Free(replacer);
_.memory.Free(effect);
replaced = none;
replacer = none;
effect = none;
}
public static final function FunctionReplacement Make(
BaseText oldFunction,
BaseText newFunction,
SideEffect sideEffect
) {
local FunctionReplacement newReplacement;
if (oldFunction == none) return none;
if (newFunction == none) return none;
if (sideEffect == none) return none;
newReplacement = FunctionReplacement(__().memory.Allocate(class'FunctionReplacement'));
newReplacement.replaced = oldFunction.Copy();
newReplacement.replacer = newFunction.Copy();
sideEffect.NewRef();
newReplacement.effect = sideEffect;
return newReplacement;
}
public final function Text GetReplacedFunctionName() {
return replaced.Copy();
}
public final function string GetReplacedFunctionName_S() {
return replaced.ToString();
}
public final function Text GetReplacerFunctionName() {
return replacer.Copy();
}
public final function string GetReplacerFunctionName_S() {
return replacer.ToString();
}
public final function SideEffect GetSideEffect() {
effect.NewRef();
return effect;
}
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/Tests/MockInitialClass.uc

@ -0,0 +1,30 @@
/**
* Config class for storing map lists.
* Copyright 2023 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 <https://www.gnu.org/licenses/>.
*/
class MockInitialClass extends AcediaObject;
var public int counter;
public final function int DoIt() {
counter += 1;
return counter;
}
defaultproperties {
}

33
sources/BaseAPI/API/Unflect/Tests/MockReplacerClass.uc

@ -0,0 +1,33 @@
/**
* Config class for storing map lists.
* Copyright 2023 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 <https://www.gnu.org/licenses/>.
*/
class MockReplacerClass extends MockInitialClass;
public final function int DoIt2() {
counter += 2;
return -counter;
}
public final function int DoIt3() {
counter -= 1;
return 7;
}
defaultproperties {
}

87
sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc

@ -0,0 +1,87 @@
/**
* Set of tests for `Command` class.
* Copyright 2021 - 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 <https://www.gnu.org/licenses/>.
*/
class TEST_Unflect extends TestCase
abstract;
protected static function TESTS() {
local MockInitialClass obj;
obj = MockInitialClass(__().memory.Allocate(class'MockInitialClass'));
Context("Replacing functions with `UnflectApi`");
Test_InitialReplacement(obj);
Test_SecondReplacement(obj);
Test_ReplacementWithSelf(obj);
Test_RevertingReplacement(obj);
}
protected static function Test_InitialReplacement(MockInitialClass obj) {
Issue("Functions aren't being replaced correctly the first time.");
TEST_ExpectTrue(__().unflect.ReplaceFunction_S(
"AcediaCore.MockInitialClass.DoIt",
"AcediaCore.MockReplacerClass.DoIt2",
"testing"));
TEST_ExpectTrue(obj.DoIt() == -2);
TEST_ExpectTrue(obj.counter == 2);
TEST_ExpectTrue(obj.DoIt() == -4);
TEST_ExpectTrue(obj.counter == 4);
}
protected static function Test_SecondReplacement(MockInitialClass obj) {
Issue("Functions aren't being replaced correctly in case they were already replaced.");
TEST_ExpectTrue(__().unflect.ReplaceFunction_S(
"AcediaCore.MockInitialClass.DoIt",
"AcediaCore.MockReplacerClass.DoIt3",
"testing"));
TEST_ExpectTrue(obj.DoIt() == 7);
TEST_ExpectTrue(obj.counter == 3);
TEST_ExpectTrue(obj.DoIt() == 7);
TEST_ExpectTrue(obj.counter == 2);
}
protected static function Test_ReplacementWithSelf(MockInitialClass obj) {
Issue("Attempting to replacing function with itself makes unexpected change.");
TEST_ExpectFalse(__().unflect.ReplaceFunction_S(
"AcediaCore.MockInitialClass.DoIt",
"AcediaCore.MockInitialClass.DoIt",
"testing"));
TEST_ExpectTrue(obj.DoIt() == 7);
TEST_ExpectTrue(obj.counter == 1);
}
protected static function Test_RevertingReplacement(MockInitialClass obj) {
Issue("Reverting replaced function doesn't work.");
TEST_ExpectTrue(__().unflect.RevertFunction_S("AcediaCore.MockInitialClass.DoIt"));
TEST_ExpectTrue(obj.DoIt() == 2);
TEST_ExpectTrue(obj.counter == 2);
TEST_ExpectTrue(obj.DoIt() == 3);
TEST_ExpectTrue(obj.counter == 3);
Issue("Reverting already reverted function ends in success.");
TEST_ExpectFalse(__().unflect.RevertFunction_S("AcediaCore.MockInitialClass.DoIt"));
Issue("Reverting already reverted function leads to unexpected results.");
TEST_ExpectTrue(obj.DoIt() == 4);
TEST_ExpectTrue(obj.counter == 4);
}
defaultproperties {
caseName = "Function replacement"
caseGroup = "Unflect"
}

29
sources/BaseAPI/API/Unflect/TypeCast.uc

@ -0,0 +1,29 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class TypeCast extends Object;
var Object nativeType;
final function NativeCast(Object type) {
nativeType = type;
}
defaultproperties {
}

49
sources/BaseAPI/API/Unflect/UClass.uc

@ -0,0 +1,49 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UClass extends UState within Package;
var int classFlags;
var int classUnique;
var Guid classGuid;
var UClass classWithin;
var name classConfigName;
var array<struct RepRecord {
var UProperty property;
var int index;
}> classReps;
var array<UField> netFields;
var array<struct Dependency {
var UClass class;
var int deep;
var int scriptTextCRC;
}> dependencies;
var array<name> packageImports;
var array<byte> defaults;
var array<name> hideCategories;
var array<name> dependentOn;
var string defaultPropText;
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UClassCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UClassCast extends Object;
var UClass nativeType;
final function UClass Cast(Class type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

28
sources/BaseAPI/API/Unflect/UField.uc

@ -0,0 +1,28 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UField extends Object
abstract;
var UField superField;
var UField next;
var UField hashNext;
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UFieldCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UFieldCast extends Object;
var UField nativeType;
final function UField Cast(Field type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

35
sources/BaseAPI/API/Unflect/UFunction.uc

@ -0,0 +1,35 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UFunction extends UStruct within UState
dependson(Unflect);
var byte functionMD5Digest[16];
var int functionFlags;
var Unflect.Int16 nativeIndex;
var Unflect.Int16 repOffset;
var byte operPrecedence;
var byte numParms;
var Unflect.Int16 parmsSize;
var Unflect.Int16 returnValueOffset;
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UFunctionCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UFunctionCast extends Object;
var UFunction nativeType;
final function UFunction Cast(Function type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

41
sources/BaseAPI/API/Unflect/UProperty.uc

@ -0,0 +1,41 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UProperty extends UField within UField
abstract;
var int arrayDim;
var int elementSize;
var int propertyFlags;
var name category;
var byte repOffset[2];
var byte repIndex[2];
var transient int offset;
var transient UProperty propertyLinkNext;
var transient UProperty configLinkNext;
var transient UProperty constructorLinkNext;
var transient UProperty nextRef;
var transient UProperty repOwner;
var string commentString;
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UPropertyCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UPropertyCast extends Object;
var UProperty nativeType;
final function UProperty Cast(Property type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UState.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UState extends UStruct
dependson(Unflect);
var Unflect.Int64 probeMask;
var Unflect.Int64 ignoreMask;
var int stateFlags;
var Unflect.Int16 labelTableOffset;
var UField vfHash[256];
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UStateCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UStateCast extends Object;
var UState nativeType;
final function UState Cast(State type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

48
sources/BaseAPI/API/Unflect/UStruct.uc

@ -0,0 +1,48 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UStruct extends UField;
var UTextBuffer scriptText;
var UTextBuffer cppText;
var UField children;
var int propertiesSize;
var name friendlyName;
var array<byte> script;
var int textPos;
var int line;
var struct EStructFlags {
var bool native;
var bool export;
var bool long;
var bool init;
var bool unused1;
var bool unused2;
var bool unused3;
var bool unused4;
} StructFlags;
var Property refLink;
var Property propertyLink;
var Property configLink;
var Property constructorLink;
defaultproperties {
}

30
sources/BaseAPI/API/Unflect/UStructCast.uc

@ -0,0 +1,30 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UStructCast extends Object;
var UStruct nativeType;
final function UStruct Cast(/*Core.Struct*/ Object type) {
super(TypeCast).NativeCast(type);
return nativeType;
}
defaultproperties {
}

28
sources/BaseAPI/API/Unflect/UTextBuffer.uc

@ -0,0 +1,28 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class UTextBuffer extends Object;
var private native const pointer outputDeviceVtbl;
var int pos, top;
var string text;
defaultproperties {
}

32
sources/BaseAPI/API/Unflect/Unflect.uc

@ -0,0 +1,32 @@
/**
* One of the original Unflect files.
* Copyright 2022-2023 EliotVU
*------------------------------------------------------------------------------
* 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 <https://www.gnu.org/licenses/>.
*/
class Unflect extends Object
abstract;
struct Int16 {
var byte h, l;
};
struct Int64 {
var int h, l;
};
defaultproperties {
}

409
sources/BaseAPI/API/Unflect/UnflectApi.uc

@ -0,0 +1,409 @@
/**
* Config class for storing map lists.
* Copyright 2020 bibibi
* 2020-2023 Shtoyan
* 2023 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 <https://www.gnu.org/licenses/>.
*/
class UnflectApi extends AcediaObject;
//! This API offers advanced reflection capabilities for Unreal Script.
//!
//! Currently, the API supports the ability to replace the code of existing functions with
//! custom code.
//! This can greatly simplify the process of bug fixing and hooking into game events.
/// A variable responsible for replacing function code in real-time.
/// This variable is used to support dynamic function replacement/patching and event interception
/// at runtime.
var private UFunctionCast functionCaster;
/// Maps lower case function name (specifies by the full path "package.class.functionName")
/// to a `FunctionRule` that completely describes how it was replaced
var private HashTable completedReplacements;
/// Maps lower case function name (specifies by the full path "package.class.functionName")
/// to the `ByteArrayBox` with that function's original code.
var private HashTable originalScriptCodes;
var private LoggerAPI.Definition warnSameFunction;
var private LoggerAPI.Definition warnOverridingReplacement, errFailedToFindFunction;
var private LoggerAPI.Definition errReplacementWithoutSources, errCannotCreateReplacementRule;
protected function Constructor() {
functionCaster = new class'UFunctionCast';
completedReplacements = _.collections.EmptyHashTable();
originalScriptCodes = _.collections.EmptyHashTable();
}
protected function Finalizer() {
_.memory.Free(completedReplacements);
_.memory.Free(originalScriptCodes);
completedReplacements = none;
originalScriptCodes = none;
functionCaster = none;
}
/// Reverts the replacement of the function's code, restoring its original behavior.
///
/// The function to be reverted should be specified using its full path, in the format
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash").
///
/// It's worth noting that several function replacements do not stack.
/// Even if [`ReplaceFunction()`] was called multiple times in a row to replace the same function,
/// this method will cancel all the changes at once.
///
/// This method returns true if the specified function was previously replaced and has now been
/// successfully reverted.
///
/// # Errors
///
/// If the specified function cannot be found (but [`functionName`] isn't `none`), or if
/// UnflectApi has not yet replaced it with any other function, this method will log an error.
public final function bool RevertFunction(BaseText functionName) {
local bool result;
local FunctionReplacement storedReplacement;
local ByteArrayBox storedSources;
local Text functionNameLowerCase;
local UFunction functionInstance;
local SideEffect sideEffect;
if (functionName == none) {
return false;
}
functionNameLowerCase = functionName.LowerCopy();
storedReplacement = FunctionReplacement(completedReplacements.GetItem(functionNameLowerCase));
if (storedReplacement != none) {
storedSources = ByteArrayBox(originalScriptCodes.GetItem(functionNameLowerCase));
if (storedSources == none) {
_.logger.Auto(errReplacementWithoutSources).Arg(functionNameLowerCase.Copy());
} else {
functionInstance = FindFunction(functionNameLowerCase);
if (functionInstance != none) {
functionInstance.script = storedSources.Get();
completedReplacements.RemoveItem(functionNameLowerCase);
sideEffect = storedReplacement.GetSideEffect();
_.sideEffects.RemoveInstance(sideEffect);
result = true;
} else {
_.logger.Auto(errFailedToFindFunction).Arg(functionNameLowerCase.Copy());
}
}
}
_.memory.Free4(storedReplacement, functionNameLowerCase, storedSources, sideEffect);
return result;
}
/// Reverts the replacement of the function's code, restoring its original behavior.
///
/// The function to be reverted should be specified using its full path, in the format
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash").
///
/// It's worth noting that several function replacements do not stack.
/// Even if [`ReplaceFunction()`] was called multiple times in a row to replace the same function,
/// this method will cancel all the changes at once.
///
/// This method returns true if the specified function was previously replaced and has now been
/// successfully reverted.
///
/// # Errors
///
/// If the specified function cannot be found (but [`functionName`] isn't `none`), or if
/// UnflectApi has not yet replaced it with any other function, this method will log an error.
public final function bool RevertFunction_S(string functionName) {
local bool result;
local MutableText wrapper;
wrapper = _.text.FromStringM(functionName);
result = RevertFunction(wrapper);
_.memory.Free(wrapper);
return result;
}
/// Determines whether the specified function has been replaced by UnflectApi.
///
/// The function to be checked should be specified using its full path, in the format
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash").
///
/// If the function has been replaced, this method will return `true`;
/// otherwise, it will return `false`.
public final function bool IsFunctionReplaced(BaseText functionName) {
local bool result;
local Text functionNameLowerCase;
if (functionName == none) {
return false;
}
functionNameLowerCase = functionName.LowerCopy();
result = completedReplacements.HasKey(functionNameLowerCase);
_.memory.Free(functionNameLowerCase);
return result;
}
/// Determines whether the specified function has been replaced by UnflectApi.
///
/// The function to be checked should be specified using its full path, in the format
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash").
///
/// If the function has been replaced, this method will return `true`;
/// otherwise, it will return `false`.
public final function bool IsFunctionReplaced_S(string functionName) {
local bool result;
local MutableText wrapper;
wrapper = _.text.FromStringM(functionName);
result = IsFunctionReplaced(wrapper);
_.memory.Free(wrapper);
return result;
}
/// Replaces one function with another by modifying its script code in real-time.
///
/// The reason for replacement must be specified and will serve as a human-readable explanation
/// for a [SideEffect] associated with the replacement.
///
/// If you need to replace a function in a class, follow these steps:
///
/// 1. Create a new class that extends the class in which the function you want to replace is
/// located.
/// 2. Declare that function in the created class.
/// 3. **DO NOT** change the function declaration and argument types/amount.
/// 4. **DO NOT** create new local variables, as this can cause random crashes.
/// If you need additional variables, make them global and access them using the
/// `class'myNewClass'.default.myNewVariable` syntax.
/// 5. If you want to call or override parent code, make sure to always specify the desired parent
/// class name.
/// For example, use `super(TargetClass).PostBeginPlay()` instead of `super.PostBeginPlay()`.
/// This will prevent runaway loop crashes.
/// 6. Make your edits to the function's code, and then call the replacement function:
/// ```unrealscript
/// _.unflect.ReplaceFunction(
/// "package.class.targetFunction",
/// "myNewPackage.myNewClass.newFunction");
/// ```
///
/// Following these steps will help ensure that your code changes are compatible with the rest of
/// the codebase and do not cause unexpected crashes.
///
/// # Errors
///
/// This method can log error messages in cases where:
///
/// * The specified function(s) cannot be found.
/// * An attempt is made to replace a function with itself.
/// * An attempt is made to replace a function that has already been replaced.
public final function bool ReplaceFunction(
BaseText oldFunction,
BaseText newFunction,
BaseText replacementReason
) {
local bool result;
local Text oldFunctionLowerCase, newFunctionLowerCase;
if (oldFunction == none) return false;
if (newFunction == none) return false;
oldFunctionLowerCase = oldFunction.LowerCopy();
newFunctionLowerCase = newFunction.LowerCopy();
result = _replaceFunction(oldFunctionLowerCase, newFunctionLowerCase);
if (result) {
RecordNewReplacement(oldFunctionLowerCase, newFunctionLowerCase, replacementReason);
}
_.memory.Free2(oldFunctionLowerCase, newFunctionLowerCase);
return result;
}
/// Replaces one function with another by modifying its script code in real-time.
///
/// The reason for replacement must be specified and will serve as a human-readable explanation
/// for a [SideEffect] associated with the replacement.
///
/// If you need to replace a function in a class, follow these steps:
///
/// 1. Create a new class that extends the class in which the function you want to replace is
/// located.
/// 2. Declare that function in the created class.
/// 3. **DO NOT** change the function declaration and argument types/amount.
/// 4. **DO NOT** create new local variables, as this can cause random crashes.
/// If you need additional variables, make them global and access them using the
/// `class'myNewClass'.default.myNewVariable` syntax.
/// 5. If you want to call or override parent code, make sure to always specify the desired parent
/// class name.
/// For example, use `super(TargetClass).PostBeginPlay()` instead of `super.PostBeginPlay()`.
/// This will prevent runaway loop crashes.
/// 6. Make your edits to the function's code, and then call the replacement function:
/// ```unrealscript
/// _.unflect.ReplaceFunction(
/// "package.class.targetFunction",
/// "myNewPackage.myNewClass.newFunction");
/// ```
///
/// Following these steps will help ensure that your code changes are compatible with the rest of
/// the codebase and do not cause unexpected crashes.
///
/// # Errors
///
/// This method can log error messages in cases where:
///
/// * The specified function(s) cannot be found.
/// * An attempt is made to replace a function with itself.
/// * An attempt is made to replace a function that has already been replaced.
public final function bool ReplaceFunction_S(
string oldFunction,
string newFunction,
string replacementReason
) {
local Text oldWrapper, newWrapper, reasonWrapper;
local bool result;
oldWrapper = _.text.FromString(oldFunction);
newWrapper = _.text.FromString(newFunction);
reasonWrapper = _.text.FromString(replacementReason);
result = ReplaceFunction(oldWrapper, newWrapper, reasonWrapper);
_.memory.Free3(oldWrapper, newWrapper, reasonWrapper);
return result;
}
// Does actual work for function replacement.
// Arguments are assumed to be not `none` and in lower case.
private final function bool _replaceFunction(Text oldFunctionLowerCase, Text newFunctionLowerCase) {
local ByteArrayBox initialCode;
local UFunction replace, with;
replace = FindFunction(oldFunctionLowerCase);
if (replace == none) {
_.logger.Auto(errFailedToFindFunction).Arg(oldFunctionLowerCase.Copy());
return false;
}
with = FindFunction(newFunctionLowerCase);
if (with == none) {
_.logger.Auto(errFailedToFindFunction).Arg(newFunctionLowerCase.Copy());
return false;
}
if (replace == with) {
_.logger.Auto(warnSameFunction).Arg(oldFunctionLowerCase.Copy());
return false;
}
// Remember old code, if haven't done so yet.
// Since we attempt it on each replacement, the first recorded `script` value will be
// the initial code.
if (!originalScriptCodes.HasKey(oldFunctionLowerCase)) {
initialCode = _.box.ByteArray(replace.script);
originalScriptCodes.SetItem(oldFunctionLowerCase, initialCode);
_.memory.Free(initialCode);
}
replace.script = with.script;
return true;
}
// Arguments assumed to be not `none` and in lower case.
private final function UFunction FindFunction(Text functionNameLowerCase) {
local string stringName;
stringName = functionNameLowerCase.ToString();
// We need to make sure functions are loaded before performing the replacement.
DynamicLoadObject(GetClassName(stringName), class'class', true);
return functionCaster.Cast(function(FindObject(stringName, class'Function')));
}
// Arguments are assumed to be not `none`.
// `oldFunctionLowerCase` and `newFunctionLowerCase` are assumed to be in lower case.
private final function RecordNewReplacement(
Text oldFunctionLowerCase,
Text newFunctionLowerCase,
BaseText replacementReason
) {
local SideEffect oldSideEffect, newSideEffect;
local FunctionReplacement oldRule, newRule;
// Remove old `FunctionReplacement`, if there is any
oldRule = FunctionReplacement(completedReplacements.GetItem(oldFunctionLowerCase));
if (oldRule != none) {
_.logger
.Auto(warnOverridingReplacement)
.Arg(oldFunctionLowerCase.Copy())
.Arg(oldRule.GetReplacerFunctionName())
.Arg(newFunctionLowerCase.Copy());
oldSideEffect = oldRule.GetSideEffect();
_.sideEffects.RemoveInstance(oldSideEffect);
_.memory.Free2(oldRule, oldSideEffect);
}
// Create replacement instance
newSideEffect = MakeSideEffect(oldFunctionLowerCase, newFunctionLowerCase, replacementReason);
newRule = class'FunctionReplacement'.static
.Make(oldFunctionLowerCase, newFunctionLowerCase, newSideEffect);
completedReplacements.SetItem(oldFunctionLowerCase, newRule);
if (newRule == none) {
_.logger
.Auto(errCannotCreateReplacementRule)
.Arg(oldFunctionLowerCase.Copy())
.Arg(newFunctionLowerCase.Copy());
}
}
// Arguments are assumed to be not `none`.
// `oldFunctionLowerCase` and `newFunctionLowerCase` are assumed to be in lower case.
private final function SideEffect MakeSideEffect(
Text oldFunctionLowerCase,
Text newFunctionLowerCase,
BaseText replacementReason
) {
local SideEffect sideEffect;
local MutableText status;
// Add side effect
status = oldFunctionLowerCase.MutableCopy();
status.Append(P(" -> "));
status.Append(newFunctionLowerCase);
sideEffect = _.sideEffects.Add(
P("Changed function's code"),
replacementReason,
P("AcediaCore"),
P("UnflectAPI"),
status
);
_.memory.Free(status);
return sideEffect;
}
// Get the "package + dot + class" string for DynamicLoadObject()
private final static function string GetClassName(string input) {
local array<string> parts;
// create an array
Split(input, ".", parts);
// state functions
if (parts.length < 3) {
return "";
}
if (parts.length == 4) {
ReplaceText(input, "." $ parts[2], "");
ReplaceText(input, "." $ parts[3], "");
} else {
ReplaceText(input, "." $ parts[2], "");
}
return input;
}
defaultproperties {
warnOverridingReplacement = (l=LOG_Error,m="Attempt to replace a function `%1` with function `%3` after it has already been replaced with `%2`.")
warnSameFunction = (l=LOG_Error,m="Attempt to replace a function `%1` with itself.")
errFailedToFindFunction = (l=LOG_Error,m="`UnflectApi` has failed to find function `%1`.")
errReplacementWithoutSources = (l=LOG_Error,m="Cannot restore function `%1` - its initial source code wasn't preserved. This most likely means that it wasn't yet replaced.")
errCannotCreateReplacementRule = (l=LOG_Error,m="`Cannot create new rule for replacing function `%1` with `%2` even though code was successfully replaces. This should happen, please report this.")
}

4
sources/BaseAPI/Global.uc

@ -35,6 +35,8 @@ class Global extends Object;
// For getting instance of [`Global`] from any object.
var protected Global myself;
var public UnflectApi unflect;
var public SideEffectAPI sideEffects;
var public RefAPI ref;
var public BoxAPI box;
var public MathAPI math;
@ -78,11 +80,13 @@ protected function Initialize() {
memory = new class'MemoryAPI';
memory._constructor();
// `TextAPI` and `CollectionsAPI` need to be loaded before `LoggerAPI`
sideEffects = SideEffectAPI(memory.Allocate(class'SideEffectAPI'));
ref = RefAPI(memory.Allocate(class'RefAPI'));
box = BoxAPI(memory.Allocate(class'BoxAPI'));
text = TextAPI(memory.Allocate(class'TextAPI'));
math = MathAPI(memory.Allocate(class'MathAPI'));
collections = CollectionsAPI(memory.Allocate(class'CollectionsAPI'));
unflect = UnflectAPI(memory.Allocate(class'UnflectAPI'));
json = JsonAPI(memory.Allocate(class'JsonAPI'));
logger = LoggerAPI(memory.Allocate(class'LoggerAPI'));
color = ColorAPI(memory.Allocate(class'ColorAPI'));

1
sources/ClientAPI/ClientAcediaAdapter.uc

@ -27,7 +27,6 @@ var public const class<InteractionAPI> clientInteractionAPIClass;
defaultproperties
{
sideEffectAPIClass = class'KF1_SideEffectAPI'
timeAPIClass = class'KF1_TimeAPI'
dbAPIClass = class'DBAPI'
clientUnrealAPIClass = class'KF1_ClientUnrealAPI'

2
sources/Gameplay/KF1Frontend/Health/KF1_HealthComponent.uc

@ -67,7 +67,7 @@ private final function TryReplaceDamageTypes()
return;
}
triedToReplaceDamageTypes = true;
if (!class'SideEffects'.default.allowReplacingDamageTypes)
if (!class'ServerSideEffects'.default.allowReplacingDamageTypes)
{
_.logger.Auto(warnReplacingDamageTypesForbidden);
return;

115
sources/KFImplementation/Core/KF1_SideEffectAPI.uc

@ -1,115 +0,0 @@
/**
* Standard implementation for simple API for managing a list of
* `SideEffect` info objects: can add, remove, return all and by package.
* 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 <https://www.gnu.org/licenses/>.
*/
class KF1_SideEffectAPI extends SideEffectAPI;
var private array<SideEffect> activeSideEffects;
public function array<SideEffect> GetAll()
{
local int i;
for (i = 0; i < activeSideEffects.length; i += 1) {
activeSideEffects[i].NewRef();
}
return activeSideEffects;
}
public function SideEffect GetClass(class<SideEffect> sideEffectClass)
{
local int i;
if (sideEffectClass == none) {
return none;
}
for (i = 0; i < activeSideEffects.length; i += 1)
{
if (activeSideEffects[i].class == sideEffectClass)
{
activeSideEffects[i].NewRef();
return activeSideEffects[i];
}
}
return none;
}
public function array<SideEffect> GetFromPackage(BaseText packageName)
{
local int i;
local Text nextPackage;
local array<SideEffect> result;
if (packageName == none) {
return result;
}
for (i = 0; i < activeSideEffects.length; i += 1)
{
nextPackage = activeSideEffects[i].GetPackage();
if (nextPackage.Compare(packageName, SCASE_INSENSITIVE))
{
activeSideEffects[i].NewRef();
result[result.length] = activeSideEffects[i];
}
_.memory.Free(nextPackage);
}
return result;
}
public function bool Add(SideEffect newSideEffect)
{
local int i;
if (newSideEffect == none) {
return false;
}
for (i = 0; i < activeSideEffects.length; i += 1)
{
if (activeSideEffects[i].class == newSideEffect.class) {
return false;
}
}
newSideEffect.NewRef();
activeSideEffects[activeSideEffects.length] = newSideEffect;
return true;
}
public function bool RemoveClass(
class<SideEffect> sideEffectClass)
{
local int i;
if (sideEffectClass == none) {
return false;
}
for (i = 0; i < activeSideEffects.length; i += 1)
{
if (activeSideEffects[i].class == sideEffectClass)
{
_.memory.Free(activeSideEffects[i]);
activeSideEffects.Remove(i, 1);
return true;
}
}
return false;
}
defaultproperties
{
}

2
sources/LevelAPI/SideEffects.uc → sources/KFImplementation/Server/ServerSideEffects.uc

@ -19,7 +19,7 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class SideEffects extends AcediaObject
class ServerSideEffects extends AcediaObject
dependson(BroadcastAPI)
abstract
config(AcediaSystem);

2
sources/KFImplementation/Server/Unreal/BroadcastsAPI/BroadcastEventsObserver.uc

@ -207,7 +207,7 @@ var private Broadcast_OnHandleTextFor_Signal onHandleTextFor;
public final function Initialize(ServerUnrealService service)
{
usedInjectionLevel =
class'SideEffects'.default.broadcastHandlerInjectionLevel;
class'ServerSideEffects'.default.broadcastHandlerInjectionLevel;
if (usedInjectionLevel == BHIJ_Root) {
Disable('Tick');
}

65
sources/KFImplementation/Server/Unreal/BroadcastsAPI/BroadcastSideEffect.uc

@ -1,65 +0,0 @@
/**
* Object representing a side effect introduced into the game/server.
* Side effects in Acedia refer to changes that aren't a part of mod's main
* functionality, but rather something necessary to make that functionality
* possible that might also affect how other mods work.
* This is a simple data container that is meant to describe relevant
* changes to the human user.
* 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 <https://www.gnu.org/licenses/>.
*/
class BroadcastSideEffect extends SideEffect
dependson(BroadcastAPI);
public final function Initialize(BroadcastAPI.InjectionLevel usedInjectionLevel)
{
sideEffectName =
_.text.FromString("AcediaCore's `BroadcastHandler` injected");
sideEffectDescription =
_.text.FromString("Handling text and localized messages between server"
@ "and clients requires AcediaCore to add its own `BroadcastHandler`"
@ "into their linked list."
@ "This is normal, since `BroadcastHandler` class was designed to allow"
@ "mods to do that, however, for full functionality Acedia requires to"
@ "inject it as the very first element (`BHIJ_Root` level injection),"
@ "since some of the events become otherwise inaccessible."
@ "This can result in incompatibility with other mods that are trying"
@ "to do the same."
@ "For that reason AcediaCore can also inject its `BroadcastHandler` as"
@ "`BHIJ_Registered`.");
sideEffectPackage = _.text.FromString("AcediaCore");
sideEffectSource = _.text.FromString("UnrealAPI");
if (usedInjectionLevel == BHIJ_Root)
{
sideEffectStatus =
_.text.FromFormattedString("{$TextPositive BHIJ_Root}");
}
else if (usedInjectionLevel == BHIJ_Registered)
{
sideEffectStatus =
_.text.FromFormattedString("{$TextNetutral BHIJ_Registered}");
}
else
{
sideEffectStatus =
_.text.FromFormattedString("{$TextNegative BHIJ_None (???)}");
}
}
defaultproperties
{
}

47
sources/KFImplementation/Server/Unreal/BroadcastsAPI/KF1_BroadcastAPI.uc

@ -1,6 +1,6 @@
/**
* Acedia's default implementation for `BroadcastAPI`.
* Copyright 2021-2022 Anton Tarasenko
* Copyright 2021-2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -23,7 +23,6 @@ class KF1_BroadcastAPI extends BroadcastAPI;
// wasting resources/spamming errors in the log about our inability to do so
var private bool triedToInjectBroadcastHandler;
var private LoggerAPI.Definition infoInjectedBroadcastEventsObserver;
var private LoggerAPI.Definition errBroadcasthandlerForbidden;
var private LoggerAPI.Definition errBroadcasthandlerUnknown;
@ -94,7 +93,7 @@ public function Broadcast_OnHandleLocalizedFor_Slot OnHandleLocalizedFor(
/**
* Method that attempts to inject Acedia's `BroadcastEventObserver`, while
* respecting settings inside `class'SideEffects'`.
* respecting settings inside `class'ServerSideEffects'`.
*
* @param service Reference to `ServerUnrealService` to exchange signal and
* slots classes with.
@ -102,27 +101,19 @@ public function Broadcast_OnHandleLocalizedFor_Slot OnHandleLocalizedFor(
protected final function TryInjectBroadcastHandler(ServerUnrealService service)
{
local InjectionLevel usedLevel;
local BroadcastSideEffect sideEffect;
local BroadcastEventsObserver broadcastObserver;
if (triedToInjectBroadcasthandler) {
return;
}
triedToInjectBroadcasthandler = true;
usedLevel = class'SideEffects'.default.broadcastHandlerInjectionLevel;
usedLevel = class'ServerSideEffects'.default.broadcastHandlerInjectionLevel;
broadcastObserver = BroadcastEventsObserver(_server.unreal.broadcasts.Add(
class'BroadcastEventsObserver', usedLevel));
if (broadcastObserver != none)
{
broadcastObserver.Initialize(service);
sideEffect =
BroadcastSideEffect(_.memory.Allocate(class'BroadcastSideEffect'));
sideEffect.Initialize(usedLevel);
_server.sideEffects.Add(sideEffect);
_.memory.Free(sideEffect);
_.logger
.Auto(infoInjectedBroadcastEventsObserver)
.Arg(InjectionLevelIntoText(usedLevel));
AddSideEffect();
return;
}
// We are here if we have failed
@ -137,6 +128,35 @@ protected final function TryInjectBroadcastHandler(ServerUnrealService service)
}
}
private final static function AddSideEffect() {
local InjectionLevel usedLevel;
local Text sideEffectStatus;
local SideEffect newSideEffect;
usedLevel = class'ServerSideEffects'.default.broadcastHandlerInjectionLevel;
if (usedLevel == BHIJ_Root) {
sideEffectStatus = __().text.FromFormattedString("{$TextPositive BHIJ_Root}");
} else if (usedLevel == BHIJ_Registered) {
sideEffectStatus = __().text.FromFormattedString("{$TextNeutral BHIJ_Registered}");
} else {
sideEffectStatus = __().text.FromFormattedString("{$TextNegative BHIJ_None (???)}");
}
newSideEffect = __().sideEffects.Add(
P("AcediaCore's `BroadcastHandler` injected"),
P("Handling text and localized messages between server and clients requires AcediaCore to"
@ "add its own `BroadcastHandler` into their linked list. This is normal, since"
@ "`BroadcastHandler` class was designed to allow mods to do that, however, for full"
@ "functionality Acedia requires to inject it as the very first element"
@ "(`BHIJ_Root` level injection), since some of the events become otherwise"
@ "inaccessible. This can result in incompatibility with other mods that are trying to"
@ "do the same. For that reason AcediaCore can also inject its `BroadcastHandler` as"
@ "`BHIJ_Registered`."),
P("AcediaCore"),
P("UnrealAPI"),
sideEffectStatus);
__().memory.Free(newSideEffect);
}
private final function Text InjectionLevelIntoText(
InjectionLevel injectionLevel)
{
@ -257,7 +277,6 @@ public function bool IsAdded(class<BroadcastHandler> BHClassToFind)
defaultproperties
{
infoInjectedBroadcastEventsObserver = (l=LOG_Info,m="Injected AcediaCore's `BroadcastEventsObserver` with level `%1`.")
errBroadcastHandlerForbidden = (l=LOG_Error,m="Injected AcediaCore's `BroadcastEventsObserver` is required, but forbidden by AcediaCore's settings: in file \"AcediaSystem.ini\", section [AcediaCore.SideEffects], variable `broadcastHandlerInjectionLevel`.")
errBroadcastHandlerUnknown = (l=LOG_Error,m="Injected AcediaCore's `BroadcastEventsObserver` failed to be injected with level `%1` for unknown reason.")
}

43
sources/KFImplementation/Server/Unreal/GameRulesAPI/GameRulesSideEffect.uc

@ -1,43 +0,0 @@
/**
* Object representing a side effect introduced into the game/server.
* Side effects in Acedia refer to changes that aren't a part of mod's main
* functionality, but rather something necessary to make that functionality
* possible that might also affect how other mods work.
* This is a simple data container that is meant to describe relevant
* changes to the human user.
* 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 <https://www.gnu.org/licenses/>.
*/
class GameRulesSideEffect extends SideEffect;
public final function Initialize()
{
sideEffectName =
_.text.FromString("AcediaCore's `AcediaGameRules` added");
sideEffectDescription =
_.text.FromString("`GameRule`s is one of the main ways to get notified"
@ "about various gameplay-related events in Unreal Engine."
@ "Of course AcediaCore would require handling some of those events,"
@ "depending on how it's used.");
sideEffectPackage = _.text.FromString("AcediaCore");
sideEffectSource = _.text.FromString("UnrealAPI");
sideEffectStatus = _.text.FromFormattedString("{$TextPositive active}");
}
defaultproperties
{
}

30
sources/KFImplementation/Server/Unreal/GameRulesAPI/KF1_GameRulesAPI.uc

@ -1,6 +1,6 @@
/**
* Acedia's default implementation for `GameRulesAPI` API.
* Copyright 2021-2022 Anton Tarasenko
* Copyright 2021-2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -23,7 +23,6 @@ class KF1_GameRulesAPI extends GameRulesAPI;
// wasting resources/spamming errors in the log about our inability to do so
var private bool triedToInjectGameRules;
var private LoggerAPI.Definition infoAddedGameRules;
var private LoggerAPI.Definition errGameRulesForbidden;
var private LoggerAPI.Definition errGameRulesUnknown;
@ -132,7 +131,7 @@ public function GameRules_OnScoreKill_Slot OnScoreKill(
/**
* Method that attempts to inject Acedia's `AcediaGameRules`, while
* respecting settings inside `class'SideEffects'`.
* respecting settings inside `class'ServerSideEffects'`.
*
* @param service Reference to `ServerUnrealService` to exchange signal and
* slots classes with.
@ -140,13 +139,12 @@ public function GameRules_OnScoreKill_Slot OnScoreKill(
protected function TryAddingGameRules(ServerUnrealService service)
{
local AcediaGameRules gameRules;
local GameRulesSideEffect sideEffect;
if (triedToInjectGameRules) {
return;
}
triedToInjectGameRules = true;
if (!class'SideEffects'.default.allowAddingGameRules)
if (!class'ServerSideEffects'.default.allowAddingGameRules)
{
_.logger.Auto(errGameRulesForbidden);
return;
@ -155,18 +153,27 @@ protected function TryAddingGameRules(ServerUnrealService service)
if (gameRules != none)
{
gameRules.Initialize(service);
sideEffect =
GameRulesSideEffect(_.memory.Allocate(class'GameRulesSideEffect'));
sideEffect.Initialize();
_server.sideEffects.Add(sideEffect);
_.memory.Free(sideEffect);
_.logger.Auto(infoAddedGameRules);
AddSideEffect();
}
else {
_.logger.Auto(errGameRulesUnknown);
}
}
private final static function AddSideEffect() {
local SideEffect newSideEffect;
newSideEffect = __().sideEffects.Add(
P("AcediaCore's `AcediaGameRules` added"),
P("`GameRule`s is one of the main ways to get notified about various gameplay-related"
@ "events in Unreal Engine. Of course AcediaCore would require handling some of those"
@ "events, depending on how it's used."),
P("AcediaCore"),
P("UnrealAPI"),
F("{$TextPositive active}"));
__().memory.Free(newSideEffect);
}
public function GameRules Add(class<GameRules> newRulesClass)
{
local GameRules newGameRules;
@ -236,7 +243,6 @@ public function bool AreAdded(class<GameRules> rulesClassToCheck)
defaultproperties
{
infoAddedGameRules = (l=LOG_Info,m="Added AcediaCore's `AcediaGameRules`.")
errGameRulesForbidden = (l=LOG_Error,m="Adding AcediaCore's `AcediaGameRules` is required, but forbidden by AcediaCore's settings: in file \"AcediaSystem.ini\", section [AcediaCore.SideEffects], variable `allowAddingGameRules`.")
errGameRulesUnknown = (l=LOG_Error,m="Adding AcediaCore's `AcediaGameRules` failed to be injected with level for unknown reason.")
}

164
sources/LevelAPI/API/SideEffects/SideEffect.uc

@ -1,164 +0,0 @@
/**
* Object representing a side effect introduced into the game/server.
* Side effects in Acedia refer to changes that aren't a part of mod's main
* functionality, but rather something necessary to make that functionality
* possible that might also affect how other mods work.
* This is a simple data container that is meant to describe relevant
* changes to the human user.
* 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 <https://www.gnu.org/licenses/>.
*/
class SideEffect extends AcediaObject
abstract;
/**
* # Side effects
*
* In Acedia "side effect" refers to changes that aren't a part of mod's
* main functionality, but rather something necessary to make that
* functionality possible that might also affect how other mods work. Their
* purpose is to help developers and server admins figure out what changes were
* performed by Acedia or mods based on it.
* What needs to be considered a side effect is loosely defined and they
* are simply a tool to inform others that something they might not have
* expected has happened, that can possibly break other (their) mods.
* AcediaCore, for example, tried to leave a minimal footprint, avoiding
* making any changes to the game classes unless requested, but it still has to
* do some changes (adding `GameRules`, replacing damage types for some zeds,
* etc.) and `SideEffect`s can be used to document these changes. They can be
* used in a similar way for AcediaFixes - a package that is only meant for
* fixing bugs, but inevitably has to make a lot of under the hood changes to
* achieve that.
* On the other hand gameplay mods like Futility can make a lot of changes,
* but they can all be just expected part of its direct functionality: we
* expect feature that shares dosh of leavers to alter players' dosh values, so
* this is not a side effect. Such mods are likely not going to have to specify
* any side effects whatsoever.
*
* ## Implementing your own `SideEffect`s
*
* Simply make a non-abstract child class based on `SideEffect`, create its
* instance filled with necessary data and register it in `SideEffectAPI` once
* side effect is introduced. If you revert introduced side effect, you should
* remove registered object through the same API.
* Each class of the `SideEffect` is supposed to represent a particular
* side effect introduced into the game engine. Whether side effect is active
* is decided by whether it is currently registered in `SideEffectAPI`.
*
* NOTE: `SideEffect` should not have any logic and should serve as
* an immutable data container.
*/
var protected Text sideEffectName;
var protected Text sideEffectDescription;
var protected Text sideEffectPackage;
var protected Text sideEffectSource;
var protected Text sideEffectStatus;
/**
* Returns name (short description) of the caller `SideEffect`. User need to
* be able to tell what this `SideEffect` is generally about from the glance at
* this name.
*
* Guideline is for this value to not exceed `80` characters, but this is not
* enforced.
*
* Must not be `none`.
*
* @return Name (short description) of the caller `SideEffect`.
* Guaranteed to not be `none`.
*/
public function Text GetName()
{
if (sideEffectName != none) {
return sideEffectName.Copy();
}
return none;
}
/**
* Returns description of the caller `SideEffect`. This should describe what
* was done and why relevant change was necessary.
*
* Must not be `none`.
*
* @return Description of the caller `SideEffect`.
* Guaranteed to not be `none`.
*/
public function Text GetDescription()
{
if (sideEffectDescription != none) {
return sideEffectDescription.Copy();
}
return none;
}
/**
* Returns name of the package ("*.u" file) that introduced this change.
*
* Note that if package "A" actually performed the change because another
* package "B" requested certain functionality, it is still package "A" that is
* responsible for the side effect.
*
* Must not be `none`.
*
* @return Name of the package ("*.u" file) that introduced this change.
* Guaranteed to not be `none`.
*/
public function Text GetPackage()
{
if (sideEffectPackage != none) {
return sideEffectPackage.Copy();
}
return none;
}
/**
* What part of package caused this change. For huge packages can be used to
* further specify what introduced the change.
*
* Returned value can be `none` (e.g. when it is unnecessary for small
* packages).
*
* @return Name (short description) of the part of the package that caused
* caller `SideEffect`.
*/
public function Text GetSource()
{
if (sideEffectSource != none) {
return sideEffectSource.Copy();
}
return none;
}
/**
* Status of the caller `SideEffect`. Some side effects can be introduced in
* several different ways - this value needs to help distinguish between them.
*
* @return Status of the caller `SideEffect`.
*/
public function Text GetStatus()
{
if (sideEffectStatus != none) {
return sideEffectStatus.Copy();
}
return none;
}
defaultproperties
{
}

76
sources/LevelAPI/API/SideEffects/SideEffectAPI.uc

@ -1,76 +0,0 @@
/**
* Base class for simple API for managing a list of `SideEffect` info
* objects: can add, remove, return all and by package.
* 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 <https://www.gnu.org/licenses/>.
*/
class SideEffectAPI extends AcediaObject
abstract;
/**
* Returns all so far registered `SideEffect`s.
*
* @return Array of all registered `SideEffect`s.
*/
public function array<SideEffect> GetAll();
/**
* Returns active `SideEffect` instance of the specified class
* `sideEffectClass`.
*
* @param sideEffectClass Class of side effect to return active instance of.
* @return Active `SideEffect` instance of the specified class
* `sideEffectClass`.
* `none` if either `sideEffectClass` is `none` or side effect of such
* class is not currently active.
*/
public function SideEffect GetClass(class<SideEffect> sideEffectClass);
/**
* Returns all so far registered `SideEffect`s from a package `packageName`
* (case insensitive).
*
* @param packageName Name of the package, in `SideEffect`s from which we are
* interested. Must be not `none`.
* @return Array of all registered `SideEffect`s from a package `packageName`.
* If `none`, returns an empty array.
*/
public function array<SideEffect> GetFromPackage(BaseText packageName);
/**
* Registers a new `SideEffect` object as active.
*
* @param newSideEffect Instance of some `SideEffect` class to register as
* active side effect. Must not be `none`.
* @return `true` if new side effect was added and `false` otherwise.
*/
public function bool Add(SideEffect newSideEffect);
/**
* Removes `SideEffect` of the specified sub-class from the list of active
* side effects.
*
* @param sideEffectClass Class of the side effect to remove.
* @return `true` if some side effect was removed as a result of this operation
* and `false` otherwise (even if there was no side effect of specified
* class to begin with).
*/
public function bool RemoveClass(class<SideEffect> sideEffectClass);
defaultproperties
{
}

1
sources/LevelAPI/AcediaAdapter.uc

@ -35,7 +35,6 @@ class AcediaAdapter extends AcediaObject
* specify desired `AcediaAdapter` before loading server/client core.
*/
var public const class<SideEffectAPI> sideEffectAPIClass;
var public const class<TimeAPI> timeAPIClass;
var public const class<DBAPI> dbAPIClass;

2
sources/LevelAPI/CoreGlobal.uc

@ -88,8 +88,6 @@ protected function Initialize()
return;
}
api = class'Global'.static.GetInstance().memory;
sideEffects =
SideEffectAPI(api.Allocate(adapterClass.default.sideEffectAPIClass));
time = TimeAPI(api.Allocate(adapterClass.default.timeAPIClass));
db = DBAPI(api.Allocate(adapterClass.default.dbAPIClass));
}

1
sources/Manifest.uc

@ -59,4 +59,5 @@ defaultproperties
testCases(30) = class'TEST_AcediaConfig'
testCases(31) = class'TEST_UTF8EncoderDecoder'
testCases(32) = class'TEST_AvariceStreamReader'
testCases(33) = class'TEST_Unflect'
}

1
sources/ServerAPI/ServerAcediaAdapter.uc

@ -30,7 +30,6 @@ var public const class<MutatorAPI> serverMutatorAPIClass;
defaultproperties
{
sideEffectAPIClass = class'KF1_SideEffectAPI'
timeAPIClass = class'KF1_TimeAPI'
dbAPIClass = class'DBAPI'
serverUnrealAPIClass = class'KF1_ServerUnrealAPI'

2
sources/ServerAPI/ServerGlobal.uc

@ -83,7 +83,7 @@ public final function bool ConnectServerLevelCore()
return false;
}
Initialize();
if (class'SideEffects'.default.allowHookingIntoMutate)
if (class'ServerSideEffects'.default.allowHookingIntoMutate)
{
_ = class'Global'.static.GetInstance();
class'InfoQueryHandler'.static.StaticConstructor();

Loading…
Cancel
Save