diff --git a/sources/Gameplay/BaseClasses/BaseBackend.uc b/sources/Gameplay/BaseClasses/BaseBackend.uc new file mode 100644 index 0000000..295a4d6 --- /dev/null +++ b/sources/Gameplay/BaseClasses/BaseBackend.uc @@ -0,0 +1,26 @@ +/** + * Base class for all backends. Does not define anything meaningful, which + * also means it does not put any limitations on it's implementation. + * Copyright 2021 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 BaseBackend extends AcediaObject + abstract; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/BaseFrontend.uc b/sources/Gameplay/BaseClasses/BaseFrontend.uc new file mode 100644 index 0000000..83d3624 --- /dev/null +++ b/sources/Gameplay/BaseClasses/BaseFrontend.uc @@ -0,0 +1,26 @@ +/** + * Base class for all frontends. Does not define anything meaningful, which + * also means it does not put any limitations on it's implementation. + * Copyright 2021 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 BaseFrontend extends AcediaObject + abstract; + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc b/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc new file mode 100644 index 0000000..90a490b --- /dev/null +++ b/sources/Gameplay/BaseClasses/KillingFloor/KFFrontend.uc @@ -0,0 +1,41 @@ +/** + * Frontend skeleton for basic killing floor game mode. + * Copyright 2021 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 KFFrontend extends BaseBackend + abstract; + +var private config class tradingClass; +var public ATradingComponent trading; + +protected function Constructor() +{ + if (tradingClass != none) { + trading = ATradingComponent(_.memory.Allocate(tradingClass)); + } +} + +protected function Finalizer() +{ + _.memory.Free(trading); +} + +defaultproperties +{ + tradingClass = none +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATrader.uc b/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATrader.uc new file mode 100644 index 0000000..c98d36c --- /dev/null +++ b/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATrader.uc @@ -0,0 +1,196 @@ +/** + * Class, objects of which are expected to represent traders located on + * the map. In classic KF game mode it would represent areas behind closed + * doors that open during trader time and allow to purchase weapons and ammo. + * Copyright 2021 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 ATrader extends AcediaObject + abstract; + +/** + * Returns location of the trader. + * + * Trader is usually associated with an area where players can trade and + * not just one point. Value returned by this method is merely expected to + * return position that "makes sense" for the trader. + * It can be used to calculate distance and/or path to the trader. + * + * @return Location of the caller trader. + */ +public function Vector GetLocation(); + +/** + * Returns name of the trader. + * + * Trader name can be any non-empty `Text`. + * The only requirement is that after map's initialization every trader + * should have a unique name. It is not forbidden to break this invariant later + * by `SetName()` method. + * If `none` or empty name is passed, this method should do nothing. + * + * This is not the hard requirement, but explanation of purpose. + * Name does not have to be player-friendly, but it must be human-readable: + * it is not expected to be seen by regular players, but admins might use it + * to tweak their server. + * + * @return Current name of the trader. + */ +public function Text GetName(); + +/** + * Changes name of the trader. + * + * @see `GetName()` for more details. + * + * @param newName New name of the caller trader. + * @return `true` if trader is currently enabled and `false` otherwise. + */ +public function ATrader SetName(Text newName); + +/** + * Checks if caller trader is currently enabled. + * + * Trader being enabled means that it can be opened and used for trading. + * Trader being disabled means that it cannot open for trading. + * + * This should override opened and auto-opened status. + * + * Marking disabled trader as selected is discouraged, especially for classic + * KF game mode, but should be allowed. + * + * @return `true` if trader is currently enabled and `false` otherwise. + */ +public function bool IsEnabled(); + +/** + * Sets whether caller `ATrader`'s is currently enabled. + * + * Disabling the trader should automatically "boot" players out + * (see `BootPlayers()`). + * + * @see `IsEnabled()` for more info. + * + * @param doEnable `true` if trader is currently enabled and + * `false` otherwise. + * @return Caller `ATrader` to allow for method chaining. + */ +public function ATrader SetEnabled(bool doEnable); + +/** + * Checks whether caller `ATrader` will auto-open when trading gets activated. + * + * This setting must be ignored if trader is disabled, but disabling `ATrader` + * should not reset it. + * + * @return `true` if trader is marked to always auto-open upon activating + * trading (unless it is also disabled) and `false` otherwise. + */ +public function bool IsAutoOpen(); + +/** + * Checks whether caller `ATrader` will auto-open when trading gets activated. + * + * @see `IsAutoOpen()` for more info. + * + * @param doAutoOpen `true` if trader should be marked to always auto-open + * upon activating trading and `false` otherwise. + * @return Caller `ATrader` to allow for method chaining. + */ +public function ATrader SetAutoOpen(bool doAutoOpen); + +/** + * Checks whether caller `ATrader` is currently open. + * + * `ATrader` being open means that players can "enter" (whatever that means for + * an implementation) and use `ATrader` to buy/sell equipment. + * + * @return `true` if it is open and `false` otherwise. + */ +public function bool IsOpen(); + +/** + * Changes whether caller `ATrader` is open. + * + * Closing the trader should not automatically "boot" players out + * (see `BootPlayers()`). + * + * @see `IsOpen()` for more details. + * + * @param doOpen `true` if it is open and `false` otherwise. + * @return Caller `ATrader` to allow for method chaining. + */ +public function ATrader SetOpen(bool doOpen); + +/** + * Checks whether caller `ATrader` is currently marked as selected. + * + * @see `ATradingComponent.GetSelectedTrader()` for more details. + * + * @return `true` if caller `ATrader` is selected and `false` otherwise. + */ +public function bool IsSelected(); + +/** + * Marks caller `ATrader` as a selected trader. + * + * @see `ATradingComponent.GetSelectedTrader()` for more details. + * + * @return Caller `ATrader` to allow for method chaining. + */ +public function ATrader Select(); + +/** + * Removes players from the trader's place. + * + * In classic KF game mode it teleported them right outside the doors. + * + * This method's goal is to make sure players are not stuck in trader's place + * after it is closed. If that is impossible (for traders resembling + * KF2's one), then this method should do nothing. + * + * @return Caller `ATrader` to allow for method chaining. + */ +public function ATrader BootPlayers(); + +/** + * Shortcut method to open the caller trader, guaranteed to be equivalent to + * `SetOpen(true)`. Provided for better interface. + * + * @return Caller `ATrader` to allow for method chaining. + */ +public final function ATrader Open() +{ + SetOpen(true); + return self; +} + +/** + * Shortcut method to close the caller trader, guaranteed to be equivalent to + * `SetOpen(false)`. Provided for better interface. + * + * @return Caller `ATrader` to allow for method chaining. + */ +public final function ATrader Close() +{ + SetOpen(false); + return self; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATradingComponent.uc b/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATradingComponent.uc new file mode 100644 index 0000000..87bc16c --- /dev/null +++ b/sources/Gameplay/BaseClasses/KillingFloor/Trading/ATradingComponent.uc @@ -0,0 +1,204 @@ +/** + * Subset of functionality for dealing with everything related to traders. + * Copyright 2021 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 ATradingComponent extends AcediaObject + abstract; + +var protected SimpleSignal onStartSignal; +var protected SimpleSignal onEndSignal; +var protected Trading_OnSelect_Signal onTraderSelectSignal; + +protected function Constructor() +{ + onStartSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal')); + onEndSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal')); + onTraderSelectSignal = Trading_OnSelect_Signal( + _.memory.Allocate(class'Trading_OnSelect_Signal')); +} + +protected function Finalizer() +{ + _.memory.Free(onStartSignal); + _.memory.Free(onEndSignal); + _.memory.Free(onTraderSelectSignal); +} + +/** + * Signal that will be emitted whenever trading time starts. + * + * [Signature] + * void () + */ +/* SIGNAL */ +public final function SimpleSlot OnStart(AcediaObject receiver) +{ + return SimpleSlot(onStartSignal.NewSlot(receiver)); +} + +/** + * Signal that will be emitted whenever trading time ends. + * + * [Signature] + * void (ATrader oldTrader, ATrader newTrader) + * + * @param oldTrader Trader that was selected before this event. + * @param newTrader Trader that will be selected after this event. + */ +/* SIGNAL */ +public final function SimpleSlot OnEnd(AcediaObject receiver) +{ + return SimpleSlot(onEndSignal.NewSlot(receiver)); +} + +/** + * Signal that will be emitted whenever a new trader is selected. + * + * [Signature] + * void () + */ +/* SIGNAL */ +public final function Trading_OnSelect_Slot OnTraderSelected( + AcediaObject receiver) +{ + return Trading_OnSelect_Slot(onTraderSelectSignal.NewSlot(receiver)); +} + +/** + * Returns array with all existing traders (including disabled once) on + * the level. + * + * @return Array of existing traders on the level. Guaranteed to not contain + * `none`-references. None of them should be deallocated, + * otherwise Acedia's behavior is undefined. + */ +public function array GetTraders(); + +/** + * Checks whether trading is currently active. + * + * For classic KF game mode it means that it is trader time and one + * (or several) traders are open. + * This interface does not impose such limitation on trading: it is + * allowed to be active at any time, independent of anything else. However + * trading should only be permitted while trading is active. + * + * @return `true` if trading is active and `false` otherwise. + */ +public function bool IsTradingActive(); + +/** + * Changes current status of trading. + * + * @see `IsTradingActive()` for more details. + */ +public function SetTradingStatus(bool makeActive); + +/** + * Returns the amount of time (in seconds) trading period will last for. + * + * For classic KF game mode it refers to how long trader time is + * (`60` seconds by default). + * + * @return Amount of time (in seconds) trading period will last for. + */ +public function int GetTradingInterval(); + +/** + * Changes the amount of time (in seconds) trading period will last for. + * + * Changing this setting only affect current round (until the end of the map). + * + * For classic KF game mode it refers to how long trader time is + * (`60` seconds by default). + * + * @param newTradingInterval New length of the trading period. + */ +public function SetTradingInterval(int newTradingInterval); + +/** + * Return amount of time remaining in the current trading period. + * + * For classic KF game mode this refers to remaining trading time. + * + * @return Amount of time remaining in the current trading period. + * `0` if trading is currently inactive. + */ +public function int GetCountdown(); + +/** + * Changes amount of time remaining in the current trading period. + * + * For classic KF game mode this refers to remaining trading time. + * + * @param newTradingInterval New amount of time that should remain in the + * current trading period. Values `<= 0` will lead to trading time ending + * immediately. + */ +public function SetCountdown(int newTradingInterval); + +/** + * Checks whether trading countdown was paused. + * + * Pause only affects current trading period and will be reset after + * the next starts. + * + * @return `true` if trading countdown was paused and `false` otherwise. + * If trading is inactive - returns `false`. + */ +public function bool IsCountDownPaused(); + +/** + * Changes whether trading countdown should be paused. + * + * Pause set by this method only affects current trading period and will be + * reset after the next starts. + * + * @return doPause `true` to pause trading countdown and `false` to resume. + * If trading time is currently inactive - does nothing. + */ +public function SetCountDownPause(bool doPause); + +/** + * Returns currently selected trader. + * + * For classing KF game mode selected trader means the trader currently + * pointed at by the arrow in the top left corner on HUD and by the red wisp + * during trading time. + * This interface allows to generalize the concept of select trader to any + * specially marked trader or even not make use of it at all. + * Changing a selected trader in any way should always be followed + * by emitting `OnTraderSelected()` signal. + * After `SelectTrader()` call `GetSelectedTrader()` should return + * specified `ATrader`. If selected trader changes in some other way, it should + * first result in emitted `OnTraderSelected()` signal. + * + * @return Currently selected trader. + */ +public function ATrader GetSelectedTrader(); + +/** + * Changes currently selected trader. + * + * @see `GetSelectedTrader()` for more details. + */ +public function SelectTrader(ATrader newSelection); + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Signal.uc b/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Signal.uc new file mode 100644 index 0000000..72a6efa --- /dev/null +++ b/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Signal.uc @@ -0,0 +1,39 @@ +/** + * Signal class implementation for `ATradingComponent`, for detecting when + * another trader is selected. + * Copyright 2021 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 Trading_OnSelect_Signal extends Signal; + +public final function Emit(ATrader oldTrader, ATrader newTrader) +{ + local Slot nextSlot; + StartIterating(); + nextSlot = GetNextSlot(); + while (nextSlot != none) + { + Trading_OnSelect_Slot(nextSlot).connect(oldTrader, newTrader); + nextSlot = GetNextSlot(); + } + CleanEmptySlots(); +} + +defaultproperties +{ + relatedSlotClass = class'Trading_OnSelect_Slot' +} \ No newline at end of file diff --git a/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Slot.uc b/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Slot.uc new file mode 100644 index 0000000..4a625b0 --- /dev/null +++ b/sources/Gameplay/BaseClasses/KillingFloor/Trading/Events/Trading_OnSelect_Slot.uc @@ -0,0 +1,41 @@ +/** + * Slot class implementation for `ATradingComponent`'s signal for + * detecting when another trader is selected. + * Copyright 2021 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 Trading_OnSelect_Slot extends Slot; + +delegate connect(ATrader oldTrader, ATrader newTrader) +{ + DummyCall(); +} + +protected function Constructor() +{ + connect = none; +} + +protected function Finalizer() +{ + super.Finalizer(); + connect = none; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/KF1_Frontend.uc b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc new file mode 100644 index 0000000..4200ded --- /dev/null +++ b/sources/Gameplay/KF1Frontend/KF1_Frontend.uc @@ -0,0 +1,27 @@ +/** + * Frontend implementation for classic `KFGameType` that changes as little as + * possible and only on request from another mod, otherwise not altering + * gameplay at all. + * Copyright 2021 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class KF1_Frontend extends KFFrontend; + +defaultproperties +{ + tradingClass = class'KF1_TradingComponent' +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/Trading/KF1_Trader.uc b/sources/Gameplay/KF1Frontend/Trading/KF1_Trader.uc new file mode 100644 index 0000000..a141838 --- /dev/null +++ b/sources/Gameplay/KF1Frontend/Trading/KF1_Trader.uc @@ -0,0 +1,248 @@ +/** + * `ATrader`'s implementation for `KF1_Frontend`. + * Wrapper for KF1's `ShopVolume`s. + * Copyright 2021 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class KF1_Trader extends ATrader; + +// We do not use any vanilla value as a name, instead storing and tracking it +// entirely as our own value. +var protected Text myName; +// Reference to `ShopVolume` actor that this `KF1_Trader` represents. +var protected NativeActorRef myShopVolume; + +protected function Finalizer() +{ + _.memory.Free(myShopVolume); + myShopVolume = none; +} + +/** + * Detect all existing traders on the level and created a `KF1_Trader` for + * each of them. + * + * @return Array of created `KF1_Trader`s. All of them are guaranteed to not + * be `none`. + */ +public static function array WrapVanillaShops() +{ + local int shopCounter; + local MutableText textBuilder; + local LevelInfo level; + local KFGameType kfGame; + local KF1_Trader nextTrader; + local array allTraders; + local ShopVolume nextShopVolume; + level = __().unreal.GetLevel(); + kfGame = __().unreal.GetKFGameType(); + textBuilder = __().text.Empty(); + foreach level.AllActors(class'ShopVolume', nextShopVolume) + { + if (nextShopVolume == none) continue; + if (!nextShopVolume.bObjectiveModeOnly || kfGame.bUsingObjectiveMode) + { + nextTrader = KF1_Trader(__().memory.Allocate(class'KF1_Trader')); + nextTrader.myShopVolume = __().unreal.ActorRef(nextShopVolume); + textBuilder.Clear().AppendPlainString("trader" $ shopCounter); + nextTrader.myName = textBuilder.Copy(); + allTraders[allTraders.length] = nextTrader; + shopCounter += 1; + } + } + textBuilder.FreeSelf(); + return allTraders; +} + +public function Text GetName() +{ + if (myName == none) { + return _.text.Empty(); + } + return myName.Copy(); +} + +public function ATrader SetName(Text newName) +{ + if (newName == none) return self; + if (newName.IsEmpty()) return self; + + myName.FreeSelf(); + newName = newName.Copy(); + return self; +} + +public function Vector GetLocation() +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume != none) { + return vanillaShopVolume.location; + } + return Vect(0, 0, 0); +} + +public function bool IsEnabled() +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume != none) { + return !vanillaShopVolume.bAlwaysClosed; + } + return false; +} + +public function ATrader SetEnabled(bool doEnable) +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume == none) { + return self; + } + if (doEnable) { + vanillaShopVolume.bAlwaysClosed = false; + } + else + { + vanillaShopVolume.bAlwaysClosed = true; + Close(); + BootPlayers(); + } + UpdateShopList(); + return self; +} + +/** + * This method re-fills `KFGameType.shopList` to contain only currently + * enabled traders. + */ +protected function UpdateShopList() +{ + local int i; + local ShopVolume nextShopVolume; + local KF1_Trader nextTrader; + local array shopVolumes; + local array availableTraders; + availableTraders = _.kf.trading.GetTraders(); + for (i = 0; i < availableTraders.length; i += 1) + { + nextTrader = KF1_Trader(availableTraders[i]); + if (nextTrader == none) continue; + if (!nextTrader.IsEnabled()) continue; + nextShopVolume = ShopVolume(nextTrader.myShopVolume.Get()); + if (nextShopVolume == none) continue; + + shopVolumes[shopVolumes.length] = nextShopVolume; + } + _.unreal.GetKFGameType().shopList = shopVolumes; +} + +public function bool IsAutoOpen() +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume != none) { + return vanillaShopVolume.bAlwaysEnabled; + } + return false; +} + +public function ATrader SetAutoOpen(bool doAutoOpen) +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume == none) { + return self; + } + if (doAutoOpen) { + vanillaShopVolume.bAlwaysEnabled = true; + } + else { + vanillaShopVolume.bAlwaysEnabled = false; + } + return self; +} + +public function bool IsOpen() +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume != none) { + return vanillaShopVolume.bCurrentlyOpen; + } + return false; +} + +public function ATrader SetOpen(bool doOpen) +{ + local ShopVolume vanillaShopVolume; + if (doOpen && !IsEnabled()) return self; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume == none) return self; + + if (doOpen) { + vanillaShopVolume.OpenShop(); + } + else { + vanillaShopVolume.CloseShop(); + } + return self; +} + +public function bool IsSelected() +{ + local ShopVolume vanillaShopVolume; + local KFGameReplicationInfo kfGameRI; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume == none) { + return false; + } + kfGameRI = _.unreal.GetKFGameRI(); + if (kfGameRI != none) { + return (kfGameRI.currentShop == vanillaShopVolume); + } + return false; +} + +public function ATrader Select() +{ + local ShopVolume vanillaShopVolume; + local KFGameReplicationInfo kfGameRI; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume == none) { + return self; + } + kfGameRI = _.unreal.GetKFGameRI(); + if (kfGameRI != none) { + kfGameRI.currentShop = vanillaShopVolume; + } + return self; +} + +public function ATrader BootPlayers() +{ + local ShopVolume vanillaShopVolume; + vanillaShopVolume = ShopVolume(myShopVolume.Get()); + if (vanillaShopVolume != none) { + vanillaShopVolume.BootPlayers(); + } + return self; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Gameplay/KF1Frontend/Trading/KF1_TradingComponent.uc b/sources/Gameplay/KF1Frontend/Trading/KF1_TradingComponent.uc new file mode 100644 index 0000000..434b14b --- /dev/null +++ b/sources/Gameplay/KF1Frontend/Trading/KF1_TradingComponent.uc @@ -0,0 +1,213 @@ +/** + * `ATradingComponent`'s implementation for `KF1_Frontend`. + * Only supports `KF1_Trader` as a possible trader class. + * Copyright 2021 Anton Tarasenko + *------------------------------------------------------------------------------ + * This file is part of Acedia. + * + * Acedia is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, or + * (at your option) any later version. + * + * Acedia is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Acedia. If not, see . + */ +class KF1_TradingComponent extends ATradingComponent; + +// Variables for enforcing a trader time pause by repeatedly setting +// `waveCountDown`'s value to `pausedCountDownValue` +var protected bool tradingCountDownPaused; +var protected int pausedCountDownValue; + +// For detecting events of trading becoming active/inactive and selecting +// a different trader, to account for these changing through non-Acedia means +var protected bool wasActiveLastCheck; +var protected Atrader lastSelectedTrader; + +// All known traders on map +var protected array registeredTraders; + +protected function Constructor() +{ + super.Constructor(); + _.unreal.OnTick(self).connect = Tick; + registeredTraders = class'KF1_Trader'.static.WrapVanillaShops(); + lastSelectedTrader = GetSelectedTrader(); + wasActiveLastCheck = IsTradingActive(); +} + +protected function Finalizer() +{ + super.Finalizer(); + _.unreal.OnTick(self).Disconnect(); + _.memory.FreeMany(registeredTraders); + registeredTraders.length = 0; +} + +public function array GetTraders() +{ + return registeredTraders; +} + +public function bool IsTradingActive() +{ + local KFGameType kfGame; + kfGame = _.unreal.GetKFGameType(); + return kfGame.IsInState('MatchInProgress') && kfGame.bTradingDoorsOpen; +} + +public function SetTradingStatus(bool makeActive) +{ + local bool isCurrentlyActive; + local KFGameType kfGame; + local KFGameReplicationInfo kfGameRI; + local KFMonster nextZed; + isCurrentlyActive = IsTradingActive(); + if (isCurrentlyActive == makeActive) { + return; + } + if (!makeActive && isCurrentlyActive) + { + SetCountDown(0); + return; + } + kfGame = _.unreal.GetKFGameType(); + kfGameRI = _.unreal.GetKFGameRI(); + foreach kfGame.DynamicActors(class'KFMonster', nextZed) + { + if (nextZed == none) continue; + if (nextZed.health <= 0) continue; + nextZed.Suicide(); + } + kfGame.totalMaxMonsters = 0; + kfGameRI.maxMonsters = 0; +} + +public function ATrader GetSelectedTrader() +{ + local int i; + for (i = 0; i < registeredTraders.length; i += 1) + { + if (registeredTraders[i].IsSelected()) { + return registeredTraders[i]; + } + } + return none; +} + +public function SelectTrader(ATrader newSelection) +{ + local ATrader oldSelection; + local KFGameReplicationInfo kfGameRI; + if (newSelection != none) { + newSelection.Select(); + } + else + { + kfGameRI = _.unreal.GetKFGameRI(); + if (kfGameRI != none) { + kfGameRI.currentShop = none; + } + } + // Emit signal, but first record new trader inside `lastSelectedTrader` + // in case someone decides it would be a grand idea to call `SelectTrader` + // during `onTraderSelectSignal` signal. + oldSelection = lastSelectedTrader; + lastSelectedTrader = newSelection; + if (lastSelectedTrader != newSelection) { + onTraderSelectSignal.Emit(oldSelection, newSelection); + } +} + +public function int GetTradingInterval() +{ + return _.unreal.GetKFGameType().timeBetweenWaves; +} + +public function SetTradingInterval(int newTradingInterval) +{ + if (newTradingInterval > 0) { + _.unreal.GetKFGameType().timeBetweenWaves = Max(newTradingInterval, 1); + } +} + +public function int GetCountDown() +{ + if (!IsTradingActive()) { + return 0; + } + return _.unreal.GetKFGameType().waveCountDown; +} + +public function SetCountDown(int newCountDownValue) +{ + local KFGameType kfGame; + if (!IsTradingActive()) { + return; + } + kfGame = _.unreal.GetKFGameType(); + if (kfGame.waveCountDown >= 5 && newCountDownValue < 5) { + _.unreal.GetKFGameRI().waveNumber = kfGame.waveNum; + } + kfGame.waveCountDown = Max(newCountDownValue, 1); + pausedCountDownValue = newCountDownValue; +} + +public function bool IsCountDownPaused() +{ + if (!IsTradingActive()) { + return false; + } + return tradingCountDownPaused; +} + +public function SetCountDownPause(bool doPause) +{ + tradingCountDownPaused = doPause; + if (doPause) { + pausedCountDownValue = _.unreal.GetKFGameType().waveCountDown; + } +} + +protected function Tick(float delta, float timeScaleCoefficient) +{ + local bool isActiveNow; + local ATrader newSelectedTrader; + // Enforce pause + if (tradingCountDownPaused) { + _.unreal.GetKFGameType().waveCountDown = pausedCountDownValue; + } + // Selected trader check + newSelectedTrader = GetSelectedTrader(); + if (lastSelectedTrader != newSelectedTrader) + { + onTraderSelectSignal.Emit(lastSelectedTrader, newSelectedTrader); + lastSelectedTrader = newSelectedTrader; + } + // Active status check + isActiveNow = IsTradingActive(); + if (wasActiveLastCheck != isActiveNow) + { + wasActiveLastCheck = isActiveNow; + if (isActiveNow) + { + onStartSignal.Emit(); + } + else + { + onEndSignal.Emit(); + // Reset pause after trading time has ended + tradingCountDownPaused = false; + } + } +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Global.uc b/sources/Global.uc index 0e046d4..9a7de61 100644 --- a/sources/Global.uc +++ b/sources/Global.uc @@ -40,6 +40,8 @@ var public UserAPI users; var public PlayersAPI players; var public JSONAPI json; +var public KFFrontend kf; + public final static function Global GetInstance() { if (default.myself == none) { @@ -70,5 +72,6 @@ protected function Initialize() users = UserAPI(memory.Allocate(class'UserAPI')); players = PlayersAPI(memory.Allocate(class'PlayersAPI')); json = JSONAPI(memory.Allocate(class'JSONAPI')); + kf = KFFrontend(memory.Allocate(class'KF1_Frontend')); json.StaticConstructor(); } \ No newline at end of file