diff --git a/sources/Commands/ACommandSpawn.uc b/sources/Commands/ACommandSpawn.uc new file mode 100644 index 0000000..9acde3c --- /dev/null +++ b/sources/Commands/ACommandSpawn.uc @@ -0,0 +1,130 @@ +/** + * Command for spawning new entities into the world. + * 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 ACommandSpawn extends Command; + +// TODO: use spawned name for errors output? +var private ACommandSpawn_Announcer announcer; + +protected function Finalizer() +{ + _.memory.Free(announcer); + super.Finalizer(); +} + +protected function BuildData(CommandDataBuilder builder) +{ + builder.Name(P("spawn")).Group(P("debug")) + .Summary(P("Spawns new entity on the map.")); + builder.ParamText(P("template")) + .Describe(P("Spawns new entity based on the given template at the point" + @ "player is currently looking at.")); + builder.SubCommand(P("at")) + .ParamText(P("template")) + .ParamNumber(P("x")) + .ParamNumber(P("y")) + .ParamNumber(P("z")) + .Describe(P("Spawns new entity based on the given template at" + @ "the point, given by the coordinates")); + announcer = ACommandSpawn_Announcer( + _.memory.Allocate(class'ACommandSpawn_Announcer')); +} + +protected function Executed( + CallData arguments, + EPlayer instigator) +{ + local Text givenTemplate, template; + local Vector spawnLocation; + + announcer.Setup(none, instigator, othersConsole); + givenTemplate = arguments.parameters.GetText(P("template")); + if (givenTemplate.StartsWithS("$")) { + template = _.alias.ResolveEntity(givenTemplate, true); + } + else { + template = givenTemplate.Copy(); + } + _.memory.Free(givenTemplate); + if (arguments.subCommandName.IsEmpty()) { + SpawnInInstigatorSight(instigator, template); + } + else if (arguments.subCommandName.Compare(P("at"), SCASE_INSENSITIVE)) + { + spawnLocation.x = arguments.parameters.GetFloat(P("x")); + spawnLocation.y = arguments.parameters.GetFloat(P("y")); + spawnLocation.z = arguments.parameters.GetFloat(P("z")); + SpawnAt(instigator, template, spawnLocation); + } + _.memory.Free(template); +} + +private final function SpawnAt( + EPlayer instigator, + BaseText template, + Vector spawnLocation) +{ + local EPlaceable result; + + result = _.kf.world.Spawn(template, spawnLocation); + if (result != none) { + announcer.AnnounceSpawned(template); + } + else { + announcer.AnnounceSpawningFailed(template); + } + _.memory.Free(result); +} + +private final function SpawnInInstigatorSight( + EPlayer instigator, + BaseText template) +{ + local EPlaceable result; + local Vector spawnLocation; + local TracingIterator iter; + + iter = _.kf.world.TracePlayerSight(instigator).LeaveOnlyVisible(); + if (iter.HasFinished()) + { + announcer.AnnounceFailedTrace(); + return; + } + spawnLocation = iter.GetHitLocation(); + result = _.kf.world.Spawn(template, spawnLocation); + // Shift position back a little and try again; + // this should fix a ton of spawning failures + if (result == none) + { + spawnLocation = spawnLocation + + Normal(iter.GetTracingStart() - spawnLocation) * 100; + result = _.kf.world.Spawn(template, spawnLocation); + } + if (result != none) { + announcer.AnnounceSpawned(template); + } + else { + announcer.AnnounceSpawningFailed(template); + } + _.memory.Free(result); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Commands/ACommandSpawn_Announcer.uc b/sources/Commands/ACommandSpawn_Announcer.uc new file mode 100644 index 0000000..7491a2c --- /dev/null +++ b/sources/Commands/ACommandSpawn_Announcer.uc @@ -0,0 +1,90 @@ +/** + * Announcer for `ACommandSpawn`. + * 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 ACommandSpawn_Announcer extends CommandAnnouncer; + +var private AnnouncementVariations spawned, spawningFailed, failedTrace; + +protected function Finalizer() +{ + FreeVariations(spawned); + FreeVariations(spawningFailed); + FreeVariations(failedTrace); + super.Finalizer(); +} + +public final function AnnounceSpawned(BaseText template) +{ + local int i; + local array templates; + + if (!spawned.initialized) + { + spawned.initialized = true; + spawned.toSelfReport = _.text.MakeTemplate_S( + "You {$TextPositive spawned} {$TextEmphasis %1}!"); + spawned.toSelfPublic = _.text.MakeTemplate_S( + "%%instigator%% {$TextNeutral spawned} {$TextEmphasis %1}!"); + } + templates = MakeArray(spawned); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(template); + } + MakeAnnouncement(spawned); +} + +public final function AnnounceSpawningFailed(BaseText template) +{ + local int i; + local array templates; + + if (!spawningFailed.initialized) + { + spawningFailed.initialized = true; + spawningFailed.toSelfReport = _.text.MakeTemplate_S( + "{$TextFailure Couldn't spawn} {$TextEmphasis %1}!"); + } + templates = MakeArray(spawningFailed); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset().Arg(template); + } + MakeAnnouncement(spawningFailed); +} + +public final function AnnounceFailedTrace() +{ + local int i; + local array templates; + + if (!failedTrace.initialized) + { + failedTrace.initialized = true; + failedTrace.toSelfReport = _.text.MakeTemplate_S( + "{$TextFailure Failed} to trace spawn point"); + } + templates = MakeArray(failedTrace); + for (i = 0; i < templates.length; i += 1) { + templates[i].Reset(); + } + MakeAnnouncement(failedTrace); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Futility_Feature.uc b/sources/Futility_Feature.uc index 958f56f..e0a6712 100644 --- a/sources/Futility_Feature.uc +++ b/sources/Futility_Feature.uc @@ -1,7 +1,7 @@ /** * This is the Futility feature, whose main purpose is to register commands * from its package. - * Copyright 2021 Anton Tarasenko + * Copyright 2021-2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Futility. * @@ -79,5 +79,6 @@ defaultproperties allCommandClasses(4) = class'ACommandInventory' allCommandClasses(5) = class'ACommandFeature' allCommandClasses(6) = class'ACommandGod' + allCommandClasses(7) = class'ACommandSpawn' 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