diff --git a/sources/Commands/ACommandGod.uc b/sources/Commands/ACommandGod.uc new file mode 100644 index 0000000..60c7dbf --- /dev/null +++ b/sources/Commands/ACommandGod.uc @@ -0,0 +1,205 @@ +/** + * Command for making player immortal. + * 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 ACommandGod extends Command; + +struct GodStatus +{ + // Player to whom we grant godhood + var EPlayer target; + // Is `target` only a demigod (can get damaged, but not die)? + var bool demigod; + // Should `target` be unaffected by attacks momentum? + var bool unmovable; +}; + +var private array godhoodList; + +var private ACommandGod_Announcer announcer; + +var private const int TDAMAGE, TMOMENTUM; + +protected function Finalizer() +{ + _.memory.Free(announcer); + _.kf.health.OnDamage(self).Disconnect(); + super.Finalizer(); +} + +protected function BuildData(CommandDataBuilder builder) +{ + builder.Name(P("god")).Group(P("gameplay")) + .Summary(P("Command for making player immortal.")); + builder.RequireTarget() + .Describe(P("Gives targeted players god status, making them" + @ "invincible.")); + builder.SubCommand(P("list")) + .Describe(P("Reports godhood status of targeted players.")); + builder.SubCommand(P("strip")) + .Describe(P("Strips targeted players from the godhood status.")); + builder.Option(P("demi")) + .Describe(P("This flag makes targeted players \"demigods\" instead -" + @ "they still cannot die, but they can take any non-lethal" + @ "damage.")); + builder.Option(P("unmovable")) + .Describe(P("This flag also prevents targeted players from being" + @ "affected by the momentum trasnferred from damaging attacks.")); + announcer = ACommandGod_Announcer( + _.memory.Allocate(class'ACommandGod_Announcer')); + _.kf.health.OnDamage(self).connect = ProtectDivines; +} + +protected function ExecutedFor( + EPlayer target, + CallData arguments, + EPlayer instigator) +{ + local GodStatus newGodStatus; + + announcer.Setup(target, instigator, othersConsole); + if (arguments.subCommandName.IsEmpty()) + { + newGodStatus.target = target; + newGodStatus.demigod = arguments.options.HasKey(P("demi")); + newGodStatus.unmovable = arguments.options.HasKey(P("unmovable")); + MakeGod(target, newGodStatus); + } + else if (arguments.subCommandName.Compare(P("list"))) { + announcer.AnnounceGodStatus(BorrowGodStatus(target)); + } + else if (arguments.subCommandName.Compare(P("strip"))) { + RemoveGod(target); + } +} + +private function ProtectDivines( + EPawn target, + EPawn instigator, + HashTable damageData) +{ + local int damage; + local EPlayer targetedPlayer; + local GodStatus targetDivinity; + + targetedPlayer = target.GetPlayer(); + targetDivinity = BorrowGodStatus(targetedPlayer); + _.memory.Free(targetedPlayer); + if (targetDivinity.target == none) { + return; + } + if (targetDivinity.unmovable) { + damageData.SetVector(T(TMOMENTUM), Vect(0.0f, 0.0f, 0.0f)); + } + if (targetDivinity.demiGod) + { + damage = damageData.GetInt(T(TDAMAGE)); + damage = Min(damage, target.GetHealth() - 1); + damageData.SetInt(T(TDAMAGE), damage); + } + else { + damageData.SetInt(T(TDAMAGE), 0); + } +} + +private final function MakeGod( + EPlayer target, + GodStatus newGodStatus) +{ + local int godIndex; + local bool wasGod; + local GodStatus oldGodStatus; + + if (target == none) { + return; + } + for (godIndex = 0; godIndex < godhoodList.length; godIndex += 1) + { + if (target.SameAs(godhoodList[godIndex].target)) + { + wasGod = true; + oldGodStatus = godhoodList[godIndex]; + break; + } + } + if (wasGod) + { + if ( newGodStatus.demiGod == oldGodStatus.demiGod + && newGodStatus.unmovable == oldGodStatus.unmovable) + { + announcer.AnnounceSameGod(newGodStatus); + } + else + { + announcer.AnnounceChangedGod(oldGodStatus, newGodStatus); + godhoodList[godIndex].target.FreeSelf(); + newGodStatus.target.NewRef(); + godhoodList[godIndex] = newGodStatus; + } + } + else { + announcer.AnnounceNewGod(newGodStatus); + newGodStatus.target.NewRef(); + godhoodList[godhoodList.length] = newGodStatus; + } +} + +private final function RemoveGod(EPlayer target) +{ + local int i; + + if (target == none) { + return; + } + for (i = 0; i < godhoodList.length; i += 1) + { + if (target.SameAs(godhoodList[i].target)) + { + announcer.AnnounceRemoveGod(godhoodList[i]); + godhoodList[i].target.FreeSelf(); + godhoodList.Remove(i, 1); + return; + } + } + announcer.AnnounceWasNotGod(); +} + +private final function GodStatus BorrowGodStatus(EPlayer target) +{ + local int i; + local GodStatus emptyStatus; + + if (target == none) { + return emptyStatus; + } + for (i = 0; i < godhoodList.length; i += 1) + { + if (target.SameAs(godhoodList[i].target)) { + return godhoodList[i]; + } + } + return emptyStatus; +} + +defaultproperties +{ + TDAMAGE = 0 + stringConstants(0) = "damage" + TMOMENTUM = 1 + stringConstants(1) = "momentum" +} \ No newline at end of file diff --git a/sources/Commands/ACommandGod_Announcer.uc b/sources/Commands/ACommandGod_Announcer.uc new file mode 100644 index 0000000..ab686ce --- /dev/null +++ b/sources/Commands/ACommandGod_Announcer.uc @@ -0,0 +1,225 @@ +/** + * Announcer for `ACommandGod`. + * 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 ACommandGod_Announcer extends CommandAnnouncer + dependson(ACommandGod); + +var private AnnouncementVariations godStatus, newGod, removeGod, sameGod; +var private AnnouncementVariations changedGod, wasNotGod; + +protected function Finalizer() +{ + FreeVariations(godStatus); + FreeVariations(newGod); + FreeVariations(removeGod); + FreeVariations(sameGod); + FreeVariations(changedGod); + FreeVariations(wasNotGod); + super.Finalizer(); +} + +public final function AnnounceGodStatus(ACommandGod.GodStatus status) +{ + local int i; + local MutableText statusAsText; + local array templates; + + if (!godStatus.initialized) + { + godStatus.initialized = true; + godStatus.toSelfReport = _.text.MakeTemplate_S( + "You're %1"); + godStatus.toOtherReport = _.text.MakeTemplate_S( + "%%target%% is %1"); + } + statusAsText = DisplayStatus(status); + templates = MakeArray(godStatus); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(statusAsText); + } + _.memory.Free(statusAsText); + MakeAnnouncement(godStatus); +} + +public final function AnnounceNewGod(ACommandGod.GodStatus status) +{ + local int i; + local MutableText statusAsText; + local array templates; + + if (!newGod.initialized) + { + newGod.initialized = true; + newGod.toSelfReport = _.text.MakeTemplate_S( + "You {$TextPositive made} yourself %1"); + newGod.toSelfPublic = _.text.MakeTemplate_S( + "%%instigator%% {$TextPositive made} themselves %1"); + newGod.toOtherReport = _.text.MakeTemplate_S( + "You {$TextPositive made} %%target%% %1"); + newGod.toOtherPrivate = _.text.MakeTemplate_S( + "%%instigator%% {$TextPositive made} you %1"); + newGod.toOtherPublic = _.text.MakeTemplate_S( + "%%instigator%% {$TextPositive made} %%target%% %1"); + } + statusAsText = DisplayStatus(status); + templates = MakeArray(newGod); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(statusAsText); + } + _.memory.Free(statusAsText); + MakeAnnouncement(newGod); +} + +public final function AnnounceRemoveGod(ACommandGod.GodStatus status) +{ + local int i; + local MutableText statusAsText; + local array templates; + + if (!removeGod.initialized) + { + removeGod.initialized = true; + removeGod.toSelfReport = _.text.MakeTemplate_S( + "You, %1, {$TextNegative became} a mere {$TextNegative mortal}"); + removeGod.toSelfPublic = _.text.MakeTemplate_S( + "%1 %%instigator%% {$TextNegative made} themselves a mere" + @ "{$TextNegative mortal}"); + removeGod.toOtherReport = _.text.MakeTemplate_S( + "%1 %%target%% was {$TextNegative made} a mere" + @ "{$TextNegative mortal} by you"); + removeGod.toOtherPrivate = _.text.MakeTemplate_S( + "You, %1, was {$TextNegative made} a mere {$TextNegative mortal}" + @ "by %%instigator%%"); + removeGod.toOtherPublic = _.text.MakeTemplate_S( + "%1 %%target%% was {$TextNegative made} a mere" + @ "{$TextNegative mortal} by %%instigator%%"); + } + statusAsText = DisplayStatus(status); + templates = MakeArray(removeGod); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(statusAsText); + } + _.memory.Free(statusAsText); + MakeAnnouncement(removeGod); +} + +public final function AnnounceSameGod(ACommandGod.GodStatus status) +{ + local int i; + local MutableText statusAsText; + local array templates; + + if (!sameGod.initialized) + { + sameGod.initialized = true; + sameGod.toSelfReport = _.text.MakeTemplate_S( + "You are already %1"); + sameGod.toOtherReport = _.text.MakeTemplate_S( + "%%target%% is already %1"); + } + statusAsText = DisplayStatus(status); + templates = MakeArray(sameGod); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(statusAsText); + } + _.memory.Free(statusAsText); + MakeAnnouncement(sameGod); +} + +public final function AnnounceChangedGod( + ACommandGod.GodStatus oldStatus, + ACommandGod.GodStatus newStatus) +{ + local int i; + local MutableText oldStatusAsText, newStatusAsText; + local array templates; + + if (!changedGod.initialized) + { + changedGod.initialized = true; + changedGod.toSelfReport = _.text.MakeTemplate_S( + "You, %1, {$TextPositive made} yourself %2"); + changedGod.toSelfPublic = _.text.MakeTemplate_S( + "%1 %%instigator%% {$TextPositive made} themselves %2"); + changedGod.toOtherReport = _.text.MakeTemplate_S( + "You {$TextPositive made} %1 %%target%% into %1"); + changedGod.toOtherPrivate = _.text.MakeTemplate_S( + "%%instigator%% {$TextPositive made} you, %1, into %2"); + changedGod.toOtherPublic = _.text.MakeTemplate_S( + "%%instigator%% {$TextPositive made} %1 %%target%% into %2"); + } + oldStatusAsText = DisplayStatus(oldStatus); + newStatusAsText = DisplayStatus(newStatus); + templates = MakeArray(changedGod); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(oldStatusAsText).Arg(newStatusAsText); + } + _.memory.Free(oldStatusAsText); + _.memory.Free(newStatusAsText); + MakeAnnouncement(changedGod); +} + +public final function AnnounceWasNotGod() +{ + local int i; + local array templates; + + if (!sameGod.initialized) + { + sameGod.initialized = true; + sameGod.toSelfReport = _.text.MakeTemplate_S( + "You are already a mere {$TextNegative mortal}"); + sameGod.toOtherReport = _.text.MakeTemplate_S( + "%%target%% is already a mere {$TextNegative mortal}"); + } + templates = MakeArray(sameGod); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset(); + } + MakeAnnouncement(sameGod); +} + +private final function MutableText DisplayStatus(ACommandGod.GodStatus status) +{ + local MutableText builder; + + builder = _.text.Empty(); + if (status.target == none) + { + builder.Append(F("a mere {$TextNegative mortal}")); + return builder; + } + if (status.unmovable) { + builder.Append(F("an {$TextPositive unmovable}, ")); + } + else { + builder.Append(F("a {$TextNeutral simple}, ")); + } + if (status.demigod) { + builder.Append(F("immortal {$TextNeutral demigod}")); + } + else { + builder.Append(F("invincible {$TextPositive god}")); + } + return builder; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Futility_Feature.uc b/sources/Futility_Feature.uc index 3d51d9e..958f56f 100644 --- a/sources/Futility_Feature.uc +++ b/sources/Futility_Feature.uc @@ -78,5 +78,6 @@ defaultproperties allCommandClasses(3) = class'ACommandDB' allCommandClasses(4) = class'ACommandInventory' allCommandClasses(5) = class'ACommandFeature' + allCommandClasses(6) = class'ACommandGod' errNoCommandsFeature = (l=LOG_Error,m="`Commands_Feature` is not detected, \"Futility\" will not be able to provide its functionality.") } \ No newline at end of file